001    package de.deepamehta.plugins.topicmaps.model;
002    
003    import de.deepamehta.plugins.topicmaps.ViewmodelCustomizer;
004    
005    import de.deepamehta.core.JSONEnabled;
006    import de.deepamehta.core.RelatedAssociation;
007    import de.deepamehta.core.RelatedTopic;
008    import de.deepamehta.core.Topic;
009    import de.deepamehta.core.model.CompositeValueModel;
010    import de.deepamehta.core.service.DeepaMehtaService;
011    import de.deepamehta.core.service.ResultList;
012    import de.deepamehta.core.util.DeepaMehtaUtils;
013    
014    import org.codehaus.jettison.json.JSONObject;
015    
016    import java.awt.Point;
017    
018    import java.util.HashMap;
019    import java.util.List;
020    import java.util.Map;
021    import java.util.logging.Logger;
022    
023    
024    
025    /**
026     * A topicmap viewmodel: a collection of topics and associations plus their view properties.
027     * <p>
028     * Features:
029     * - load from DB (by constructor).
030     * - Serialization to JSON.
031     */
032    public class TopicmapViewmodel implements JSONEnabled {
033    
034        // ---------------------------------------------------------------------------------------------- Instance Variables
035    
036        private Topic topicmapTopic;
037        private Map<Long, TopicViewmodel> topics = new HashMap();
038        private Map<Long, AssociationViewmodel> assocs = new HashMap();
039    
040        private DeepaMehtaService dms;
041        private List<ViewmodelCustomizer> customizers;
042    
043        private Logger logger = Logger.getLogger(getClass().getName());
044    
045        // ---------------------------------------------------------------------------------------------------- Constructors
046    
047        /**
048         * Loads a topicmap from the DB.
049         */
050        public TopicmapViewmodel(long topicmapId, DeepaMehtaService dms, List<ViewmodelCustomizer> customizers) {
051            this.topicmapTopic = dms.getTopic(topicmapId, true);    // fetchComposite=true
052            this.dms = dms;
053            this.customizers = customizers;
054            //
055            logger.info("Loading topicmap " + getId());
056            loadTopics();
057            loadAssociations();
058        }
059    
060        // -------------------------------------------------------------------------------------------------- Public Methods
061    
062        public long getId() {
063            return topicmapTopic.getId();
064        }
065    
066        // ---
067    
068        public Iterable<TopicViewmodel> getTopics() {
069            return topics.values();
070        }
071    
072        public Iterable<AssociationViewmodel> getAssociations() {
073            return assocs.values();
074        }
075    
076        // ---
077    
078        @Override
079        public JSONObject toJSON() {
080            try {
081                JSONObject topicmap = new JSONObject();
082                topicmap.put("info", topicmapTopic.toJSON());
083                topicmap.put("topics", DeepaMehtaUtils.objectsToJSON(topics.values()));
084                topicmap.put("assocs", DeepaMehtaUtils.objectsToJSON(assocs.values()));
085                return topicmap;
086            } catch (Exception e) {
087                throw new RuntimeException("Serialization failed (" + this + ")", e);
088            }
089        }
090    
091        @Override
092        public String toString() {
093            return "topicmap " + getId();
094        }
095    
096        // -------------------------------------------------------------------------------------------- Public Inner Classes
097    
098        // Note: there is a client-side equivalent in canvas_view.js (deepamehta-webclient plugin)
099        public class GridPositioning {
100    
101            // Settings
102            private final int GRID_DIST_X = 220;    // MAX_TOPIC_LABEL_WIDTH + 20 pixel padding
103            private final int GRID_DIST_Y = 80;
104            private final int START_X;
105            private final int START_Y = 50;
106            private final int MIN_Y = -9999;
107    
108            private final int canvasWidth;
109            private final int transX;
110    
111            private int gridX;
112            private int gridY;
113    
114            // --- Constructors ---
115    
116            public GridPositioning(int canvasWidth, int transX) {
117                this.canvasWidth = canvasWidth;
118                this.transX = transX;
119                START_X = 50 - transX;
120                //
121                Point startPos = findStartPostition();
122                gridX = startPos.x;
123                gridY = startPos.y;
124            }
125    
126            // --- Public Methods ---
127    
128            public Point nextPosition() {
129                Point pos = new Point(gridX, gridY);
130                advancePosition();
131                return pos;
132            }
133    
134            // --- Private Methods ---
135    
136            private Point findStartPostition() {
137                int maxY = MIN_Y;
138                for (TopicViewmodel topic : topics.values()) {
139                    if (topic.getY() > maxY) {
140                        maxY = topic.getY();
141                    }
142                }
143                int x = START_X;
144                int y = maxY != MIN_Y ? maxY + GRID_DIST_Y : START_Y;
145                return new Point(x, y);
146            }
147    
148            private void advancePosition() {
149                if (gridX + GRID_DIST_X + transX > canvasWidth) {
150                    gridX = START_X;
151                    gridY += GRID_DIST_Y;
152                } else {
153                    gridX += GRID_DIST_X;
154                }
155            }
156        }
157    
158        // ------------------------------------------------------------------------------------------------- Private Methods
159    
160        private void loadTopics() {
161            ResultList<RelatedTopic> topics = topicmapTopic.getRelatedTopics("dm4.topicmaps.topic_mapcontext",
162                "dm4.core.default", "dm4.topicmaps.topicmap_topic", null, false, true, 0);
163                // othersTopicTypeUri=null, fetchComposite=false, fetchRelatingComposite=true, maxResultSize=0
164            for (RelatedTopic topic : topics) {
165                CompositeValueModel viewProps = topic.getRelatingAssociation().getCompositeValue().getModel();
166                invokeViewmodelCustomizers(topic, viewProps);
167                addTopic(new TopicViewmodel(topic.getModel(), viewProps));
168            }
169        }
170    
171        private void loadAssociations() {
172            List<RelatedAssociation> assocs = topicmapTopic.getRelatedAssociations("dm4.topicmaps.association_mapcontext",
173                "dm4.core.default", "dm4.topicmaps.topicmap_association", null, false, false);
174            for (RelatedAssociation assoc : assocs) {
175                addAssociation(new AssociationViewmodel(assoc.getModel()));
176            }
177        }
178    
179        // ---
180    
181        // ### There is a copy in TopicmapsPlugin
182        private void invokeViewmodelCustomizers(Topic topic, CompositeValueModel viewProps) {
183            for (ViewmodelCustomizer customizer : customizers) {
184                invokeViewmodelCustomizer(customizer, topic, viewProps);
185            }
186        }
187    
188        // ### There is a principal copy in TopicmapsPlugin
189        private void invokeViewmodelCustomizer(ViewmodelCustomizer customizer, Topic topic, CompositeValueModel viewProps) {
190            try {
191                customizer.enrichViewProperties(topic, viewProps);
192            } catch (Exception e) {
193                throw new RuntimeException("Invoking viewmodel customizer for topic " + topic.getId() + " failed " +
194                    "(customizer=\"" + customizer.getClass().getName() + "\", method=\"enrichViewProperties\")", e);
195            }
196        }
197    
198        // ---
199    
200        private void addTopic(TopicViewmodel topic) {
201            topics.put(topic.getId(), topic);
202        }
203    
204        private void addAssociation(AssociationViewmodel assoc) {
205            assocs.put(assoc.getId(), assoc);
206        }
207    }