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