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 }