001package de.deepamehta.core.impl; 002 003import de.deepamehta.core.AssociationDefinition; 004import de.deepamehta.core.model.AssociationDefinitionModel; 005import de.deepamehta.core.model.AssociationModel; 006import de.deepamehta.core.model.ChildTopicsModel; 007import de.deepamehta.core.model.DeepaMehtaObjectModel; 008import de.deepamehta.core.model.RelatedTopicModel; 009import de.deepamehta.core.model.TopicDeletionModel; 010import de.deepamehta.core.model.TopicModel; 011import de.deepamehta.core.model.TopicRoleModel; 012import de.deepamehta.core.model.TypeModel; 013import de.deepamehta.core.model.ViewConfigurationModel; 014 015import org.codehaus.jettison.json.JSONException; 016import org.codehaus.jettison.json.JSONObject; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.List; 021import java.util.logging.Logger; 022 023 024 025/** 026 * Definition of an association between 2 topic types -- part of DeepaMehta's type system, 027 * like an association in a class diagram. Used to represent both, aggregations and compositions. 028 * ### FIXDOC: also assoc types have assoc defs 029 * 030 * @author <a href="mailto:jri@deepamehta.de">Jörg Richter</a> 031 */ 032class AssociationDefinitionModelImpl extends AssociationModelImpl implements AssociationDefinitionModel { 033 034 // ---------------------------------------------------------------------------------------------- Instance Variables 035 036 private String parentCardinalityUri; 037 private String childCardinalityUri; 038 039 private ViewConfigurationModelImpl viewConfig; // is never null 040 041 private Logger logger = Logger.getLogger(getClass().getName()); 042 043 // ---------------------------------------------------------------------------------------------------- Constructors 044 045 /** 046 * ### TODO: add include-in-label parameter? 047 * 048 * @param customAssocTypeUri if null no custom association type will be set. 049 */ 050 AssociationDefinitionModelImpl(AssociationModelImpl assoc, String parentCardinalityUri, String childCardinalityUri, 051 ViewConfigurationModelImpl viewConfig) { 052 super(assoc); 053 this.parentCardinalityUri = parentCardinalityUri; 054 this.childCardinalityUri = childCardinalityUri; 055 this.viewConfig = viewConfig != null ? viewConfig : mf.newViewConfigurationModel(); 056 // ### TODO: why null check? Compare to TypeModelImpl constructor 057 } 058 059 // -------------------------------------------------------------------------------------------------- Public Methods 060 061 @Override 062 public String getAssocDefUri() { 063 String customAssocTypeUri = getCustomAssocTypeUriOrNull(); 064 return getChildTypeUri() + (customAssocTypeUri !=null ? "#" + customAssocTypeUri : ""); 065 } 066 067 @Override 068 public String getCustomAssocTypeUri() { 069 TopicModel customAssocType = getCustomAssocType(); 070 return customAssocType != null ? customAssocType.getUri() : null; 071 } 072 073 /** 074 * The type to be used to create an association instance based on this association definition. 075 */ 076 @Override 077 public String getInstanceLevelAssocTypeUri() { 078 String customAssocTypeUri = getCustomAssocTypeUri(); 079 return customAssocTypeUri !=null ? customAssocTypeUri : defaultInstanceLevelAssocTypeUri(); 080 } 081 082 @Override 083 public String getParentTypeUri() { 084 return ((TopicRoleModel) getRoleModel("dm4.core.parent_type")).getTopicUri(); 085 } 086 087 @Override 088 public String getChildTypeUri() { 089 return ((TopicRoleModel) getRoleModel("dm4.core.child_type")).getTopicUri(); 090 } 091 092 @Override 093 public String getParentCardinalityUri() { 094 return parentCardinalityUri; 095 } 096 097 @Override 098 public String getChildCardinalityUri() { 099 return childCardinalityUri; 100 } 101 102 @Override 103 public ViewConfigurationModelImpl getViewConfigModel() { 104 return viewConfig; 105 } 106 107 // --- 108 109 @Override 110 public void setParentCardinalityUri(String parentCardinalityUri) { 111 this.parentCardinalityUri = parentCardinalityUri; 112 } 113 114 @Override 115 public void setChildCardinalityUri(String childCardinalityUri) { 116 this.childCardinalityUri = childCardinalityUri; 117 } 118 119 @Override 120 public void setViewConfigModel(ViewConfigurationModel viewConfig) { 121 this.viewConfig = (ViewConfigurationModelImpl) viewConfig; 122 } 123 124 // --- 125 126 @Override 127 public JSONObject toJSON() { 128 try { 129 return super.toJSON() 130 .put("parent_cardinality_uri", parentCardinalityUri) 131 .put("child_cardinality_uri", childCardinalityUri) 132 .put("view_config_topics", viewConfig.toJSONArray()); 133 } catch (Exception e) { 134 throw new RuntimeException("Serialization failed (" + this + ")", e); 135 } 136 } 137 138 // --- 139 140 @Override 141 public String toString() { 142 return "\n association definition (" + super.toString() + 143 ",\n parent cardinality=\"" + parentCardinalityUri + 144 "\",\n child cardinality=\"" + childCardinalityUri + 145 "\",\n " + viewConfig + ")\n"; 146 } 147 148 // ----------------------------------------------------------------------------------------- Package Private Methods 149 150 151 152 @Override 153 String className() { 154 return "association definition"; 155 } 156 157 @Override 158 AssociationDefinition instantiate() { 159 return new AssociationDefinitionImpl(this, pl); 160 } 161 162 163 164 // === Core Internal Hooks === 165 166 @Override 167 void postUpdate(DeepaMehtaObjectModel newModel, DeepaMehtaObjectModel oldModel) { 168 super.postUpdate(newModel, oldModel); 169 // 170 updateCardinality((AssociationDefinitionModel) newModel); 171 // 172 // rehash 173 boolean changeCustomAssocType = customAssocTypeChange((AssociationDefinitionModel) newModel, 174 (AssociationDefinitionModel) oldModel); 175 if (changeCustomAssocType) { 176 logger.info("### Changed custom association type URI from \"" + 177 ((AssociationDefinitionModelImpl) oldModel).getCustomAssocTypeUri() + "\" -> \"" + 178 ((AssociationDefinitionModelImpl) newModel).getCustomAssocTypeUriOrNull() + "\""); 179 getParentType().rehashAssocDef(newModel.getId()); 180 } 181 } 182 183 184 185 // === Update (memory + DB) === 186 187 void updateParentCardinalityUri(String parentCardinalityUri) { 188 setParentCardinalityUri(parentCardinalityUri); // update memory 189 pl.typeStorage.storeParentCardinalityUri(id, parentCardinalityUri); // update DB 190 } 191 192 void updateChildCardinalityUri(String childCardinalityUri) { 193 setChildCardinalityUri(childCardinalityUri); // update memory 194 pl.typeStorage.storeChildCardinalityUri(id, childCardinalityUri); // update DB 195 } 196 197 198 199 // === 200 201 /** 202 * ### TODO: make private 203 * 204 * @return <code>null</code> if this assoc def's custom assoc type model is null or represents a deletion ref. 205 * Otherwise returns the custom assoc type URI. 206 */ 207 String getCustomAssocTypeUriOrNull() { 208 return getCustomAssocType() instanceof TopicDeletionModel ? null : getCustomAssocTypeUri(); 209 } 210 211 // --- 212 213 TypeModelImpl getParentType() { 214 return pl.typeStorage.getType(getParentTypeUri()); 215 } 216 217 // ------------------------------------------------------------------------------------------------- Private Methods 218 219 220 221 // === Update === 222 223 private void updateCardinality(AssociationDefinitionModel newModel) { 224 updateParentCardinality(newModel.getParentCardinalityUri()); 225 updateChildCardinality(newModel.getChildCardinalityUri()); 226 } 227 228 // --- 229 230 private void updateParentCardinality(String newParentCardinalityUri) { 231 // abort if no update is requested 232 if (newParentCardinalityUri == null) { 233 return; 234 } 235 // 236 String parentCardinalityUri = getParentCardinalityUri(); 237 if (!parentCardinalityUri.equals(newParentCardinalityUri)) { 238 logger.info("### Changing parent cardinality URI from \"" + parentCardinalityUri + "\" -> \"" + 239 newParentCardinalityUri + "\""); 240 updateParentCardinalityUri(newParentCardinalityUri); 241 } 242 } 243 244 private void updateChildCardinality(String newChildCardinalityUri) { 245 // abort if no update is requested 246 if (newChildCardinalityUri == null) { 247 return; 248 } 249 // 250 String childCardinalityUri = getChildCardinalityUri(); 251 if (!childCardinalityUri.equals(newChildCardinalityUri)) { 252 logger.info("### Changing child cardinality URI from \"" + childCardinalityUri + "\" -> \"" + 253 newChildCardinalityUri + "\""); 254 updateChildCardinalityUri(newChildCardinalityUri); 255 } 256 } 257 258 259 260 // ==== 261 262 private boolean customAssocTypeChange(AssociationDefinitionModel newModel, AssociationDefinitionModel oldModel) { 263 String oldUri = oldModel.getCustomAssocTypeUri(); // null if no assoc type is set 264 String newUri = ((AssociationDefinitionModelImpl) newModel).getCustomAssocTypeUriOrNull(); // null if del ref 265 if (newUri != null) { 266 // new value is neither a deletion ref nor null, compare it to old value (which may be null) 267 return !newUri.equals(oldUri); 268 } else { 269 // compare old value to null if new value is a deletion ref or null 270 // ### FIXME: must differentiate "no change requested" (= null) and "remove current assignment" (= del ref)? 271 return oldUri != null; 272 } 273 } 274 275 private RelatedTopicModel getCustomAssocType() { 276 RelatedTopicModel customAssocType = getChildTopicsModel().getTopicOrNull( 277 "dm4.core.assoc_type#dm4.core.custom_assoc_type"); 278 // Note: we can't do this sanity check because a type model would not even deserialize. 279 // The type model JSON constructor repeatedly calls addAssocDef() which hashes by assoc def URI. ### still true? 280 /* if (customAssocType instanceof TopicDeletionModel) { 281 throw new RuntimeException("Tried to get an assoc def's custom assoc type when it is a deletion " + 282 "reference (" + this + ")"); 283 } */ 284 return customAssocType; 285 } 286 287 private String defaultInstanceLevelAssocTypeUri() { 288 if (typeUri.equals("dm4.core.aggregation_def")) { 289 return "dm4.core.aggregation"; 290 } else if (typeUri.equals("dm4.core.composition_def")) { 291 return "dm4.core.composition"; 292 } else { 293 throw new RuntimeException("Unexpected association type URI: \"" + typeUri + "\""); 294 } 295 } 296}