001    package de.deepamehta.plugins.mail;
002    
003    import static de.deepamehta.plugins.mail.MailPlugin.*;
004    
005    import java.util.ArrayList;
006    import java.util.Collections;
007    import java.util.Comparator;
008    import java.util.HashMap;
009    import java.util.List;
010    import java.util.Map;
011    import java.util.Set;
012    import java.util.logging.Logger;
013    
014    import de.deepamehta.core.RelatedTopic;
015    import de.deepamehta.core.Topic;
016    import de.deepamehta.core.model.CompositeValueModel;
017    import de.deepamehta.core.model.TopicModel;
018    import de.deepamehta.core.service.ClientState;
019    import de.deepamehta.core.service.DeepaMehtaService;
020    
021    public class Autocomplete {
022    
023        private static Logger log = Logger.getLogger(Autocomplete.class.getName());
024    
025        private final DeepaMehtaService dms;
026    
027        private final MailConfigurationCache config;
028    
029        public static final Comparator<TopicModel> VALUE_COMPARATOR = new Comparator<TopicModel>() {
030            @Override
031            public int compare(TopicModel a, TopicModel b) {
032                return a.getSimpleValue().toString().compareTo(b.getSimpleValue().toString());
033            }
034        };
035    
036        public Autocomplete(DeepaMehtaService dms, MailConfigurationCache config) {
037            this.dms = dms;
038            this.config = config;
039        }
040    
041        /**
042         * call a search on all configured topic types and on all email addresses
043         * 
044         * @param query
045         * @param clientState
046         * 
047         * @return topic list with email ID and contact type URI
048         */
049        public List<TopicModel> search(String query, ClientState clientState) {
050            // search and hash parent results by ID (to overwrite duplicates)
051            Map<Long, Topic> parents = new HashMap<Long, Topic>();
052            for (String uri : config.getSearchTypeUris()) {
053                for (Topic topic : dms.searchTopics(query, uri)) {
054                    Topic parent = getParent(topic);
055                    parents.put(parent.getId(), parent);
056                }
057            }
058    
059            // get and hash addresses of each parent
060            Map<Long, TopicModel> addresses = new HashMap<Long, TopicModel>();
061            for (Topic result : parents.values()) {
062                Topic parent = dms.getTopic(result.getId(), true);
063                for (TopicModel address : getEmailAddresses(parent, clientState)) {
064                    putAddress(addresses, parent, address);
065                }
066            }
067    
068            // search email directly afterwards and merge the results
069            Set<Topic> searchTopics = dms.searchTopics(query, EMAIL_ADDRESS);
070            for (Topic address : searchTopics) {
071                TopicModel model = address.getModel();
072                if (addresses.containsKey(model.getId()) == false) {
073                    putAddress(addresses, getParent(address), model);
074                }
075            }
076    
077            // wrap, sort and return
078            List<TopicModel> result = new ArrayList<TopicModel>(addresses.values());
079            Collections.sort(result, VALUE_COMPARATOR);
080    
081            // FIXME inconsistent model => use a specific view model
082            return result;
083        }
084    
085        /**
086         * 
087         * @param topic
088         * @param clientState
089         * 
090         * @return list of mail addresses with at minimum one empty address
091         */
092        private List<TopicModel> getEmailAddresses(Topic topic, ClientState clientState) {
093            // FIXME attached value should support add(...) of child compositions
094            if (topic.getCompositeValue().has(EMAIL_ADDRESS) == false) {
095                log.warning("composite of " + topic.getSimpleValue() + " contains no email address");
096                topic.setCompositeValue(new CompositeValueModel().add(EMAIL_ADDRESS, //
097                        new TopicModel(EMAIL_ADDRESS)), clientState, null);
098            }
099            // return the existing or the newly created list of addresses
100            return topic.getCompositeValue().getModel().getTopics(EMAIL_ADDRESS);
101        }
102    
103        private RelatedTopic getParent(Topic child) {
104            return child.getRelatedTopic(COMPOSITION, CHILD, PARENT, null, false, false);
105        }
106    
107        /**
108         * puts an inconsistent model (address ID + contact type URI) of email
109         * address into the addresses map.
110         */
111        private void putAddress(Map<Long, TopicModel> addresses, Topic parent, TopicModel address) {
112            // concatenate values
113            address.setSimpleValue(parent.getSimpleValue() + //
114                    " &lt;" + address.getSimpleValue() + "&gt;");
115            // replace type URI
116            address.setTypeUri(parent.getTypeUri());
117            addresses.put(address.getId(), address);
118        }
119    }