001package de.deepamehta.core.impl;
002
003import de.deepamehta.core.AssociationDefinition;
004import de.deepamehta.core.JSONEnabled;
005import de.deepamehta.core.model.AssociationDefinitionModel;
006import de.deepamehta.core.model.AssociationModel;
007import de.deepamehta.core.model.DeepaMehtaObjectModel;
008import de.deepamehta.core.model.IndexMode;
009import de.deepamehta.core.model.TopicModel;
010import de.deepamehta.core.model.TypeModel;
011import de.deepamehta.core.model.ViewConfigurationModel;
012import de.deepamehta.core.service.Directive;
013import de.deepamehta.core.service.Directives;
014import de.deepamehta.core.util.SequencedHashMap;
015
016import org.codehaus.jettison.json.JSONArray;
017import org.codehaus.jettison.json.JSONObject;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Iterator;
022import java.util.List;
023import java.util.logging.Logger;
024
025
026
027class TypeModelImpl extends TopicModelImpl implements TypeModel {
028
029    // ---------------------------------------------------------------------------------------------- Instance Variables
030
031    private String dataTypeUri;
032    private List<IndexMode> indexModes;
033    private SequencedHashMap<String, AssociationDefinitionModelImpl> assocDefs; // is never null, may be empty
034    private List<String> labelConfig;                                           // is never null, may be empty
035    private ViewConfigurationModelImpl viewConfig;                              // is never null
036
037    private Logger logger = Logger.getLogger(getClass().getName());
038
039    // ---------------------------------------------------------------------------------------------------- Constructors
040
041    TypeModelImpl(TopicModelImpl typeTopic, String dataTypeUri, List<IndexMode> indexModes,
042                  List<AssociationDefinitionModel> assocDefs, List<String> labelConfig,
043                  ViewConfigurationModelImpl viewConfig) {
044        super(typeTopic);
045        this.dataTypeUri = dataTypeUri;
046        this.indexModes  = indexModes;
047        this.assocDefs   = toMap(assocDefs);
048        this.labelConfig = labelConfig;
049        this.viewConfig  = viewConfig;
050    }
051
052    TypeModelImpl(TypeModelImpl type) {
053        super(type);
054        this.dataTypeUri = type.getDataTypeUri();
055        this.indexModes  = type.getIndexModes();
056        this.assocDefs   = toMap(type.getAssocDefs());
057        this.labelConfig = type.getLabelConfig();
058        this.viewConfig  = type.getViewConfigModel();
059    }
060
061    // -------------------------------------------------------------------------------------------------- Public Methods
062
063
064
065    // === Data Type ===
066
067    @Override
068    public String getDataTypeUri() {
069        return dataTypeUri;
070    }
071
072    @Override
073    public void setDataTypeUri(String dataTypeUri) {
074        this.dataTypeUri = dataTypeUri;
075    }
076
077
078
079    // === Index Modes ===
080
081    @Override
082    public List<IndexMode> getIndexModes() {
083        return indexModes;
084    }
085
086    @Override
087    public void addIndexMode(IndexMode indexMode) {
088        indexModes.add(indexMode);
089    }
090
091
092
093    // === Association Definitions ===
094
095    @Override
096    public Collection<AssociationDefinitionModelImpl> getAssocDefs() {
097        return assocDefs.values();
098    }
099
100    @Override
101    public AssociationDefinitionModelImpl getAssocDef(String assocDefUri) {
102        return getAssocDefOrThrow(assocDefUri);
103    }
104
105    @Override
106    public boolean hasAssocDef(String assocDefUri) {
107        return _getAssocDef(assocDefUri) != null;
108    }
109
110    /**
111     * @param   assocDef    the assoc def to add.
112     *                      Note: its ID might be uninitialized (-1).
113     */
114    @Override
115    public TypeModel addAssocDef(AssociationDefinitionModel assocDef) {
116        return addAssocDefBefore(assocDef, null);   // beforeAssocDefUri=null
117    }
118
119    /**
120     * @param   assocDef            the assoc def to add.
121     *                              Note: its ID might be uninitialized (-1).
122     * @param   beforeAssocDefUri   the URI of the assoc def <i>before</i> the given assoc def is inserted.
123     *                              If <code>null</code> the assoc def is appended at the end.
124     */
125    @Override
126    public TypeModel addAssocDefBefore(AssociationDefinitionModel assocDef, String beforeAssocDefUri) {
127        try {
128            // error check
129            String assocDefUri = assocDef.getAssocDefUri();
130            AssociationDefinitionModel existing = _getAssocDef(assocDefUri);
131            if (existing != null) {
132                throw new RuntimeException("Ambiguity: type \"" + getUri() + "\" has more than one \"" + assocDefUri +
133                    "\" association definitions");
134            }
135            //
136            assocDefs.putBefore(assocDefUri, (AssociationDefinitionModelImpl) assocDef, beforeAssocDefUri);
137            return this;
138        } catch (Exception e) {
139            throw new RuntimeException("Adding an association definition to type \"" + getUri() + "\" before \"" +
140                beforeAssocDefUri + "\" failed" + assocDef, e);
141        }
142    }
143
144    @Override
145    public AssociationDefinitionModel removeAssocDef(String assocDefUri) {
146        try {
147            AssociationDefinitionModel assocDef = assocDefs.remove(assocDefUri);
148            if (assocDef == null) {
149                throw new RuntimeException("Association definition \"" + assocDefUri + "\" not found in " +
150                    assocDefs.keySet());
151            }
152            return assocDef;
153        } catch (Exception e) {
154            throw new RuntimeException("Removing association definition \"" + assocDefUri + "\" from type \"" +
155                getUri() + "\" failed", e);
156        }
157    }
158
159
160
161    // === Label Configuration ===
162
163    @Override
164    public List<String> getLabelConfig() {
165        return labelConfig;
166    }
167
168    @Override
169    public void setLabelConfig(List<String> labelConfig) {
170        this.labelConfig = labelConfig;
171    }
172
173
174
175    // === View Configuration ===
176
177    @Override
178    public ViewConfigurationModelImpl getViewConfigModel() {
179        return viewConfig;
180    }
181
182    // FIXME: server-side operations on the view config settings possibly suggest they are not acually
183    // view config settings but part of the topic type model. Possibly this method should be dropped.
184    @Override
185    public Object getViewConfig(String typeUri, String settingUri) {
186        return viewConfig.getSetting(typeUri, settingUri);
187    }
188
189    @Override
190    public void setViewConfig(ViewConfigurationModel viewConfig) {
191        this.viewConfig = (ViewConfigurationModelImpl) viewConfig;
192    }
193
194
195
196    // === Iterable Implementation ===
197
198    /**
199     * Returns an interator which iterates this TypeModel's assoc def URIs.
200     */
201    @Override
202    public Iterator<String> iterator() {
203        return assocDefs.keySet().iterator();
204    }
205
206
207
208    // ****************************
209    // *** TopicModel Overrides ***
210    // ****************************
211
212
213
214    @Override
215    public JSONObject toJSON() {
216        try {
217            return super.toJSON()
218                .put("data_type_uri", getDataTypeUri())
219                .put("index_mode_uris", toJSONArray(indexModes))
220                .put("assoc_defs", toJSONArray(assocDefs.values()))
221                .put("label_config", new JSONArray(getLabelConfig()))
222                .put("view_config_topics", getViewConfigModel().toJSONArray());
223        } catch (Exception e) {
224            throw new RuntimeException("Serialization failed (" + this + ")", e);
225        }
226    }
227
228
229
230    // ****************
231    // *** Java API ***
232    // ****************
233
234
235
236    @Override
237    public String toString() {
238        return "id=" + id + ", uri=\"" + uri + "\", value=\"" + value + "\", typeUri=\"" + typeUri +
239            "\", dataTypeUri=\"" + getDataTypeUri() + "\", indexModes=" + getIndexModes() + ", assocDefs=" +
240            getAssocDefs() + ", labelConfig=" + getLabelConfig() + ", " + getViewConfigModel();
241    }
242
243    // ----------------------------------------------------------------------------------------- Package Private Methods
244
245
246
247    // === Abstract Methods ===
248
249    List<? extends DeepaMehtaObjectModelImpl> getAllInstances() {
250        throw new UnsupportedOperationException();
251    }
252
253    // ---
254
255    Directive getUpdateTypeDirective() {
256        throw new UnsupportedOperationException();
257    }
258
259    Directive getDeleteTypeDirective() {
260        throw new UnsupportedOperationException();
261    }
262
263
264
265    // === Core Internal Hooks ===
266
267    @Override
268    void preUpdate(DeepaMehtaObjectModel newModel) {
269        super.preUpdate(newModel);
270        //
271        // ### TODO: is it sufficient if we rehash (remove + add) at post-time?
272        if (uriChange(newModel.getUri(), uri)) {
273            removeFromTypeCache();
274        }
275    }
276
277    @Override
278    void postUpdate(DeepaMehtaObjectModel newModel, DeepaMehtaObjectModel oldModel) {
279        super.postUpdate(newModel, oldModel);
280        //
281        if (uriChange(newModel.getUri(), oldModel.getUri())) {
282            pl.typeStorage.putInTypeCache(this);
283        }
284        //
285        updateType((TypeModel) newModel);
286        //
287        // Note: the UPDATE_TOPIC_TYPE/UPDATE_ASSOCIATION_TYPE directive must be added *before* a possible UPDATE_TOPIC
288        // directive (added by DeepaMehtaObjectModelImpl.update()). In case of a changed type URI the webclient's type
289        // cache must be updated *before* the TopicTypeRenderer/AssociationTypeRenderer can render the type.
290        Directives.get().add(getUpdateTypeDirective(), instantiate());
291    }
292
293    // ---
294
295    @Override
296    void preDelete() {
297        super.preDelete();
298        //
299        int size = getAllInstances().size();
300        if (size > 0) {
301            throw new RuntimeException(size + " \"" + value + "\" instances still exist");
302        }
303    }
304
305    @Override
306    void postDelete() {
307        super.postDelete();
308        //
309        removeFromTypeCache();
310    }
311
312
313
314    // === Update (memory + DB) ===
315
316    void updateDataTypeUri(String dataTypeUri) {
317        setDataTypeUri(dataTypeUri);    // update memory
318        storeDataTypeUri();             // update DB
319    }
320
321    void updateLabelConfig(List<String> labelConfig) {
322        setLabelConfig(labelConfig);                                    // update memory
323        pl.typeStorage.updateLabelConfig(labelConfig, getAssocDefs());  // update DB
324    }
325
326    void _addIndexMode(IndexMode indexMode) {
327        // update memory
328        addIndexMode(indexMode);
329        // update DB
330        pl.typeStorage.storeIndexMode(uri, indexMode);
331        indexAllInstances(indexMode);
332    }
333
334    // ---
335
336    void _addAssocDefBefore(AssociationDefinitionModel assocDef, String beforeAssocDefUri) {
337        try {
338            long lastAssocDefId = lastAssocDefId();     // must be determined *before* memory is updated
339            //
340            // 1) update memory
341            // Note: the assoc def's custom association type is stored as a child topic. The meta model extension that
342            // adds "Association Type" as a child to the "Composition Definition" and "Aggregation Definition"
343            // association types has itself a custom association type (named "Custom Association Type"), see migration
344            // 5. It would not be stored as storage is model driven and the (meta) model doesn't know about custom
345            // associations as this very concept is introduced only by the assoc def being added here. So, the model
346            // must be updated (in-memory) *before* the assoc def is stored.
347            addAssocDefBefore(assocDef, beforeAssocDefUri);
348            //
349            // 2) update DB
350            pl.typeStorage.storeAssociationDefinition(assocDef);
351            long beforeAssocDefId = beforeAssocDefUri != null ? getAssocDef(beforeAssocDefUri).getId() : -1;
352            long firstAssocDefId = firstAssocDefId();   // must be determined *after* memory is updated
353            pl.typeStorage.addAssocDefToSequence(getId(), assocDef.getId(), beforeAssocDefId, firstAssocDefId,
354                lastAssocDefId);
355        } catch (Exception e) {
356            throw new RuntimeException("Adding an association definition to type \"" + getUri() + "\" before \"" +
357                beforeAssocDefUri + "\" failed" + assocDef, e);
358        }
359    }
360
361    void _removeAssocDef(String assocDefUri) {
362        // We trigger deleting an association definition by deleting the underlying association. This mimics deleting an
363        // association definition interactively in the webclient. Updating this type definition's memory and DB sequence
364        // is triggered then by the Type Editor plugin's preDeleteAssociation() hook.
365        // This way deleting an association definition works for both cases: 1) interactive deletion (when the user
366        // deletes an association), and 2) programmatical deletion (e.g. from a migration).
367        getAssocDef(assocDefUri).delete();
368    }
369
370
371
372    // === Type Editor Support ===
373
374    void _addAssocDef(AssociationModel assoc) {
375        _addAssocDefBefore(pl.typeStorage.newAssociationDefinition(assoc), null);    // beforeAssocDefUri=null
376    }
377
378    /**
379     * Note: in contrast to the other "update" methods this one updates the memory only, not the DB!
380     * If you want to update memory and DB use {@link AssociationDefinition#update}.
381     * <p>
382     * This method is here to support a special case: the user retypes an association which results in
383     * a changed type definition. In this case the DB is already up-to-date and only the type's memory
384     * representation must be updated. So, here the DB update is the *cause* for a necessary memory-update.
385     * Normally the situation is vice-versa: the DB update is the necessary *effect* of a memory-update.
386     *
387     * @param   assocDef    the new association definition.
388     *                      Note: in contrast to the other "update" methods this one does not support partial updates.
389     *                      That is all association definition fields must be initialized. ### FIXDOC
390     */
391    void _updateAssocDef(AssociationModel assoc) {
392        // Note: if the assoc def's custom association type is changed the assoc def URI changes as well.
393        // So we must identify the assoc def to update **by ID** and rehash (that is remove + add).
394        String[] assocDefUris = findAssocDefUris(assoc.getId());
395        AssociationDefinitionModel oldAssocDef = getAssocDef(assocDefUris[0]);
396        if (assoc == oldAssocDef) {
397            // edited via type topic -- abort
398            return;
399        }
400        // Note: we must not manipulate the assoc model in-place. The Webclient expects by-ID roles.
401        AssociationModel newAssocModel = mf.newAssociationModel(assoc);
402        AssociationModel oldAssocModel = oldAssocDef;
403        // Note: an assoc def expects by-URI roles.
404        newAssocModel.setRoleModel1(oldAssocModel.getRoleModel1());
405        newAssocModel.setRoleModel2(oldAssocModel.getRoleModel2());
406        //
407        AssociationDefinitionModel newAssocDef = mf.newAssociationDefinitionModel(newAssocModel,
408            oldAssocDef.getParentCardinalityUri(),
409            oldAssocDef.getChildCardinalityUri(), oldAssocDef.getViewConfigModel()
410        );
411        String oldAssocDefUri = oldAssocDef.getAssocDefUri();
412        String newAssocDefUri = newAssocDef.getAssocDefUri();
413        if (oldAssocDefUri.equals(newAssocDefUri)) {
414            replaceAssocDef(newAssocDef);
415        } else {
416            replaceAssocDef(newAssocDef, oldAssocDefUri, assocDefUris[1]);
417            //
418            // Note: if the custom association type has changed and the assoc def is part the label config
419            // we must replace the assoc def URI in the label config
420            replaceInLabelConfig(newAssocDefUri, oldAssocDefUri);
421        }
422    }
423
424    /**
425     * Removes an association from memory and rebuilds the sequence in DB. Note: the underlying
426     * association is *not* removed from DB.
427     * This method is called (by the Type Editor plugin's preDeleteAssociation() hook) when the
428     * deletion of an association that represents an association definition is imminent. ### FIXDOC
429     */
430    void _removeAssocDefFromMemoryAndRebuildSequence(AssociationModel assoc) {
431        String[] assocDefUris = findAssocDefUris(assoc.getId());
432        String assocDefUri = getAssocDef(assocDefUris[0]).getAssocDefUri();
433        // update memory
434        removeAssocDef(assocDefUri);
435        removeFromLabelConfig(assocDefUri);
436        // update DB
437        pl.typeStorage.rebuildSequence(this);
438    }
439
440
441
442    // ===
443
444    void rehashAssocDef(long assocDefId) {
445        String[] assocDefUris = findAssocDefUris(assocDefId);
446        rehashAssocDef(assocDefUris[0], assocDefUris[1]);
447    }
448
449    // ------------------------------------------------------------------------------------------------- Private Methods
450
451
452
453    // === Update (memory + DB) ===
454
455    private void updateType(TypeModel newModel) {
456        _updateDataTypeUri(newModel.getDataTypeUri());
457        _updateAssocDefs(newModel.getAssocDefs());
458        _updateSequence(newModel.getAssocDefs());
459        _updateLabelConfig(newModel.getLabelConfig());
460    }
461
462    // ---
463
464    private void _updateDataTypeUri(String newDataTypeUri) {
465        if (newDataTypeUri != null) {
466            String dataTypeUri = getDataTypeUri();
467            if (!dataTypeUri.equals(newDataTypeUri)) {
468                logger.info("### Changing data type URI from \"" + dataTypeUri + "\" -> \"" + newDataTypeUri + "\"");
469                updateDataTypeUri(newDataTypeUri);
470            }
471        }
472    }
473
474    private void _updateAssocDefs(Collection<? extends AssociationDefinitionModel> newAssocDefs) {
475        for (AssociationDefinitionModel assocDef : newAssocDefs) {
476            // Note: if the assoc def's custom association type was changed the assoc def URI changes as well.
477            // So we must identify the assoc def to update **by ID**.
478            // ### TODO: drop updateAssocDef() and rehash here (that is remove + add).
479            String[] assocDefUris = findAssocDefUris(assocDef.getId());
480            getAssocDef(assocDefUris[0]).update(assocDef);
481        }
482    }
483
484    private void _updateSequence(Collection<? extends AssociationDefinitionModel> newAssocDefs) {
485        try {
486            if (getAssocDefs().size() != newAssocDefs.size()) {
487                throw new RuntimeException("adding/removing of assoc defs not yet supported via type update");
488            }
489            if (hasSameAssocDefSequence(newAssocDefs)) {
490                return;
491            }
492            // update memory
493            logger.info("### Changing assoc def sequence (" + getAssocDefs().size() + " items)");
494            rehashAssocDefs(newAssocDefs);
495            // update DB
496            pl.typeStorage.rebuildSequence(this);
497        } catch (Exception e) {
498            throw new RuntimeException("Updating the assoc def sequence failed", e);
499        }
500    }
501
502    private void _updateLabelConfig(List<String> newLabelConfig) {
503        try {
504            if (!getLabelConfig().equals(newLabelConfig)) {
505                logger.info("### Changing label configuration from " + getLabelConfig() + " -> " + newLabelConfig);
506                updateLabelConfig(newLabelConfig);
507            }
508        } catch (Exception e) {
509            throw new RuntimeException("Updating label configuration of type \"" + getUri() + "\" failed", e);
510        }
511    }
512
513
514
515    // === Store (DB only) ===
516
517    private void storeDataTypeUri() {
518        // remove current assignment
519        getRelatedTopic("dm4.core.aggregation", "dm4.core.type", "dm4.core.default", "dm4.core.data_type")
520            .getRelatingAssociation().delete();
521        // create new assignment
522        pl.typeStorage.storeDataType(uri, dataTypeUri);
523    }
524
525    private void indexAllInstances(IndexMode indexMode) {
526        List<? extends DeepaMehtaObjectModel> objects = getAllInstances();
527        //
528        String str = "\"" + value + "\" (" + uri + ") instances";
529        if (indexModes.size() > 0) {
530            if (objects.size() > 0) {
531                logger.info("### Indexing " + objects.size() + " " + str + " (indexMode=" + indexMode + ")");
532            } else {
533                logger.info("### Indexing " + str + " ABORTED -- no instances in DB");
534            }
535        } else {
536            logger.info("### Indexing " + str + " ABORTED -- no index mode set");
537        }
538        //
539        for (DeepaMehtaObjectModel obj : objects) {
540            ((DeepaMehtaObjectModelImpl) obj).indexSimpleValue(indexMode);
541        }
542    }
543
544
545
546    // === Association Definitions (memory access) ===
547
548    /**
549     * Finds an assoc def by ID and returns its URI (at index 0). Returns the URI of the next-in-sequence
550     * assoc def as well (at index 1), or null if the found assoc def is the last one.
551     */
552    private String[] findAssocDefUris(long assocDefId) {
553        if (assocDefId == -1) {
554            throw new IllegalArgumentException("findAssocDefUris() called with assocDefId=-1");
555        }
556        String[] assocDefUris = new String[2];
557        Iterator<String> i = iterator();
558        while (i.hasNext()) {
559            String assocDefUri = i.next();
560            long _assocDefId = checkAssocDefId(_getAssocDef(assocDefUri));
561            if (_assocDefId == assocDefId) {
562                assocDefUris[0] = assocDefUri;
563                if (i.hasNext()) {
564                    assocDefUris[1] = i.next();
565                }
566                break;
567            }
568        }
569        if (assocDefUris[0] == null) {
570            throw new RuntimeException("Assoc def with ID " + assocDefId + " not found in assoc defs of type \"" +
571                getUri() + "\" (" + assocDefs.keySet() + ")");
572        }
573        return assocDefUris;
574    }
575
576    private boolean hasSameAssocDefSequence(Collection<? extends AssociationDefinitionModel> assocDefs) {
577        Collection<? extends AssociationDefinitionModel> _assocDefs = getAssocDefs();
578        if (assocDefs.size() != _assocDefs.size()) {
579            return false;
580        }
581        //
582        Iterator<? extends AssociationDefinitionModel> i = assocDefs.iterator();
583        for (AssociationDefinitionModel _assocDef : _assocDefs) {
584            AssociationDefinitionModel assocDef = i.next();
585            // Note: if the assoc def's custom association type changedes the assoc def URI changes as well.
586            // So we must identify the assoc defs to compare **by ID**.
587            long assocDefId  = checkAssocDefId(assocDef);
588            long _assocDefId = checkAssocDefId(_assocDef);
589            if (assocDefId != _assocDefId) {
590                return false;
591            }
592        }
593        //
594        return true;
595    }
596
597    // ---
598
599    private void rehashAssocDef(String assocDefUri, String beforeAssocDefUri) {
600        AssociationDefinitionModel assocDef = removeAssocDef(assocDefUri);
601        logger.info("### Rehashing assoc def \"" + assocDefUri + "\" -> \"" + assocDef.getAssocDefUri() +
602            "\" (put " + (beforeAssocDefUri != null ? "before \"" + beforeAssocDefUri + "\"" : "at end") + ")");
603        addAssocDefBefore(assocDef, beforeAssocDefUri);
604    }
605
606    private void rehashAssocDefs(Collection<? extends AssociationDefinitionModel> newAssocDefs) {
607        for (AssociationDefinitionModel assocDef : newAssocDefs) {
608            rehashAssocDef(assocDef.getAssocDefUri(), null);
609        }
610    }
611
612    private void replaceAssocDef(AssociationDefinitionModel assocDef) {
613        replaceAssocDef(assocDef, assocDef.getAssocDefUri(), null);
614    }
615
616    private void replaceAssocDef(AssociationDefinitionModel assocDef, String oldAssocDefUri, String beforeAssocDefUri) {
617        removeAssocDef(oldAssocDefUri);
618        addAssocDefBefore(assocDef, beforeAssocDefUri);
619    }
620
621    // ---
622
623    private AssociationDefinitionModelImpl getAssocDefOrThrow(String assocDefUri) {
624        AssociationDefinitionModelImpl assocDef = _getAssocDef(assocDefUri);
625        if (assocDef == null) {
626            throw new RuntimeException("Association definition \"" + assocDefUri + "\" not found in " +
627                assocDefs.keySet());
628        }
629        return assocDef;
630    }
631
632    private AssociationDefinitionModelImpl _getAssocDef(String assocDefUri) {
633        return assocDefs.get(assocDefUri);
634    }
635
636    // ---
637
638    /**
639     * Returns the ID of the last association definition of this type or
640     * <code>-1</code> if there are no association definitions.
641     */
642    private long lastAssocDefId() {
643        long lastAssocDefId = -1;
644        for (AssociationDefinitionModel assocDef : getAssocDefs()) {
645            lastAssocDefId = assocDef.getId();
646        }
647        return lastAssocDefId;
648    }
649
650    private long firstAssocDefId() {
651        return getAssocDefs().iterator().next().getId();
652    }
653
654    // ---
655
656    private long checkAssocDefId(AssociationDefinitionModel assocDef) {
657        long assocDefId = assocDef.getId();
658        if (assocDefId == -1) {
659            throw new RuntimeException("The assoc def ID is uninitialized (-1): " + assocDef);
660        }
661        return assocDefId;
662    }
663
664    // ---
665
666    private SequencedHashMap<String, AssociationDefinitionModelImpl> toMap(
667                                                           Collection<? extends AssociationDefinitionModel> assocDefs) {
668        SequencedHashMap<String, AssociationDefinitionModelImpl> _assocDefs = new SequencedHashMap();
669        for (AssociationDefinitionModel assocDef : assocDefs) {
670            _assocDefs.put(assocDef.getAssocDefUri(), (AssociationDefinitionModelImpl) assocDef);
671        }
672        return _assocDefs;
673    }
674
675
676
677    // === Label Configuration (memory access) ===
678
679    private void replaceInLabelConfig(String newAssocDefUri, String oldAssocDefUri) {
680        List<String> labelConfig = getLabelConfig();
681        int i = labelConfig.indexOf(oldAssocDefUri);
682        if (i != -1) {
683            logger.info("### Label config: replacing \"" + oldAssocDefUri + "\" -> \"" + newAssocDefUri +
684                "\" (position " + i + ")");
685            labelConfig.set(i, newAssocDefUri);
686        }
687    }
688
689    private void removeFromLabelConfig(String assocDefUri) {
690        List<String> labelConfig = getLabelConfig();
691        int i = labelConfig.indexOf(assocDefUri);
692        if (i != -1) {
693            logger.info("### Label config: removing \"" + assocDefUri + "\" (position " + i + ")");
694            labelConfig.remove(i);
695        }
696    }
697
698
699
700    // === Type Cache (memory access) ===
701
702    /**
703     * Removes this type from type cache and adds a DELETE TYPE directive to the given set of directives.
704     */
705    private void removeFromTypeCache() {
706        pl.typeStorage.removeFromTypeCache(uri);
707        //
708        Directive dir = getDeleteTypeDirective();   // abstract
709        Directives.get().add(dir, new JSONWrapper("uri", uri));
710    }
711
712
713
714    // === Serialization ===
715
716    private JSONArray toJSONArray(List<IndexMode> indexModes) {
717        JSONArray indexModeUris = new JSONArray();
718        for (IndexMode indexMode : indexModes) {
719            indexModeUris.put(indexMode.toUri());
720        }
721        return indexModeUris;
722    }
723
724    private JSONArray toJSONArray(Collection<? extends AssociationDefinitionModel> assocDefs) {
725        JSONArray _assocDefs = new JSONArray();
726        for (AssociationDefinitionModel assocDef : assocDefs) {
727            _assocDefs.put(assocDef.toJSON());
728        }
729        return _assocDefs;
730    }
731
732
733
734    // ------------------------------------------------------------------------------------------------- Private Classes
735
736    private static final class JSONWrapper implements JSONEnabled {
737
738        private JSONObject wrapped;
739
740        private JSONWrapper(String key, Object value) {
741            try {
742                wrapped = new JSONObject();
743                wrapped.put(key, value);
744            } catch (Exception e) {
745                throw new RuntimeException("Constructing a JSONWrapper failed", e);
746            }
747        }
748
749        @Override
750        public JSONObject toJSON() {
751            return wrapped;
752        }
753    }
754}