source: trunk/src/anaToMia/GDL_TmEngine/war/gdl_tmengine/lib/tm.js

Last change on this file was 497, checked in by lgiessmann, 13 years ago

gdl-frontend: TmEngine?: fixed a problem with getId of tm.js; fixed a bug when creating typed names; fixed a bug when creating scope

File size: 139.6 KB
Line 
1// tmjs, version 0.4.0
2// http://github.com/jansc/tmjs
3// Copyright (c) 2010 Jan Schreiber <jans@ravn.no>
4// Licensed under the MIT-License.
5//
6// Permission is hereby granted, free of charge, to any person
7// obtaining a copy of this software and associated documentation
8// files (the "Software"), to deal in the Software without
9// restriction, including without limitation the rights to use,
10// copy, modify, merge, publish, distribute, sublicense, and/or sell
11// copies of the Software, and to permit persons to whom the
12// Software is furnished to do so, subject to the following
13// conditions:
14//
15// The above copyright notice and this permission notice shall be
16// included in all copies or substantial portions of the Software.
17//
18// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25// OTHER DEALINGS IN THE SOFTWARE.
26/*jslint browser: true, devel: true, onevar: true, undef: true,
27  nomen: false, eqeqeq: true, plusplus: true, bitwise: true,
28  regexp: true, newcap: true, immed: true, indent: 4 */
29/*global exports*/ 
30
31var TM, TopicMapSystemFactory;
32
33/**
34 * @namespace Global namespace that holds all Topic Maps related objects.
35 * @author Jan Schreiber <jans@ravn.no>
36 * @copyright 2010 Jan Schreiber <http://purl.org/net/jans>
37 * Date: Wed Dec 1 08:39:28 2010 +0100
38 */
39TM = (function () {
40    var Version, Hash, XSD, TMDM, Locator, EventType, Topic, Association,
41        Scoped, Construct, Typed, Reifiable,
42        DatatypeAware, TopicMap, Role, Name,
43        Variant, Occurrence, TopicMapSystemMemImpl,
44        Index, TypeInstanceIndex, ScopedIndex, 
45        SameTopicMapHelper, ArrayHelper, IndexHelper, addScope,
46        DuplicateRemover,
47        SignatureGenerator, MergeHelper, CopyHelper,
48        TypeInstanceHelper;
49
50    Version = '0.4.0';
51
52    // -----------------------------------------------------------------------
53    // Our swiss army knife for mixin of functions.
54    // See http://javascript.crockford.com/inheritance.html
55    Function.prototype.swiss = function (parnt) {
56        var i, name;
57        for (i = 1; i < arguments.length; i += 1) {
58            name = arguments[i];
59            this.prototype[name] = parnt.prototype[name];
60        }
61        return this;
62    };
63
64    // -----------------------------------------------------------------------
65    // Simple hash table for lookup tables
66    Hash = function () {
67        this.hash = {};
68        this.length = 0;
69    };
70
71    /**
72     * @class Simple hash implementation.
73     */
74    Hash.prototype = {
75        /**
76         * Returns the object belonging to the key key or undefined if the
77         * key does not exist.
78         * @param key {String} The hash key.
79         * @returns {object} The stored object or undefined.
80         */       
81        get: function (key) {
82            return this.hash[key];
83        },
84
85        /**
86         * Checks if the key exists in the hash table.
87         * @param key {String} The hash key.
88         * @returns {boolean} True if key exists in the hash table. False
89         *          otherwise.
90         */
91        contains: function (key) {
92            return this.get(key) !== undefined;
93        },
94
95        /**
96         * Stores an object in the hash table.
97         * @param key {String} The hash key.
98         * @param val {object} The value to be stored in the hash table.
99         * @returns val {object} A reference to the stored object.
100         */
101        put: function (key, val) {
102            if (!this.hash[key]) {
103                this.length += 1;
104            }
105            this.hash[key] = val;
106            return val;
107        },
108
109        /**
110         * Removes the key and the corresponding value from the hash table.
111         * @param key {String} Removes value corresponding to key and the key
112         *             from the hash table
113         * @returns {Hash} The hash table itself.
114         */
115        remove: function (key) {
116            delete this.hash[key];
117            this.length -= 1;
118            return this;
119        },
120
121        /**
122         * Returns an array with keys of the hash table.
123         * @returns {Array} An array with strings.
124         */
125        keys: function () {
126            var ret = [], key;
127            for (key in this.hash) {
128                if (this.hash.hasOwnProperty(key)) {
129                    ret.push(key);
130                }
131            }
132            return ret;
133        },
134
135        /**
136         * Returns an array with all values of the hash table.
137         * @returns {Array} An array with all objects stored as a value in
138         *          the hash table.
139         */
140        values: function () {
141            var ret = [], key;
142            for (key in this.hash) {
143                if (this.hash.hasOwnProperty(key)) {
144                    ret.push(this.hash[key]);
145                }
146            }
147            return ret;
148        },
149
150        /**
151         * Empties the hash table by removing the reference to all objects.
152         * Note that the store objects themselves are not touched.
153         * @returns undefined
154         */
155        empty: function () {
156            this.hash = {};
157            this.length = 0;
158        },
159
160        /**
161         * Returns the size of the hash table, that is the count of all
162         * key/value pairs.
163         * @returns {Number} The count of all key/value pairs stored in the
164         *          hash table.
165         */
166        size: function () {
167            return this.length;
168        }
169    };
170
171    // -----------------------------------------------------------------------
172    // Internal event handling system
173    EventType = {};
174    EventType.ADD_ASSOCIATION = 1;
175    EventType.ADD_NAME = 2;
176    EventType.ADD_OCCURRENCE = 3;
177    EventType.ADD_ROLE = 4;
178    EventType.ADD_THEME = 5;
179    EventType.ADD_TOPIC = 6;
180    EventType.ADD_TYPE = 7;
181    EventType.REMOVE_ASSOCIATION = 8;
182    EventType.REMOVE_NAME = 9;
183    EventType.REMOVE_OCCURRENCE = 10;
184    EventType.REMOVE_ROLE = 11;
185    EventType.REMOVE_THEME = 12;
186    EventType.REMOVE_TOPIC = 13;
187    EventType.REMOVE_TYPE = 14;
188    EventType.SET_TYPE = 15;
189
190    /**
191     * @namespace Namespace for XML Schema URIs. // FIXME!!
192     */
193    XSD = {
194        'string': "http://www.w3.org/2001/XMLSchema#string",
195        'integer': "http://www.w3.org/2001/XMLSchema#integer",
196        'anyURI': "http://www.w3.org/2001/XMLSchema#anyURI"
197
198        // TODO: Add all build-in types
199    };
200
201    TMDM = {
202        'TYPE_INSTANCE': 'http://psi.topicmaps.org/iso13250/model/type-instance',
203        'TYPE': 'http://psi.topicmaps.org/iso13250/model/type',
204        'INSTANCE': 'http://psi.topicmaps.org/iso13250/model/instance',
205        'TOPIC_NAME': 'http://psi.topicmaps.org/iso13250/model/topic-name'
206    };
207
208    // -----------------------------------------------------------------------
209    // TODO: The locator functions need some more work. Implement resolve()
210    // and toExternalForm()
211
212    /**
213     * @class Immutable representation of an IRI.
214     */
215    Locator = function (parnt, iri) {
216        this.parnt = parnt;
217        this.iri = iri;
218    };
219
220    /**
221     * Returns the IRI.
222     * @returns {String} A lexical representation of the IRI.
223     */
224    Locator.prototype.getReference = function () {
225        return this.iri;
226    };
227
228    /**
229     * Returns true if the other object is equal to this one.
230     * @param other The object to compare this object against.
231     * @returns <code>(other instanceof Locator &&
232     *     this.getReference().equals(((Locator)other).getReference()))</code>
233     */
234    Locator.prototype.equals = function (other) {
235        return (this.iri === other.getReference());
236    };
237
238    /**
239     * Returns the external form of the IRI. Any special character will be
240     * escaped using the escaping conventions of RFC 3987.
241     * @returns {String} A string representation of this locator suitable for
242     * output or passing to APIs which will parse the locator anew.
243     */
244    Locator.prototype.toExternalForm = function () {
245        throw {name: 'NotImplemented', message: 'Locator.toExternalForm() not implemented'};
246    };
247
248
249    // -----------------------------------------------------------------------
250    /**
251     * @class Represents a Topic Maps construct.
252     */
253    Construct = function () {};
254
255    /**
256     * Adds an item identifier.
257     * @param {Locator} itemIdentifier The item identifier to add.
258     * @returns {Construct} The construct itself (for chaining support)
259     * @throws {ModelConstraintException} If the itemidentifier is null.
260     * @throws {IdentityConstraintException} If another Topic Maps construct with
261     *         the same item identifier exists.
262     */
263    Construct.prototype.addItemIdentifier = function (itemIdentifier) {
264        var existing;
265        if (itemIdentifier === null) {
266            throw {name: 'ModelConstraintException',
267            message: 'addItemIdentifier(null) is illegal'};
268        }
269        existing = this.getTopicMap()._ii2construct.get(itemIdentifier.getReference());
270        if (existing) {
271            throw {name: 'IdentityConstraintException',
272                message: 'Topic Maps constructs with the same item identifier ' +
273                        'are not allowed',
274                reporter: this,
275                existing: existing,
276                locator: itemIdentifier};
277        }
278        this.itemIdentifiers.push(itemIdentifier);
279        this.getTopicMap()._ii2construct.put(itemIdentifier.getReference(), this);
280        return this;
281    };
282
283    /**
284     * Returns true if the other object is equal to this one. Equality must be
285     * the result of comparing the identity (<code>this == other</code>) of the
286     * two objects.
287     * Note: This equality test does not reflect any equality rule according to
288     * the Topic Maps - Data Model (TMDM) by intention.
289     * @param {String} other The object to compare this object against.
290     */
291    Construct.prototype.equals = function (other) {
292        return (this.id === other.id);
293    };
294   
295    /**
296     * Returns the identifier of this construct. This property has no
297     * representation in the Topic Maps - Data Model.
298     *
299     * The ID can be anything, so long as no other Construct in the same topic
300     * map has the same ID.
301     * @returns {String} An identifier which identifies this construct uniquely
302     * within a topic map.
303     */
304    Construct.prototype.getId = function () {
305        return this.id;
306    };
307   
308    /**
309     * Returns the item identifiers of this Topic Maps construct. The return
310     * value may be empty but must never be <code>null</code>.
311     * @returns {Array} An array of Locators representing the item identifiers.
312     * The array MUST NOT be modified.
313     */
314    Construct.prototype.getItemIdentifiers = function () {
315        return this.itemIdentifiers;
316    };
317   
318    /**
319     * Returns the parent of this construct. This method returns
320     * <code>null</code> iff this construct is a TopicMap instance.
321     * @returns {Construct} The parent of this construct or <code>null</code>
322     * iff the construct is an instance of TopicMap.
323     */
324    Construct.prototype.getParent = function () {
325        return this.parnt;
326    };
327   
328    /**
329     * Returns the TopicMap instance to which this Topic Maps construct belongs.
330     * A TopicMap instance returns itself.
331     * @returns {Construct} The topic map instance to which this construct belongs.
332     */
333    Construct.prototype.getTopicMap = function () {
334        throw {name: 'NotImplemented', message: 'getTopicMap() not implemented'};
335    };
336   
337    /**
338     * Returns the hash code value.
339     * TODO: Is this needed?
340     */
341    Construct.prototype.hashCode = function () {
342        throw {name: 'NotImplemented', message: 'hashCode() not implemented'};
343    };
344   
345    /**
346     * Returns the parent of this construct. This method returns
347     * <code>null</code> iff this construct is a TopicMap instance.
348     * @returns {Construct} The parent of this construct or <code>null</code>
349     * iff the construct is an instance of {@link TopicMap}.
350     */
351    Construct.prototype.remove = function () {
352        throw {name: 'NotImplemented', message: 'remove() not implemented'};
353    };
354   
355    /**
356     * Removes an item identifier.
357     * @param {Locator} itemIdentifier The item identifier to be removed from
358     * this construct, if present (<code>null</code> is ignored).
359     * @returns {Construct} The construct itself (for chaining support)
360     */
361    Construct.prototype.removeItemIdentifier = function (itemIdentifier) {
362        if (itemIdentifier === null) {
363            return;
364        }
365        for (var i = 0; i < this.itemIdentifiers.length; i += 1) {
366            if (this.itemIdentifiers[i].getReference() ===
367                    itemIdentifier.getReference()) {
368                this.itemIdentifiers.splice(i, 1);
369                break;
370            }
371        }
372        this.getTopicMap()._ii2construct.remove(itemIdentifier.getReference());
373        return this;
374    };
375   
376    /**
377     * Returns true if the construct is a {@link TopicMap}-object
378     * @returns <code>true</code> if the construct is a {@link TopicMap}-object,
379     *     <code>false</code> otherwise.
380     */
381    Construct.prototype.isTopicMap = function () {
382        return false;
383    };
384   
385    /**
386     * Returns true if the construct is a {@link Topic}-object
387     * @returns <code>true</code> if the construct is a {@link Topic}-object,
388     *     <code>false</code> otherwise.
389     */
390    Construct.prototype.isTopic = function () {
391        return false;
392    };
393   
394    /**
395     * Returns true if the construct is an {@link Association}-object
396     * @returns <code>true</code> if the construct is an {@link Association}-
397     *     object, <code>false</code> otherwise.
398     */
399    Construct.prototype.isAssociation = function () {
400        return false;
401    };
402   
403    /**
404     * Returns true if the construct is a {@link Role}-object
405     * @returns <code>true</code> if the construct is a {@link Role}-object,
406     *     <code>false</code> otherwise.
407     */
408    Construct.prototype.isRole = function () {
409        return false;
410    };
411   
412    /**
413     * Returns true if the construct is a {@link Name}-object
414     * @returns <code>true</code> if the construct is a {@link Name}-object,
415     *     <code>false</code> otherwise.
416     */
417    Construct.prototype.isName = function () {
418        return false;
419    };
420   
421    /**
422     * Returns true if the construct is an {@link Occurrenct}-object
423     * @returns <code>true</code> if the construct is an {@link Occurrence}-object,
424     *     <code>false</code> otherwise.
425     */
426    Construct.prototype.isOccurrence = function () {
427        return false;
428    };
429   
430    /**
431     * Returns true if the construct is a {@link Variant}-object
432     * @returns <code>true</code> if the construct is a {@link Variant}-object,
433     *     <code>false</code> otherwise.
434     */
435    Construct.prototype.isVariant = function () {
436        return false;
437    };
438
439    // --------------------------------------------------------------------------
440    Typed = function () {};
441   
442    // Returns the type of this construct.
443    Typed.prototype.getType = function () {
444        return this.type;
445    };
446   
447    /**
448     * Sets the type of this construct.
449     * @throws {ModelConstraintException} If type is null.
450     * @returns {Typed} The type itself (for chaining support)
451     */
452    Typed.prototype.setType = function (type) {
453        if (type === null) {
454            throw {name: 'ModelConstraintException',
455            message: 'Topic.setType cannot be called without type'};
456        }
457        SameTopicMapHelper.assertBelongsTo(this.getTopicMap(), type);
458        this.getTopicMap().setTypeEvent.fire(this, {old: this.type, type: type});
459        this.type = type;
460        return this;
461    };
462   
463    // --------------------------------------------------------------------------
464    /**
465     * @class Indicates that a statement (Topic Maps construct) has a scope.
466     * Associations, Occurrences, Names, and Variants are scoped.
467     */
468    Scoped = function () {};
469   
470    /**
471     * Adds a topic to the scope.
472     * @throws {ModelConstraintException} If theme is null.
473     * @returns {Typed} The type itself (for chaining support)
474     */
475    Scoped.prototype.addTheme = function (theme) {
476        if (theme === null) {
477            throw {name: 'ModelConstraintException',
478            message: 'addTheme(null) is illegal'}; 
479        }
480        // Check if theme is part of the scope
481        for (var i = 0; i < this.scope.length; i += 1) {
482            if (this.scope[i] === theme) {
483                return false;
484            }
485        }
486        SameTopicMapHelper.assertBelongsTo(this.getTopicMap(), theme);
487        this.scope.push(theme);
488        this.getTopicMap().addThemeEvent.fire(this, {theme: theme});
489        // Special case for names: add the theme to all variants
490        if (this.isName()) {
491            for (i = 0; i < this.variants.length; i += 1) {
492                this.getTopicMap().addThemeEvent.fire(this.variants[i], {theme: theme});
493            }
494        }
495        return this;
496    };
497   
498    /**
499     * Returns the topics which define the scope.
500     * @returns {Array} A possible empty Array with Topic objects.
501     */
502    Scoped.prototype.getScope = function () {
503        if (this.isVariant()) {
504            var i, tmp = new Hash(), parent_scope = this.parnt.getScope();
505            for (i = 0; i < parent_scope.length; i += 1) {
506                tmp.put(parent_scope[i].getId(), parent_scope[i]);
507            }
508            for (i = 0; i < this.scope.length; i += 1) {
509                tmp.put(this.scope[i].getId(), this.scope[i]);
510            }
511            return tmp.values();
512        }
513        return this.scope;
514    };
515   
516    /**
517     * Removes a topic from the scope.
518     * @returns {Scoped} The scoped object itself (for chaining support)
519     */
520    Scoped.prototype.removeTheme = function (theme) {
521        var i, j, scope, found;
522        for (i = 0; i < this.scope.length; i += 1) {
523            if (this.scope[i] === theme) {
524                this.getTopicMap().removeThemeEvent.fire(this, {theme: this.scope[i]});
525                this.scope.splice(i, 1);
526                break;
527            }
528        }
529        // Special case for names: remove the theme from index for all variants
530        if (this.isName()) {
531            for (i = 0; i < this.variants.length; i += 1) {
532                scope = this.variants[i].scope;
533                // Check if the the variant has theme as scope
534                found = false;
535                for (j = 0; j < scope.length; j += 1) {
536                    if (theme.equals(scope[j])) {
537                        found = true;
538                    }
539                }
540                if (!found) {
541                    this.getTopicMap().removeThemeEvent.fire(this.variants[i], {theme: theme});
542                }
543            }
544        }
545        return this;
546    };
547   
548   
549    // --------------------------------------------------------------------------
550    /**
551     * @class Indicates that a Construct is reifiable. Every Topic Maps
552     * construct that is not a Topic is reifiable.
553     */
554    Reifiable = function () {};
555   
556    /**
557     * Returns the reifier of this construct.
558     */
559    Reifiable.prototype.getReifier = function () {
560        return this.reifier;
561    };
562   
563    /**
564     * Sets the reifier of the construct.
565     * @throws {ModelConstraintException} If reifier already reifies another
566     * construct.
567     * @returns {Reifiable} The reified object itself (for chaining support)
568     */
569    Reifiable.prototype.setReifier = function (reifier) {
570        if (reifier && reifier.getReified() !== null) {
571            throw {name: 'ModelConstraintException',
572                message: 'Reifies already another construct'};
573        }
574        SameTopicMapHelper.assertBelongsTo(this.getTopicMap(), reifier);
575        if (this.reifier) {
576            this.reifier._setReified(null);
577        }
578        if (reifier) {
579            reifier._setReified(this);
580        }
581        this.reifier = reifier;
582        return this;
583    };
584   
585    // --------------------------------------------------------------------------
586    /**
587     * @class Common base interface for Occurrences and Variants.
588     * Inherits Scoped, Reifiable
589     */
590    DatatypeAware = function () {};
591   
592    /**
593     * Returns the BigDecimal representation of the value.
594     */
595    DatatypeAware.prototype.decimalValue = function () {
596        // FIXME Implement!
597    };
598   
599    /**
600     * Returns the float representation of the value.
601     * @throws {NumberFormatException} If the value is not convertable to float.
602     */
603    DatatypeAware.prototype.floatValue = function () {
604        var ret = parseFloat(this.value);
605        if (isNaN(ret)) {
606            throw {name: 'NumberFormatException',
607                message: '"' + this.value + '" is not a float'};
608        }
609        return ret;
610    };
611   
612    /**
613     * Returns the Locator identifying the datatype of the value.
614     */
615    DatatypeAware.prototype.getDatatype = function () {
616        return this.datatype;
617    };
618   
619    /**
620     * Returns the lexical representation of the value.
621     */
622    DatatypeAware.prototype.getValue = function () {
623        if (typeof this.value === 'object' && this.value instanceof Locator) {
624            return this.value.getReference();
625        }
626        return this.value.toString();
627    };
628   
629    /**
630     * Returns the BigInteger representation of the value.
631     * @throws {NumberFormatException} If the value cannot be parsed as an int.
632     */
633    DatatypeAware.prototype.integerValue = function () {
634        var ret = parseInt(this.value, 10);
635        if (isNaN(ret)) {
636            throw {name: 'NumberFormatException',
637                message: '"' + this.value + '" is not an integer'};
638        }
639        return ret;
640    };
641   
642    /**
643     * Returns the Locator representation of the value.
644     * @throws {ModelConstraintException} If the value is not an Locator
645     * object.
646     */
647    DatatypeAware.prototype.locatorValue = function () {
648        if (!(typeof this.value === 'object' && this.value instanceof Locator)) {
649            throw {name: 'ModelConstraintException',
650                message: '"' + this.value + '" is not a locator'};
651        }
652        return this.value;
653    };
654   
655    /**
656     * Returns the long representation of the value.
657     */
658    DatatypeAware.prototype.longValue = function () {
659        // FIXME Implement!
660    };
661   
662    /**
663     * Sets the value and the datatype.
664     * @throws {ModelConstraintException} If datatype or value is null.
665     */
666    DatatypeAware.prototype.setValue = function (value, datatype) {
667        var tm = this.getTopicMap();
668        if (datatype === null) {
669            throw {name: 'ModelConstraintException', message: 'Invalid datatype'};
670        }
671        if (value === null) {
672            throw {name: 'ModelConstraintException', message: 'Invalid value'};
673        }
674        this.value = value;
675        this.datatype = datatype ||
676            this.getTopicMap().createLocator(XSD.string);
677        if (datatype && datatype.getReference() === XSD.anyURI) {
678            this.value = tm.createLocator(value);
679        }
680        if (!datatype) {
681            if (typeof value === 'number') {
682                // FIXME Could be XSD.float as well
683                this.datatype = tm.createLocator(XSD.integer);
684            }
685        }
686        if (typeof value === 'object' && value instanceof Locator) {
687            this.datatype = tm.createLocator(XSD.anyURI);
688        }
689    };
690   
691    // --------------------------------------------------------------------------
692    /**
693     * Constructs a new Topic Map System Factoy. The constructor should not be
694     * called directly. Use the {TM.TopicMapSystemFactory.newInstance} instead.
695     * @class Represents a Topic Maps construct.
696     * @memberOf TM
697     */
698    TopicMapSystemFactory = function () {
699        this.properties = {};
700        this.features = {};
701    };
702   
703    /**
704     * Returns the particular feature requested for in the underlying implementation
705     * of TopicMapSystem.
706     */
707    TopicMapSystemFactory.prototype.getFeature = function (featureName) {
708        return this.features;
709    };
710   
711    /**
712     * Gets the value of a property in the underlying implementation of
713     * TopicMapSystem.
714     */
715    TopicMapSystemFactory.prototype.getProperty = function (propertyName) {
716        return this.properties[propertyName];
717    };
718   
719    /**
720     * Returns if the particular feature is supported by the TopicMapSystem.
721     */
722    TopicMapSystemFactory.prototype.hasFeature = function (featureName) {
723        return false;
724    };
725   
726    /**
727     * Obtain a new instance of a TopicMapSystemFactory.
728     * @static
729     * @returns {TopicMapSystemFactory}
730     */
731    TopicMapSystemFactory.newInstance = function () {
732        return new TopicMapSystemFactory();
733    };
734   
735    /**
736     * Creates a new TopicMapSystem instance using the currently configured
737     * factory parameters.
738     */
739    TopicMapSystemFactory.prototype.newTopicMapSystem = function () {
740        var backend = this.properties['com.semanticheadache.tmjs.backend'] || 'memory'; 
741        if (backend === 'memory') {
742            return new TopicMapSystemMemImpl();
743        }
744    };
745   
746    /**
747     * Sets a particular feature in the underlying implementation of TopicMapSystem.
748     */
749    TopicMapSystemFactory.prototype.setFeature = function (featureName, enable) {
750        this.features[featureName] = enable;
751    };
752   
753    /**
754     * Sets a property in the underlying implementation of TopicMapSystem.
755     */
756    TopicMapSystemFactory.prototype.setProperty = function (propertyName, value) {
757        this.properties[propertyName] = value;
758    };
759   
760    /**
761     * Creates a new instance of TopicMamSystem.
762     * @class Implementation of the TopicMapSystem interface.
763     */
764    TopicMapSystemMemImpl = function () {
765        this.topicmaps = {};
766    };
767   
768    /**
769     * @throws {TopicMapExistsException} If a topic map with the given locator
770     * already exists.
771     */
772    TopicMapSystemMemImpl.prototype.createTopicMap = function (locator) {
773        if (this.topicmaps[locator.getReference()]) {
774            throw {name: 'TopicMapExistsException',
775                message: 'A topic map under the same IRI already exists'};
776        }
777        var tm = new TopicMap(this, locator);
778        this.topicmaps[locator.getReference()] = tm;
779        return tm;
780    };
781   
782    TopicMapSystemMemImpl.prototype.getLocators = function () {
783        var locators = [], key;
784        for (key in this.topicmaps) {
785            if (this.topicmaps.hasOwnProperty(key)) {
786                locators.push(this.createLocator(key));
787            }
788        }
789        return locators;
790    };
791   
792    TopicMapSystemMemImpl.prototype.getTopicMap = function (locator) {
793        var tm;
794        if (locator instanceof Locator) {
795            tm = this.topicmaps[locator.getReference()];
796        } else {
797            tm = this.topicmaps[locator];
798        }
799        if (!tm) {
800            return null;
801        }
802        return tm;
803    };
804   
805    /**
806     * @param {String} iri
807     */
808    TopicMapSystemMemImpl.prototype.createLocator = function (iri) {
809        return new Locator(this, iri);
810    };
811   
812    TopicMapSystemMemImpl.prototype.getFeature = function (featureName) {
813        return false;
814    };
815   
816    TopicMapSystemMemImpl.prototype._removeTopicMap = function (tm) {
817        var key;
818        for (key in this.topicmaps) {
819            if (this.topicmaps.hasOwnProperty(key) &&
820                key === tm.locator.getReference()) {
821                delete this.topicmaps[key];
822            }
823        }
824    };
825   
826    TopicMapSystemMemImpl.prototype.close = function () {
827        this.topicmaps = null; // release references
828    };
829   
830    TopicMap = function (tms, locator) {
831        this.topicmapsystem = tms;
832        this.itemIdentifiers = [];
833        this.locator = locator;
834        this.topics = [];
835        this.associations = [];
836        this._constructId = 1;
837        this._si2topic = new Hash(); // Index for subject identifiers
838        this._sl2topic = new Hash(); // Index for subject locators
839        this._ii2construct = new Hash(); // Index for item identifiers
840        this._id2construct = new Hash(); // Index for object ids
841
842        // The topic map object always get the id 0
843        this.id = 0;
844        this._id2construct.put(this.id, this);
845
846        this.reifier = null;
847        this.handlers = [];
848
849        // Our own event handling mechanism
850        var EventHandler = function (eventtype) {
851            this.eventtype = eventtype;
852            this.handlers = [];
853        };
854        EventHandler.prototype = {
855            registerHandler: function (handler) {
856                this.handlers.push(handler);
857            },
858            removeHandler: function (handler) {
859                for (var i = 0; i < this.handlers.length; i += 1) {
860                    if (handler.toString() ===
861                        this.handlers[i].toString()) {
862                        this.handlers.splice(i, 1);
863                    }
864                }
865            },
866            fire: function (source, obj) {
867                obj = obj || {};
868                for (var i = 0; i < this.handlers.length; i += 1) {
869                        this.handlers[i](this.eventtype, source, obj);
870                }
871            }
872        };
873        this.addAssociationEvent = new EventHandler(EventType.ADD_ASSOCIATION); 
874        this.addNameEvent = new EventHandler(EventType.ADD_NAME); 
875        this.addOccurrenceEvent = new EventHandler(EventType.ADD_OCCURRENCE); 
876        this.addRoleEvent = new EventHandler(EventType.ADD_ROLE); 
877        this.addThemeEvent = new EventHandler(EventType.ADD_THEME); 
878        this.addTopicEvent = new EventHandler(EventType.ADD_TOPIC); 
879        this.addTypeEvent = new EventHandler(EventType.ADD_TYPE); 
880        this.removeAssociationEvent = new EventHandler(EventType.REMOVE_ASSOCIATION);
881        this.removeNameEvent = new EventHandler(EventType.REMOVE_NAME);
882        this.removeOccurrenceEvent = new EventHandler(EventType.REMOVE_OCCURRENCE);
883        this.removeRoleEvent = new EventHandler(EventType.REMOVE_ROLE);
884        this.removeThemeEvent = new EventHandler(EventType.REMOVE_THEME);
885        this.removeTopicEvent = new EventHandler(EventType.REMOVE_TOPIC);
886        this.removeTypeEvent = new EventHandler(EventType.REMOVE_TYPE); 
887        this.setTypeEvent = new EventHandler(EventType.SET_TYPE);
888        this.typeInstanceIndex = new TypeInstanceIndex(this);
889        this.scopedIndex = new ScopedIndex(this);
890    };
891
892    /**
893     * @returns {TopicMap} The topic map object itself (for chaining support)
894     */
895    TopicMap.prototype.register_event_handler = function (type, handler) {
896        switch (type) {
897        case EventType.ADD_ASSOCIATION:
898            this.addAssociationEvent.registerHandler(handler);
899            break;
900        case EventType.ADD_NAME:
901            this.addNameEvent.registerHandler(handler);
902            break;
903        case EventType.ADD_OCCURRENCE:
904            this.addOccurrenceEvent.registerHandler(handler);
905            break;
906        case EventType.ADD_ROLE:
907            this.addRoleEvent.registerHandler(handler);
908            break;
909        case EventType.ADD_THEME:
910            this.addThemeEvent.registerHandler(handler);
911            break;
912        case EventType.ADD_TOPIC:
913            this.addTopicEvent.registerHandler(handler);
914            break;
915        case EventType.ADD_TYPE:
916            this.addTypeEvent.registerHandler(handler);
917            break;
918        case EventType.REMOVE_ASSOCIATION:
919            this.removeAssociationEvent.registerHandler(handler);
920            break;
921        case EventType.REMOVE_NAME:
922            this.removeNameEvent.registerHandler(handler);
923            break;
924        case EventType.REMOVE_OCCURRENCE:
925            this.removeOccurrenceEvent.registerHandler(handler);
926            break;
927        case EventType.REMOVE_ROLE:
928            this.removeRoleEvent.registerHandler(handler);
929            break;
930        case EventType.REMOVE_THEME:
931            this.removeThemeEvent.registerHandler(handler);
932            break;
933        case EventType.REMOVE_TOPIC:
934            this.removeTopicEvent.registerHandler(handler);
935            break;
936        case EventType.REMOVE_TYPE:
937            this.removeTypeEvent.registerHandler(handler);
938            break;
939        case EventType.SET_TYPE:
940            this.setTypeEvent.registerHandler(handler);
941            break;
942        }
943        return this;
944    };
945
946    TopicMap.swiss(Reifiable, 'getReifier', 'setReifier');
947    TopicMap.swiss(Construct, 'addItemIdentifier', 'getItemIdentifiers',
948        'removeItemIdentifier', 'isTopic', 'isAssociation', 'isRole',
949        'isOccurrence', 'isName', 'isVariant', 'isTopicMap');
950    /**
951     * Removes duplicate topic map objects. This function is quite expensive,
952     * so it should not be called too often. It is meant to remove duplicates
953     * after imports of topic maps.
954     * @returns {TopicMap} The topic map object itself (for chaining support)
955     */
956    TopicMap.prototype.sanitize = function () {
957        DuplicateRemover.removeTopicMapDuplicates(this);
958        TypeInstanceHelper.convertAssociationsToType(this);
959        return this;
960    };
961   
962    TopicMap.prototype.isTopicMap = function () {
963        return true;
964    };
965   
966    TopicMap.prototype._getConstructId = function () {
967        this._constructId = this._constructId + 1;
968        return this._constructId;
969    };
970   
971    TopicMap.prototype.remove = function () {
972        if (this.topicmapsystem === null) {
973            return null;
974        }
975        this.topicmapsystem._removeTopicMap(this);
976        this.topicmapsystem = null;
977        this.itemIdentifiers = null;
978        this.locator = null;
979        this.topics = null;
980        this.associations = null;
981        this._si2topic = null;
982        this._sl2topic = null;
983        this._ii2construct = null;
984        this._id2construct = null;
985        this.reifier = null;
986        this.id = null;
987        this.typeInstanceIndex = null;
988        return null;
989    };
990   
991    /**
992     * @throws {ModelConstraintException} If type or scope is null.
993     */
994    TopicMap.prototype.createAssociation = function (type, scope) {
995        var a;
996        if (type === null) {
997            throw {name: 'ModelConstraintException',
998                message: 'Creating an association with type == null is not allowed'};
999        } 
1000        if (scope === null) {
1001            throw {name: 'ModelConstraintException',
1002                message: 'Creating an association with scope == null is not allowed'};
1003        }
1004        SameTopicMapHelper.assertBelongsTo(this, type);
1005        SameTopicMapHelper.assertBelongsTo(this, scope);
1006   
1007        a = new Association(this);
1008        this.associations.push(a);
1009        if (type) {
1010            a.setType(type);
1011        }
1012        addScope(a, scope);
1013        this.addAssociationEvent.fire(a);
1014        return a;
1015    };
1016   
1017    TopicMap.prototype.createLocator = function (iri) {
1018        return new Locator(this, iri);
1019    };
1020   
1021    TopicMap.prototype._createEmptyTopic = function () {
1022        var t = new Topic(this);
1023        this.addTopicEvent.fire(t);
1024        this.topics.push(t);
1025        return t;
1026    };
1027   
1028    TopicMap.prototype.createTopic = function () {
1029        var t = this._createEmptyTopic();
1030        t.addItemIdentifier(this.createLocator('urn:x-tmjs:' + t.getId()));
1031        return t;
1032    };
1033   
1034    /**
1035     * @throws {ModelConstraintException} If no itemIdentifier is given.
1036     * @throws {IdentityConstraintException} If another construct with the
1037     * specified item identifier exists which is not a Topic.
1038     */
1039    TopicMap.prototype.createTopicByItemIdentifier = function (itemIdentifier) {
1040        if (!itemIdentifier) {
1041            throw {name: 'ModelConstraintException',
1042            message: 'createTopicByItemIdentifier() needs an item identifier'};
1043        }
1044        var t = this.getConstructByItemIdentifier(itemIdentifier);
1045        if (t) {
1046            if (!t.isTopic()) {
1047                throw {name: 'IdentityConstraintException',
1048                message: 'Another construct with the specified item identifier ' +
1049                    'exists which is not a Topic.'};
1050            }
1051            return t;
1052        }
1053        t = this._createEmptyTopic();
1054        t.addItemIdentifier(itemIdentifier);
1055        return t;
1056    };
1057   
1058    /**
1059     * @throws {ModelConstraintException} If no subjectIdentifier is given.
1060     */
1061    TopicMap.prototype.createTopicBySubjectIdentifier = function (subjectIdentifier) {
1062        if (!subjectIdentifier) {
1063            throw {name: 'ModelConstraintException',
1064            message: 'createTopicBySubjectIdentifier() needs a subject identifier'};
1065        }
1066        var t = this.getTopicBySubjectIdentifier(subjectIdentifier);
1067        if (t) {
1068            return t;
1069        }
1070        t = this._createEmptyTopic();
1071        t.addSubjectIdentifier(subjectIdentifier);
1072        return t;
1073    };
1074   
1075    /**
1076     * @throws {ModelConstraintException} If no subjectLocator is given.
1077     */
1078    TopicMap.prototype.createTopicBySubjectLocator = function (subjectLocator) {
1079        if (!subjectLocator) {
1080            throw {name: 'ModelConstraintException',
1081            message: 'createTopicBySubjectLocator() needs a subject locator'};
1082        }
1083        var t = this.getTopicBySubjectLocator(subjectLocator);
1084        if (t) {
1085            return t;
1086        }
1087        t = this._createEmptyTopic();
1088        t.addSubjectLocator(subjectLocator);
1089        return t;
1090    };
1091   
1092    TopicMap.prototype.getAssociations = function () {
1093        return this.associations;
1094    };
1095   
1096    /**
1097     * @throws {ModelConstraintException} If id is null.
1098     */
1099    TopicMap.prototype.getConstructById = function (id) {
1100        if (id === null) {
1101            throw {name: 'ModelConstraintException',
1102                message: 'getConstructById(null) is illegal'};
1103        }
1104        var ret = this._id2construct.get(id);
1105        if (!ret) {
1106            return null;
1107        }
1108        return ret;
1109    };
1110   
1111    /**
1112     * @throws {ModelConstraintException} If itemIdentifier is null.
1113     */
1114    TopicMap.prototype.getConstructByItemIdentifier = function (itemIdentifier) {
1115        if (itemIdentifier === null) {
1116            throw {name: 'ModelConstraintException',
1117                message: 'getConstructByItemIdentifier(null) is illegal'};
1118        }
1119        var ret = this._ii2construct.get(itemIdentifier.getReference());
1120        if (!ret) {
1121            return null;
1122        }
1123        return ret;
1124    };
1125   
1126    /**
1127     * @throws {UnsupportedOperationException} If the index type is not
1128     * supported.
1129     */
1130    TopicMap.prototype.getIndex = function (className) {
1131        var index;
1132        if (className === 'TypeInstanceIndex') {
1133            index = this.typeInstanceIndex;
1134            return index;
1135        } else if (className === 'ScopedIndex') {
1136            index = new ScopedIndex(this);
1137            return index;
1138        }
1139        // TODO: Should we throw an exception that indicates that the
1140        // index is not known? Check the TMAPI docs!
1141        throw {name: 'UnsupportedOperationException', 
1142            message: 'getIndex ist not (yet) supported'};
1143    };
1144   
1145    TopicMap.prototype.getParent = function () {
1146        return null;
1147    };
1148   
1149    TopicMap.prototype.getTopicBySubjectIdentifier = function (subjectIdentifier) {
1150        var res = this._si2topic.get(subjectIdentifier.getReference());
1151        if (res) {
1152            return res;
1153        }
1154        return null; // Make sure that the result is not undefined
1155    };
1156   
1157    TopicMap.prototype.getTopicBySubjectLocator = function (subjectLocator) {
1158        var res = this._sl2topic.get(subjectLocator.getReference());
1159        if (res) {
1160            return res;
1161        }
1162        return null; // Make sure that the result is not undefined
1163    };
1164   
1165    TopicMap.prototype.getLocator = function () {
1166        return this.locator;
1167    };
1168   
1169    TopicMap.prototype.getTopics = function () {
1170        return this.topics;
1171    };
1172   
1173    TopicMap.prototype.mergeIn = function (topicmap) {
1174        // TODO implement!
1175        throw {name: 'NotImplemented', message: 'TopicMap.mergeIn() not implemented'};
1176    };
1177   
1178    TopicMap.prototype.equals = function (topicmap) {
1179        return this.locator.equals(topicmap.locator);
1180    };
1181   
1182    TopicMap.prototype.getId = function () {
1183        return this.id;
1184    };
1185   
1186    TopicMap.prototype.getTopicMap = function () {
1187        return this;
1188    };
1189   
1190    // Remove item identifiers
1191    TopicMap.prototype._removeConstruct = function (construct) {
1192        var iis = construct.getItemIdentifiers(), i;
1193        for (i = 0; i < iis.length; i += 1) {
1194            this._ii2construct.remove(iis[i].getReference());
1195        }
1196        this._id2construct.remove(construct.getId());
1197    };
1198   
1199    TopicMap.prototype._removeTopic = function (topic) {
1200        var i, sis = topic.getSubjectIdentifiers(),
1201            slos = topic.getSubjectLocators();
1202        // remove subject identifiers from TopicMap._si2topic
1203        for (i = 0; i < sis.length; i += 1) {
1204            this._si2topic.remove(sis[i].getReference());
1205        }
1206        // remove subject locators from TopicMap._sl2topic
1207        for (i = 0; i < slos.length; i += 1) {
1208            this._sl2topic.remove(slos[i].getReference());
1209        }
1210        this._removeConstruct(topic);
1211        // remove topic from TopicMap.topics
1212        for (i = 0; i < this.topics.length; i += 1) {
1213            if (topic.id === this.topics[i].id) {
1214                this.topics.splice(i, 1);
1215                break;
1216            }
1217        }
1218    };
1219   
1220    TopicMap.prototype._removeAssociation = function (association) {
1221        var i;
1222        // remove association from TopicMap.associations
1223        for (i = 0; i < this.associations.length; i += 1) {
1224            if (association.id === this.associations[i].id) {
1225                this.associations.splice(i, 1);
1226                break;
1227            }
1228        }
1229        this._removeConstruct(association);
1230        // remove association from TopicMap.associations
1231        for (i = 0; i < this.associations.length; i += 1) {
1232            if (association.id === this.associations[i].id) {
1233                this.associations.splice(i, 1);
1234                break;
1235            }
1236        }
1237    };
1238   
1239    TopicMap.prototype._removeRole = function (role) {
1240        this._removeConstruct(role);
1241    };
1242   
1243    TopicMap.prototype._removeOccurrence = function (occ) {
1244        this._removeConstruct(occ);
1245    };
1246   
1247    TopicMap.prototype._removeName = function (name) {
1248        this._removeConstruct(name);
1249    };
1250   
1251    TopicMap.prototype._removeVariant = function (variant) {
1252        this._removeConstruct(variant);
1253    };
1254   
1255    // hashCode, remove
1256   
1257    // --------------------------------------------------------------------------
1258   
1259    Topic = function (parnt) {
1260        this.subjectIdentifiers = [];
1261        this.subjectLocators = [];
1262        this.itemIdentifiers = [];
1263        this.parnt = parnt;
1264        this.id = parnt._getConstructId();
1265        this.getTopicMap()._id2construct.put(this.id, this);
1266        this.types = [];
1267        this.rolesPlayed = [];
1268        this.occurrences = [];
1269        this.names = [];
1270        this.reified = null;
1271    };
1272   
1273    Topic.swiss(Construct, 'addItemIdentifier', 'equals', 'getId',
1274        'getItemIdentifiers', 'getParent', 'getTopicMap', 'hashCode', 'remove',
1275        'removeItemIdentifier', 'isTopic', 'isAssociation', 'isRole',
1276        'isOccurrence', 'isName', 'isVariant', 'isTopicMap');
1277   
1278    Topic.prototype.isTopic = function () {
1279        return true;
1280    };
1281   
1282    Topic.prototype.getTopicMap = function () {
1283        return this.parnt;
1284    };
1285   
1286    /**
1287     * Adds a subject identifier to this topic.
1288     * @throws {ModelConstraintException} If subjectIdentifier is null or
1289     * not defined.
1290     * @returns {Topic} The topic itself (for chaining support)
1291     */
1292    Topic.prototype.addSubjectIdentifier = function (subjectIdentifier) {
1293        if (!subjectIdentifier) {
1294            throw {name: 'ModelConstraintException',
1295            message: 'addSubjectIdentifier() needs subject identifier'};
1296        }
1297        // Ignore if the identifier already exists
1298        for (var i = 0; i < this.subjectIdentifiers.length; i += 1) {
1299            if (this.subjectIdentifiers[i].getReference() ===
1300                subjectIdentifier.getReference()) {
1301                return;
1302            }
1303        }
1304        this.subjectIdentifiers.push(subjectIdentifier);
1305        this.parnt._si2topic.put(subjectIdentifier.getReference(), this);
1306        return this;
1307    };
1308   
1309    /**
1310     * Adds a subject locator to this topic.
1311     * @throws {ModelConstraintException} If subjectLocator is null or
1312     * not defined.
1313     * @returns {Topic} The topic itself (for chaining support)
1314     */
1315    Topic.prototype.addSubjectLocator = function (subjectLocator) {
1316        if (!subjectLocator) {
1317            throw {name: 'ModelConstraintException',
1318            message: 'addSubjectLocator() needs subject locator'};
1319        }
1320        // Ignore if the identifier already exists
1321        for (var i = 0; i < this.subjectLocators.length; i += 1) {
1322            if (this.subjectLocators[i].getReference() ===
1323                subjectLocator.getReference()) {
1324                return;
1325            }
1326        }
1327        this.subjectLocators.push(subjectLocator);
1328        this.parnt._sl2topic.put(subjectLocator.getReference(), this);
1329        return this;
1330    };
1331   
1332    /**
1333     * Adds a type to this topic.
1334     * @throws {ModelConstraintException} If type is null or not defined.
1335     * @returns {Topic} The topic itself (for chaining support)
1336     */
1337    Topic.prototype.addType = function (type) {
1338        if (!type) {
1339            throw {name: 'ModelConstraintException',
1340            message: 'addType() needs type'};
1341        }
1342        SameTopicMapHelper.assertBelongsTo(this.parnt, type);
1343        this.parnt.addTypeEvent.fire(this, {type: type});
1344        this.types.push(type);
1345        return this;
1346    };
1347   
1348    // TODO: @type is optional In TMAPI 2.0
1349    // Creates a Name for this topic with the specified value, and scope.
1350    // Creates a Name for this topic with the specified type, value, and scope.
1351    Topic.prototype.createName = function (value, type, scope) {
1352        var name;
1353        if (type) {
1354                SameTopicMapHelper.assertBelongsTo(this.parnt, type);
1355        }
1356        if (scope) {
1357                SameTopicMapHelper.assertBelongsTo(this.parnt, scope);
1358        }
1359        if (typeof scope === 'undefined') {
1360                scope = null;
1361        }
1362        name = new Name(this, value, type);
1363        addScope(name, scope);
1364        this.names.push(name);
1365        return name;
1366    };
1367   
1368    // TODO: @datatype is optional in TMAPI, value may be string or locator.
1369    // Creates an Occurrence for this topic with the specified type, IRI value, and
1370    // scope.
1371    // createOccurrence(Topic type, java.lang.String value, Locator datatype,
1372    // java.util.Collection<Topic> scope)
1373    // Creates an Occurrence for this topic with the specified type, string value,
1374    // and scope.
1375    Topic.prototype.createOccurrence = function (type, value, datatype, scope) {
1376        var occ;
1377        SameTopicMapHelper.assertBelongsTo(this.parnt, type);
1378        SameTopicMapHelper.assertBelongsTo(this.parnt, scope);
1379   
1380        occ = new Occurrence(this, type, value, datatype);
1381        this.parnt.addOccurrenceEvent.fire(occ, {type: type, value: value});
1382        addScope(occ, scope);
1383        this.occurrences.push(occ);
1384        return occ;
1385    };
1386   
1387    /**
1388     * Returns the Names of this topic where the name type is type.
1389     *type is optional.
1390     */
1391    Topic.prototype.getNames = function (type) {
1392        var ret = [], i;
1393
1394        for (i = 0; i < this.names.length; i += 1) {
1395            if (type && this.names[i].getType().equals(type)) {
1396                ret.push(this.names[i]);
1397            } else if (!type) {
1398                ret.push(this.names[i]);
1399            }
1400        }
1401        return ret;
1402    };
1403   
1404    /**
1405     * Returns the Occurrences of this topic where the occurrence type is type. type
1406     * is optional.
1407     * @throws {IllegalArgumentException} If type is null.
1408     */
1409    Topic.prototype.getOccurrences = function (type) {
1410        var ret = [], i;
1411        if (type === null) {
1412            throw {name: 'IllegalArgumentException',
1413                message: 'Topic.getOccurrences cannot be called without type'};
1414        }
1415        for (i = 0; i < this.occurrences.length; i += 1) {
1416            if (type && this.occurrences[i].getType().equals(type)) {
1417                ret.push(this.occurrences[i]);
1418            } else if (!type) {
1419                ret.push(this.occurrences[i]);
1420            }
1421        }
1422        return ret;
1423    };
1424   
1425    Topic.prototype._removeOccurrence = function (occ) {
1426        // remove this from TopicMap.topics
1427        for (var i = 0; i < this.occurrences.length; i += 1) {
1428            if (this.occurrences[i].equals(occ)) {
1429                this.occurrences.splice(i, 1);
1430                break;
1431            }
1432        }
1433        this.getTopicMap()._removeOccurrence(occ);
1434    };
1435   
1436    // Returns the Construct which is reified by this topic.
1437    Topic.prototype.getReified = function (type) {
1438        return this.reified;
1439    };
1440   
1441    Topic.prototype._setReified = function (reified) {
1442        this.reified = reified;
1443    };
1444   
1445    /**
1446     * Returns the roles played by this topic.
1447     * Returns the roles played by this topic where the role type is type.
1448     * assocType is optional
1449     * @throws {IllegalArgumentException} If type or assocType is null.
1450     */
1451    Topic.prototype.getRolesPlayed = function (type, assocType) {
1452        if (type === null) {
1453            throw {name: 'IllegalArgumentException',
1454                message: 'Topic.getRolesPlayed cannot be called without type'};
1455        }
1456        if (assocType === null) {
1457            throw {name: 'IllegalArgumentException',
1458                message: 'Topic.getRolesPlayed cannot be called with assocType===null'};
1459        }
1460        var ret = [], i;
1461        for (i = 0; i < this.rolesPlayed.length; i += 1) {
1462            if (!type) {
1463                ret.push(this.rolesPlayed[i]);
1464            } else if (this.rolesPlayed[i].getType().equals(type)) {
1465                if (assocType &&
1466                    this.rolesPlayed[i].getParent().getType().equals(assocType) ||
1467                    !assocType) {
1468                    ret.push(this.rolesPlayed[i]);
1469                }
1470            }
1471        }
1472        return ret;
1473    };
1474   
1475    // @private Registers role as a role played
1476    // TODO: Rename to _addRolePlayed
1477    Topic.prototype.addRolePlayed = function (role) {
1478        this.rolesPlayed.push(role);
1479    };
1480   
1481    // TODO: Rename to _removeRolePlayed
1482    Topic.prototype.removeRolePlayed = function (role) {
1483        for (var i = 0; i < this.rolesPlayed.length; i += 1) {
1484            if (this.rolesPlayed[i].id === role.id) {
1485                this.rolesPlayed.splice(i, 1);
1486            }
1487        }
1488    };
1489   
1490    /**
1491     * Returns the subject identifiers assigned to this topic.
1492     */
1493    Topic.prototype.getSubjectIdentifiers = function () {
1494        return this.subjectIdentifiers;
1495    };
1496   
1497    /**
1498     * Returns the subject locators assigned to this topic.
1499     */
1500    Topic.prototype.getSubjectLocators = function () {
1501        return this.subjectLocators;
1502    };
1503   
1504    /**
1505     * Returns the types of which this topic is an instance of.
1506     */
1507    Topic.prototype.getTypes = function () {
1508        return this.types;
1509    };
1510   
1511    /**
1512     * Merges another topic into this topic.
1513     * @throws {ModelConstraintException} If the topics reify different
1514     * information items.
1515     * @returns {Topic} The topic itself (for chaining support)
1516     */
1517    Topic.prototype.mergeIn = function (other) {
1518        var arr, i, tmp, tmp2, signatures, tiidx, sidx;
1519        if (this.equals(other)) {
1520            return true;
1521        }
1522
1523        SameTopicMapHelper.assertBelongsTo(this.getTopicMap(), other);
1524        if (this.getReified() && other.getReified() &&
1525            !this.getReified().equals(other.getReified())) {
1526            throw {name: 'ModelConstraintException',
1527                message: 'The topics reify different Topic Maps constructs and cannot be merged!'
1528            };
1529        }
1530
1531        if (!this.getReified() && other.getReified()) {
1532            tmp = other.getReified();
1533            tmp.setReifier(this);
1534        }
1535
1536        // Change all constructs that use other as type
1537        tiidx = this.parnt.typeInstanceIndex;
1538        MergeHelper.moveTypes(tiidx.getOccurrences(other), this);
1539        MergeHelper.moveTypes(tiidx.getNames(other), this);
1540        MergeHelper.moveTypes(tiidx.getAssociations(other), this);
1541        MergeHelper.moveTypes(tiidx.getRoles(other), this);
1542
1543        // Change all topics that have other as type
1544        arr = tiidx.getTopics(other);
1545        for (i = 0; i < arr.length; i += 1) {
1546            arr[i].removeType(other);
1547            arr[i].addType(this);
1548        }
1549
1550        // Change all constructs that use other as theme
1551        sidx = this.parnt.scopedIndex;
1552        MergeHelper.moveThemes(sidx.getAssociations(other), other, this);
1553        MergeHelper.moveThemes(sidx.getOccurrences(other), other, this);
1554        MergeHelper.moveThemes(sidx.getNames(other), other, this);
1555        MergeHelper.moveThemes(sidx.getVariants(other), other, this);
1556
1557        MergeHelper.moveItemIdentifiers(other, this);
1558
1559        arr = other.getSubjectLocators();
1560        while (arr.length) {
1561            tmp = arr[arr.length - 1];
1562            other.removeSubjectLocator(tmp);
1563            this.addSubjectLocator(tmp);
1564        }
1565
1566        arr = other.getSubjectIdentifiers();
1567        while (arr.length) {
1568            tmp = arr[arr.length - 1];
1569            other.removeSubjectIdentifier(tmp);
1570            this.addSubjectIdentifier(tmp);
1571        }
1572
1573        arr = other.getTypes();
1574        while (arr.length) {
1575            tmp = arr[arr.length - 1];
1576            other.removeType(tmp);
1577            this.addType(tmp);
1578        }
1579
1580        // merge roles played
1581        arr = this.getRolesPlayed();
1582        signatures = {};
1583        for (i = 0; i < arr.length; i += 1) {
1584            tmp2 = arr[i].getParent();
1585            signatures[SignatureGenerator.makeAssociationSignature(tmp2)] = tmp2;
1586        }
1587        arr = other.getRolesPlayed();
1588        for (i = 0; i < arr.length; i += 1) {
1589            tmp = arr[i];
1590            tmp.setPlayer(this);
1591            if ((tmp2 = signatures[SignatureGenerator.makeAssociationSignature(tmp.getParent())])) {
1592                MergeHelper.moveItemIdentifiers(tmp.getParent(), tmp2);
1593                MergeHelper.moveReifier(tmp.getParent(), tmp2);
1594                tmp.getParent().remove();
1595            }
1596        }
1597
1598        // merge names
1599        arr = this.getNames();
1600        signatures = {};
1601        for (i = 0; i < arr.length; i += 1) {
1602            signatures[SignatureGenerator.makeNameSignature(arr[i])] = arr[i];
1603        }
1604        arr = other.getNames();
1605        for (i = 0; i < arr.length; i += 1) {
1606            tmp = arr[i];
1607            if ((tmp2 = signatures[SignatureGenerator.makeNameSignature(arr[i])])) {
1608                MergeHelper.moveItemIdentifiers(tmp, tmp2);
1609                MergeHelper.moveReifier(tmp, tmp2);
1610                MergeHelper.moveVariants(tmp, tmp2);
1611                tmp.remove();
1612            } else {
1613                tmp2 = this.createName(tmp.getValue(), tmp.getType(), tmp.getScope());
1614                MergeHelper.moveVariants(tmp, tmp2);
1615            }
1616        }
1617
1618        // merge occurrences
1619        arr = this.getOccurrences();
1620        signatures = {};
1621        for (i = 0; i < arr.length; i += 1) {
1622            signatures[SignatureGenerator.makeOccurrenceSignature(arr[i])] = arr[i];
1623        }
1624        arr = other.getOccurrences();
1625        for (i = 0; i < arr.length; i += 1) {
1626            tmp = arr[i];
1627            if ((tmp2 = signatures[SignatureGenerator.makeOccurrenceSignature(arr[i])])) {
1628                MergeHelper.moveItemIdentifiers(tmp, tmp2);
1629                MergeHelper.moveReifier(tmp, tmp2);
1630                tmp.remove();
1631            } else {
1632                tmp2 = this.createOccurrence(tmp.getType(), tmp.getValue(),
1633                    tmp.getDatatype(), tmp.getScope());
1634                MergeHelper.moveReifier(tmp, tmp2);
1635            }
1636        }
1637
1638        other.remove();
1639        return this;
1640    };
1641   
1642    /**
1643     * Removes this topic from the containing TopicMap instance.
1644     * @throws {TopicInUseException} If the topics is used as reifier,
1645     * occurrence type, name type, association type, role type, topic type,
1646     * association theme, occurrence theme, name theme, variant theme,
1647     * or if it is used as a role player.
1648     */
1649    Topic.prototype.remove = function () {
1650        var tiidx = this.parnt.typeInstanceIndex,
1651            sidx = this.parnt.scopedIndex;
1652        if (this.getReified() ||
1653            tiidx.getOccurrences(this).length ||
1654            tiidx.getNames(this).length ||
1655            tiidx.getAssociations(this).length ||
1656            tiidx.getRoles(this).length ||
1657            tiidx.getTopics(this).length ||
1658            sidx.getAssociations(this).length ||
1659            sidx.getOccurrences(this).length ||
1660            sidx.getNames(this).length ||
1661            sidx.getVariants(this).length ||
1662            this.getRolesPlayed().length) {
1663            throw {name: 'TopicInUseException',
1664                message: '', reporter: this};
1665        }
1666        this.parnt._removeTopic(this);
1667        this.parnt._id2construct.remove(this.id);
1668        this.parnt.removeTopicEvent.fire(this);
1669        this.id = null;
1670        return this.parnt;
1671    };
1672   
1673    /**
1674     * Removes a subject identifier from this topic.
1675     * @returns {Topic} The topic itself (for chaining support)
1676     */
1677    Topic.prototype.removeSubjectIdentifier = function (subjectIdentifier) {
1678        for (var i = 0; i < this.subjectIdentifiers.length; i += 1) {
1679            if (this.subjectIdentifiers[i].getReference() ===
1680                subjectIdentifier.getReference()) {
1681                this.subjectIdentifiers.splice(i, 1);
1682                break;
1683            }
1684        }
1685        this.parnt._sl2topic.remove(subjectIdentifier.getReference());
1686        return this;
1687    };
1688   
1689    /**
1690     * Removes a subject locator from this topic.
1691     * @returns {Topic} The topic itself (for chaining support)
1692     */
1693    Topic.prototype.removeSubjectLocator = function (subjectLocator) {
1694        for (var i = 0; i < this.subjectLocators.length; i += 1) {
1695            if (this.subjectLocators[i].getReference() ===
1696                subjectLocator.getReference()) {
1697                this.subjectLocators.splice(i, 1);
1698                break;
1699            }
1700        }
1701        this.parnt._sl2topic.remove(subjectLocator.getReference());
1702        return this;
1703    };
1704   
1705    /**
1706     * Removes a type from this topic.
1707     * @returns {Topic} The topic itself (for chaining support)
1708     */
1709    Topic.prototype.removeType = function (type) {
1710        for (var i = 0; i < this.types.length; i += 1) {
1711            if (this.types[i].equals(type)) {
1712                this.types.splice(i, 1);
1713                this.parnt.removeTypeEvent.fire(this, {type: type});
1714                break;
1715            }
1716        }
1717    };
1718   
1719    Topic.prototype._removeName = function (name) {
1720        for (var i = 0; i < this.names.length; i += 1) {
1721            if (this.names[i].equals(name)) {
1722                this.names.splice(i, 1);
1723                break;
1724            }
1725        }
1726        this.getTopicMap()._removeName(name);
1727    };
1728   
1729    // --------------------------------------------------------------------------
1730    Occurrence = function (parnt, type, value, datatype) {
1731        this.itemIdentifiers = [];
1732        this.parnt = parnt;
1733        this.type = type;
1734        this.value = value;
1735        this.datatype = datatype ? datatype : this.getTopicMap().createLocator(XSD.string);
1736        this.scope = [];
1737        this.reifier = null;
1738        this.id = this.getTopicMap()._getConstructId();
1739        this.getTopicMap()._id2construct.put(this.id, this);
1740    };
1741   
1742    // mergein Typed, DatatypeAware, Reifiable, Scoped, Construct
1743    Occurrence.swiss(Typed, 'getType', 'setType');
1744    Occurrence.swiss(DatatypeAware, 'decimalValue', 'floatValue',
1745        'getDatatype', 'getValue', 'integerValue', 'locatorValue', 'longValue',
1746        'setValue');
1747    Occurrence.swiss(Reifiable, 'getReifier', 'setReifier');
1748    Occurrence.swiss(Scoped, 'addTheme', 'getScope', 'removeTheme');
1749    Occurrence.swiss(Construct, 'addItemIdentifier', 'equals', 'getId',
1750        'getItemIdentifiers', 'getParent', 'getTopicMap', 'hashCode', 'remove',
1751        'removeItemIdentifier', 'isTopic', 'isAssociation', 'isRole',
1752        'isOccurrence', 'isName', 'isVariant', 'isTopicMap');
1753   
1754    Occurrence.prototype.isOccurrence = function () {
1755        return true;
1756    };
1757   
1758    Occurrence.prototype.getTopicMap = function () {
1759        return this.parnt.getParent();
1760    };
1761   
1762    Occurrence.prototype.remove = function () {
1763        var i;
1764        for (i = 0; i < this.scope.length; i += 1) {
1765            this.parnt.parnt.removeThemeEvent.fire(this, {theme: this.scope[i]});
1766        }
1767        this.parnt.parnt.removeOccurrenceEvent.fire(this);
1768        this.parnt._removeOccurrence(this);
1769        this.id = null;
1770        return this.parnt;
1771    };
1772   
1773    Name = function (parnt, value, type) {
1774        this.itemIdentifiers = [];
1775        this.parnt = parnt;
1776        this.value = value;
1777        this.scope = [];
1778        this.id = this.getTopicMap()._getConstructId();
1779        this.type = type ||
1780            parnt.parnt.createTopicBySubjectIdentifier(
1781                parnt.parnt.createLocator('http://psi.topicmaps.org/iso13250/model/topic-name'));
1782        this.reifier = null;
1783        this.variants = [];
1784        this.getTopicMap()._id2construct.put(this.id, this);
1785        this.parnt.parnt.addNameEvent.fire(this, {type: this.type, value: value});
1786    };
1787   
1788    // mergein Typed, DatatypeAware, Reifiable, Scoped, Construct
1789    Name.swiss(Typed, 'getType', 'setType');
1790    Name.swiss(Reifiable, 'getReifier', 'setReifier');
1791    Name.swiss(Scoped, 'addTheme', 'getScope', 'removeTheme');
1792    Name.swiss(Construct, 'addItemIdentifier', 'equals', 'getId',
1793        'getItemIdentifiers', 'getParent', 'getTopicMap', 'hashCode', 'remove',
1794        'removeItemIdentifier', 'isTopic', 'isAssociation', 'isRole',
1795        'isOccurrence', 'isName', 'isVariant', 'isTopicMap');
1796   
1797    Name.prototype.isName = function () {
1798        return true;
1799    };
1800   
1801    Name.prototype.getTopicMap = function () {
1802        return this.parnt.parnt;
1803    };
1804   
1805    /**
1806     * @throws {ModelConstraintException} If scope is null.
1807     */
1808    Name.prototype.createVariant = function (value, datatype, scope) {
1809        var scope_length = 0, i, variant;
1810        if (typeof scope === 'undefined' || scope === null) {
1811            throw {name: 'ModelConstraintException',
1812            message: 'Creation of a variant with a null scope is not allowed'};
1813        }
1814        if (scope && typeof scope === 'object') {
1815            if (scope instanceof Array) {
1816                scope_length = scope.length;
1817            } else if (scope instanceof Topic) {
1818                scope_length = 1;
1819            }
1820        }
1821       /*
1822        TODO: Compare scope of Name and Variant
1823        if (scope_length <= this.getScope().length) {
1824            // check if the variants scope contains more scoping topics
1825            throw {name: 'ModelConstraintException',
1826            message: 'The variant would be in the same scope as the parent'};
1827        }*/
1828        variant = new Variant(this, value, datatype);
1829        addScope(variant, scope);
1830        for (i = 0; i < this.scope.length; i += 1) {
1831            this.getTopicMap().addThemeEvent.fire(variant,
1832                {theme: this.scope[i]});
1833        }
1834        this.variants.push(variant);
1835        return variant;
1836    };
1837   
1838    /**
1839     * @throws {ModelConstraintException} If value is null.
1840     * @returns {Name} The name itself (for chaining support)
1841     */
1842    Name.prototype.setValue = function (value) {
1843        if (!value) {
1844            throw {name: 'ModelConstraintException',
1845            message: 'Name.setValue(null) is not allowed'};
1846        }
1847        this.value = value;
1848        return this;
1849    };
1850   
1851    Name.prototype.getValue = function (value) {
1852        return this.value;
1853    };
1854   
1855    Name.prototype.remove = function () {
1856        var i;
1857        for (i = 0; i < this.scope.length; i += 1) {
1858            this.parnt.parnt.removeThemeEvent.fire(this, {theme: this.scope[i]});
1859        }
1860        this.parnt.parnt.removeNameEvent.fire(this);
1861        this.parnt._removeName(this);
1862        this.id = null;
1863        return this.parnt;
1864    };
1865   
1866    Name.prototype._removeVariant = function (variant) {
1867        for (var i = 0; i < this.variants.length; i += 1) {
1868            if (this.variants[i].equals(variant)) {
1869                this.variants.splice(i, 1);
1870                break;
1871            }
1872        }
1873        this.getTopicMap()._removeVariant(variant);
1874    };
1875
1876    Name.prototype.getVariants = function () {
1877        return this.variants;
1878    };
1879   
1880    /**
1881     * @throws {ModelConstraintException} If value or datatype is null.
1882     */
1883    Variant = function (parnt, value, datatype) {
1884        if (value === null) {
1885            throw {name: 'ModelConstraintException',
1886                message: 'Creation of a variant with null value is not allowed'};
1887        }
1888        if (datatype === null) {
1889            throw {name: 'ModelConstraintException',
1890            message: 'Creation of a variant with datatype == null is not allowed'};
1891        }
1892        this.itemIdentifiers = [];
1893        this.scope = [];
1894        this.parnt = parnt;
1895        if (typeof value === 'object' && value instanceof Locator) {
1896            this.datatype = this.getTopicMap().createLocator('http://www.w3.org/2001/XMLSchema#anyURI');
1897        } else {
1898            this.datatype = 
1899                this.getTopicMap().createLocator(XSD.string);
1900        }
1901        this.datatype = datatype;
1902        this.reifier = null;
1903        this.value = value;
1904        this.id = this.getTopicMap()._getConstructId();
1905        this.getTopicMap()._id2construct.put(this.id, this);
1906    };
1907   
1908    Variant.swiss(Reifiable, 'getReifier', 'setReifier');
1909    Variant.swiss(Scoped, 'addTheme', 'getScope', 'removeTheme');
1910    Variant.swiss(Construct, 'addItemIdentifier', 'equals', 'getId',
1911        'getItemIdentifiers', 'getParent', 'getTopicMap', 'hashCode', 'remove',
1912        'removeItemIdentifier', 'isTopic', 'isAssociation', 'isRole',
1913        'isOccurrence', 'isName', 'isVariant', 'isTopicMap');
1914    Variant.swiss(DatatypeAware, 'decimalValue', 'floatValue', 'getDatatype',
1915        'getValue', 'integerValue', 'locatorValue', 'longValue', 'setValue');
1916   
1917    Variant.prototype.isVariant = function () {
1918        return true;
1919    };
1920   
1921    Variant.prototype.getTopicMap = function () {
1922        return this.getParent().getParent().getParent();
1923    };
1924   
1925    Variant.prototype.remove = function () {
1926        var i;
1927        for (i = 0; i < this.scope.length; i += 1) {
1928            this.getTopicMap().removeThemeEvent.fire(this, {theme: this.scope[i]});
1929        }
1930        this.getParent()._removeVariant(this);
1931        this.id = null;
1932        return this.parnt;
1933    };
1934   
1935
1936    Role = function (parnt, type, player) {
1937        this.itemIdentifiers = [];
1938        this.parnt = parnt;
1939        this.type = type;
1940        this.player = player;
1941        this.id = this.getTopicMap()._getConstructId();
1942        this.reifier = null;
1943        this.getTopicMap()._id2construct.put(this.id, this);
1944    };
1945   
1946    Role.swiss(Typed, 'getType', 'setType');
1947    Role.swiss(Reifiable, 'getReifier', 'setReifier');
1948    Role.swiss(Construct, 'addItemIdentifier', 'equals', 'getId',
1949        'getItemIdentifiers', 'getParent', 'getTopicMap', 'hashCode', 'remove',
1950        'removeItemIdentifier', 'isTopic', 'isAssociation', 'isRole',
1951        'isOccurrence', 'isName', 'isVariant', 'isTopicMap');
1952   
1953    Role.prototype.isRole = function () {
1954        return true;
1955    };
1956   
1957    Role.prototype.getTopicMap = function () {
1958        return this.getParent().getParent();
1959    };
1960   
1961    Role.prototype.remove = function () {
1962        var parnt = this.parnt;
1963        this.parnt.parnt.removeRoleEvent.fire(this);
1964        this.parnt._removeRole(this);
1965        this.itemIdentifiers = null;
1966        this.parnt = null;
1967        this.type = null;
1968        this.player = null;
1969        this.reifier = null;
1970        this.id = null;
1971        return parnt;
1972    };
1973   
1974    Role.prototype.getPlayer = function () {
1975        return this.player;
1976    };
1977   
1978    /**
1979     * @throws {ModelConstraintException} If player is null.
1980     * @returns {Role} The role itself (for chaining support)
1981     */
1982    Role.prototype.setPlayer = function (player) {
1983        if (!player) {
1984            throw {name: 'ModelConstraintException',
1985            message: 'player i Role.setPlayer cannot be null'};
1986        }
1987        SameTopicMapHelper.assertBelongsTo(this.parnt.parnt, player);
1988        if (this.player.equals(player)) {
1989            return;
1990        }
1991        this.player.removeRolePlayed(this);
1992        player.addRolePlayed(this);
1993        this.player = player;
1994        return this;
1995    };
1996   
1997    Association = function (par) {
1998        this.itemIdentifiers = [];
1999        this.parnt = par;
2000        this.id = this.getTopicMap()._getConstructId();
2001        this.getTopicMap()._id2construct.put(this.id, this);
2002        this.roles = [];
2003        this.scope = [];
2004        this.type = null;
2005        this.reifier = null;
2006    };
2007   
2008    Association.swiss(Typed, 'getType', 'setType');
2009    Association.swiss(Reifiable, 'getReifier', 'setReifier');
2010    Association.swiss(Scoped, 'addTheme', 'getScope', 'removeTheme');
2011    Association.swiss(Construct, 'addItemIdentifier', 'equals', 'getId',
2012        'getItemIdentifiers', 'getParent', 'getTopicMap', 'hashCode', 'remove',
2013        'removeItemIdentifier', 'isTopic', 'isAssociation', 'isRole',
2014        'isOccurrence', 'isName', 'isVariant', 'isTopicMap');
2015   
2016    Association.prototype.isAssociation = function () {
2017        return true;
2018    };
2019   
2020    Association.prototype.getTopicMap = function () {
2021        return this.parnt;
2022    };
2023   
2024    /**
2025     * Creates a new Role representing a role in this association.
2026     * @throws {ModelConstraintException} If type or player is null.
2027     */
2028    Association.prototype.createRole = function (type, player) {
2029        if (!type) {
2030            throw {name: 'ModelConstraintException',
2031                message: 'type i Role.createPlayer cannot be null'};
2032        }
2033        if (!player) {
2034            throw {name: 'ModelConstraintException',
2035                message: 'player i Role.createRole cannot be null'};
2036        }
2037        SameTopicMapHelper.assertBelongsTo(this.parnt, type);
2038        SameTopicMapHelper.assertBelongsTo(this.parnt, player);
2039        var role = new Role(this, type, player);
2040        player.addRolePlayed(role);
2041        this.roles.push(role);
2042        this.parnt.addRoleEvent.fire(role, {type: type, player: player});
2043        return role;
2044    };
2045   
2046    Association.prototype._removeRole = function (role) {
2047        for (var i = 0; i < this.roles.length; i += 1) {
2048            if (role.id === this.roles[i].id) {
2049                this.roles.splice(i, 1);
2050                break;
2051            }
2052        }
2053        role.getPlayer().removeRolePlayed(role);
2054        this.getTopicMap()._removeRole(role);
2055    };
2056   
2057    Association.prototype.remove = function () {
2058        var i;
2059        for (i = 0; i < this.scope.length; i += 1) {
2060            this.parnt.removeThemeEvent.fire(this, {theme: this.scope[i]});
2061        }
2062        this.parnt.removeAssociationEvent.fire(this);
2063        while (this.roles.length) {
2064            this.roles[0].remove();
2065        }
2066        this.id = null;
2067        this.roles = null;
2068        this.parnt._removeAssociation(this);
2069        this.getTopicMap()._ii2construct.remove(this.id);
2070        this.item_identifiers = null;
2071        this.scope = null;
2072        this.type = null;
2073        this.reifier = null;
2074        return this.parnt;
2075    };
2076   
2077    /**
2078     * Returns the roles participating in this association, or, if type
2079     * is given, all roles with the specified type.
2080     * @throws {IllegalArgumentException} If type is null.
2081     */
2082    Association.prototype.getRoles = function (type) {
2083        if (type === null) {
2084            throw {name: 'IllegalArgumentException',
2085                message: 'Topic.getRoles cannot be called with type null'};
2086        }
2087        if (!type) {
2088            return this.roles;
2089        }
2090        var ret = [], i; 
2091        for (i = 0; i < this.roles.length; i += 1) {
2092            if (this.roles[i].getType().equals(type)) {
2093                ret.push(this.roles[i]);
2094            }
2095        }
2096        return ret;
2097    };
2098
2099    /**
2100     * Returns the role types participating in this association.
2101     */
2102    Association.prototype.getRoleTypes = function () {
2103        // Create a hash with the object ids as keys to avoid duplicates
2104        var types = {}, typearr = [], i, t;
2105        for (i = 0; i < this.roles.length; i += 1) {
2106            types[this.roles[i].getType().getId()] =
2107                this.roles[i].getType();
2108        }
2109        for (t in types) {
2110            if (types.hasOwnProperty(t)) {
2111                typearr.push(types[t]);
2112            }
2113        }
2114        return typearr;
2115    };
2116
2117    // ------ ----------------------------------------------------------------
2118    /** @class */
2119    Index = function () {
2120        this.opened = false;
2121    };
2122
2123    /**
2124     * Close the index.
2125     */
2126    Index.prototype.close = function () {
2127        return;
2128    };
2129
2130    /**
2131     * Indicates whether the index is updated automatically.
2132     * @returns {boolean}
2133     */
2134    Index.prototype.isAutoUpdated = function () {
2135        return true;
2136    };
2137
2138    /** Indicates if the index is open.
2139     * @returns {boolean} true if index is already opened, false otherwise.
2140     */
2141    Index.prototype.isOpen = function () {
2142        return this.opened;
2143    };
2144
2145    /**
2146     * Opens the index. This method must be invoked before using any other
2147     * method (aside from isOpen()) exported by this interface or derived
2148     * interfaces.
2149     */
2150    Index.prototype.open = function () {
2151        this.opened = true;
2152    };
2153
2154    /**
2155     * Synchronizes the index with data in the topic map.
2156     */
2157    Index.prototype.reindex = function () {
2158        return;
2159    };
2160
2161    /**
2162     * Creates a new instance of TypeInstanceIndex.
2163     * @class Implementation of the TypeInstanceIndex interface.
2164     */
2165    TypeInstanceIndex = function (tm) {
2166        var eventHandler, that = this;
2167        this.tm = tm;
2168        // we use hash tables of hash tables for our index
2169        this.type2topics = new Hash();
2170        this.type2associations = new Hash();
2171        this.type2roles = new Hash();
2172        this.type2occurrences = new Hash();
2173        this.type2names = new Hash();
2174        this.type2variants = new Hash();
2175        this.opened = false;
2176
2177        eventHandler = function (eventtype, source, obj) {
2178            var existing, untyped, types, type, i;
2179            switch (eventtype) {
2180            case EventType.ADD_ASSOCIATION:
2181                break;
2182            case EventType.ADD_NAME:
2183                existing = that.type2names.get(obj.type.getId());
2184                if (typeof existing === 'undefined') {
2185                    existing = new Hash();
2186                }
2187                existing.put(source.getId(), source);
2188                that.type2names.put(obj.type.getId(), existing);
2189                break;
2190            case EventType.ADD_OCCURRENCE:
2191                existing = that.type2occurrences.get(obj.type.getId());
2192                if (typeof existing === 'undefined') {
2193                    existing = new Hash();
2194                }
2195                existing.put(source.getId(), source);
2196                that.type2occurrences.put(obj.type.getId(), existing);
2197                break;
2198            case EventType.ADD_ROLE:
2199                existing = that.type2roles.get(obj.type.getId());
2200                if (typeof existing === 'undefined') {
2201                    existing = new Hash();
2202                }
2203                existing.put(source.getId(), source);
2204                that.type2roles.put(obj.type.getId(), existing);
2205                break;
2206            case EventType.ADD_TOPIC:
2207                existing = that.type2topics.get('null');
2208                if (typeof existing === 'undefined') {
2209                    existing = new Hash();
2210                }
2211                existing.put(source.getId(), source);
2212                that.type2topics.put('null', existing);
2213                break;
2214            case EventType.ADD_TYPE:
2215                // check if source exists with type null, remove it there
2216                untyped = that.type2topics.get('null');
2217                if (untyped && untyped.get(source.getId())) {
2218                    untyped.remove(source.getId());
2219                    that.type2topics.put('null', untyped);
2220                }
2221                   
2222                existing = that.type2topics.get(obj.type.getId());
2223                if (typeof existing === 'undefined') {
2224                    existing = new Hash();
2225                }
2226                existing.put(source.getId(), source);
2227                that.type2topics.put(obj.type.getId(), existing);
2228                break;
2229            case EventType.REMOVE_ASSOCIATION:
2230                type = source.getType();
2231                if (!type) {
2232                    break;
2233                }
2234                existing = that.type2associations.get(type.getId());
2235                for (i = 0; i < existing.length; i += 1) {
2236                    if (existing[i].equals(source)) {
2237                        existing.splice(i, 1);
2238                        break;
2239                    }   
2240                }
2241                if (existing.length > 0) {
2242                    that.type2associations.put(type.getId(),
2243                            existing);
2244                } else {
2245                    that.type2associations.remove(type.getId());
2246                }
2247                break;
2248            case EventType.REMOVE_NAME:
2249                type = source.getType();
2250                existing = that.type2names.get(type.getId());
2251                existing.remove(source.getId());
2252                if (existing.length > 0) {
2253                    that.type2names.put(type.getId(), existing);
2254                } else {
2255                    that.type2names.remove(type.getId());
2256                }
2257                break;
2258            case EventType.REMOVE_OCCURRENCE:
2259                type = source.getType();
2260                existing = that.type2occurrences.get(type.getId());
2261                existing.remove(source.getId());
2262                if (existing.length > 0) {
2263                    that.type2occurrences.put(type.getId(), existing);
2264                } else {
2265                    that.type2occurrences.remove(type.getId());
2266                }
2267                break;
2268            case EventType.REMOVE_ROLE:
2269                type = source.getType();
2270                existing = that.type2roles.get(type.getId());
2271                existing.remove(source.getId());
2272                if (existing.length > 0) {
2273                    that.type2roles.put(type.getId(), existing);
2274                } else {
2275                    that.type2roles.remove(type.getId());
2276                }
2277                break;
2278            case EventType.REMOVE_TOPIC:
2279                // two cases:
2280                //  topic has types
2281                types = source.getTypes();
2282                for (i = 0; i < types.length; i += 1) {
2283                    existing = that.type2topics.get(types[i].getId());
2284                    existing.remove(source.getId());
2285                    if (!existing.size()) {
2286                        that.type2topics.remove(types[i].getId());
2287                    }
2288                }
2289                // topic used as type
2290                that.type2topics.remove(source.getId());
2291                that.type2associations.remove(source.getId());
2292                that.type2roles.remove(source.getId());
2293                that.type2occurrences.remove(source.getId());
2294                that.type2variants.remove(source.getId());
2295                break;
2296            case EventType.REMOVE_TYPE:
2297                existing = that.type2topics.get(obj.type.getId());
2298                existing.remove(source.getId());
2299                if (!existing.size()) {
2300                    that.type2topics.remove(obj.type.getId());
2301                }
2302                if (source.getTypes().length === 0) {
2303                    untyped = that.type2topics.get('null');
2304                    if (typeof untyped === 'undefined') {
2305                        untyped = new Hash();
2306                    }
2307                    untyped.put(source.getId(), source);
2308                }
2309                break;
2310            case EventType.SET_TYPE:
2311                if (source.isAssociation()) {
2312                    // remove source from type2associations(obj.old.getId());
2313                    if (obj.old) {
2314                        existing = that.type2associations.get(obj.old.getId());
2315                        for (i = 0; i < existing.length; i += 1) {
2316                            if (existing[i].equals(source)) {
2317                                existing.splice(i, 1);
2318                                break;
2319                            }   
2320                        }
2321                        if (existing.length > 0) {
2322                            that.type2associations.put(obj.old.getId(),
2323                                    existing);
2324                        } else {
2325                            that.type2associations.remove(obj.old.getId());
2326                        }
2327                    }
2328                    existing = that.type2associations.get(obj.type.getId());
2329                    if (typeof existing === 'undefined') {
2330                        existing = [];
2331                    }
2332                    existing.push(source);
2333                    that.type2associations.put(obj.type.getId(), existing);
2334                } else if (source.isName()) {
2335                    existing = that.type2names.get(obj.old.getId());
2336                    if (existing) {
2337                        existing.remove(source.getId());
2338                        if (existing.length > 0) {
2339                            that.type2names.put(obj.old.getId(), existing);
2340                        } else {
2341                            that.type2names.remove(obj.old.getId());
2342                        }
2343                    }
2344                    existing = that.type2names.get(obj.type.getId());
2345                    if (typeof existing === 'undefined') {
2346                        existing = new Hash();
2347                    }
2348                    existing.put(source.getId(), source);
2349                    that.type2names.put(obj.type.getId(), existing);
2350                } else if (source.isOccurrence()) {
2351                    existing = that.type2occurrences.get(obj.old.getId());
2352                    if (existing) {
2353                        existing.remove(source.getId());
2354                        if (existing.length > 0) {
2355                            that.type2occurrences.put(obj.old.getId(), existing);
2356                        } else {
2357                            that.type2occurrences.remove(obj.old.getId());
2358                        }
2359                    }
2360                    existing = that.type2occurrences.get(obj.type.getId());
2361                    if (typeof existing === 'undefined') {
2362                        existing = new Hash();
2363                    }
2364                    existing.put(source.getId(), source);
2365                    that.type2occurrences.put(obj.type.getId(), existing);
2366                } else if (source.isRole()) {
2367                    existing = that.type2roles.get(obj.old.getId());
2368                    if (existing) {
2369                        existing.remove(source.getId());
2370                        if (existing.length > 0) {
2371                            that.type2roles.put(obj.old.getId(), existing);
2372                        } else {
2373                            that.type2roles.remove(obj.old.getId());
2374                        }
2375                    }
2376                    existing = that.type2roles.get(obj.type.getId());
2377                    if (typeof existing === 'undefined') {
2378                        existing = new Hash();
2379                    }
2380                    existing.put(source.getId(), source);
2381                    that.type2roles.put(obj.type.getId(), existing);
2382                }
2383                break;
2384            }
2385        };
2386        tm.addAssociationEvent.registerHandler(eventHandler);
2387        tm.addNameEvent.registerHandler(eventHandler);
2388        tm.addOccurrenceEvent.registerHandler(eventHandler);
2389        tm.addRoleEvent.registerHandler(eventHandler);
2390        tm.addTopicEvent.registerHandler(eventHandler);
2391        tm.addTypeEvent.registerHandler(eventHandler);
2392        tm.removeAssociationEvent.registerHandler(eventHandler);
2393        tm.removeNameEvent.registerHandler(eventHandler);
2394        tm.removeOccurrenceEvent.registerHandler(eventHandler);
2395        tm.removeRoleEvent.registerHandler(eventHandler);
2396        tm.removeTopicEvent.registerHandler(eventHandler);
2397        tm.removeTypeEvent.registerHandler(eventHandler);
2398        tm.setTypeEvent.registerHandler(eventHandler);
2399    };
2400
2401    TypeInstanceIndex.swiss(Index, 'close', 'isAutoUpdated',
2402        'isOpen', 'open', 'reindex');
2403         
2404    /**
2405     * Returns the associations in the topic map whose type property equals type.
2406     *
2407     * @param {Topic} type
2408     * @returns {Array} A list of all associations in the topic map with the given type.
2409     */
2410    TypeInstanceIndex.prototype.getAssociations = function (type) {
2411        var ret = this.type2associations.get(type.getId());
2412        if (!ret) {
2413            return [];
2414        }
2415        return ret;
2416    };
2417
2418    /**
2419     * Returns the topics in the topic map used in the type property of Associations.
2420     *
2421     * @returns {Array} A list of all topics that are used as an association type.
2422     */
2423    TypeInstanceIndex.prototype.getAssociationTypes = function () {
2424        var ret = [], keys = this.type2associations.keys(), i;
2425        for (i = 0; i < keys.length; i += 1) {
2426            ret.push(this.tm.getConstructById(keys[i]));
2427        }
2428        return ret;
2429    };
2430
2431    /**
2432     * Returns the topic names in the topic map whose type property equals type.
2433     *
2434     * @param {Topic} type
2435     * @returns {Array}
2436     */
2437    TypeInstanceIndex.prototype.getNames = function (type) {
2438        var ret = this.type2names.get(type.getId());
2439        if (!ret) {
2440            return [];
2441        }
2442        return ret.values();
2443    };
2444
2445    /**
2446     * Returns the topics in the topic map used in the type property of Names.
2447     *
2448     * @returns {Array} An array of topic types. Note that the array contains
2449     * a reference to the actual topics, not copies of them.
2450     */
2451    TypeInstanceIndex.prototype.getNameTypes = function () {
2452        var ret = [], keys = this.type2names.keys(), i;
2453        for (i = 0; i < keys.length; i += 1) {
2454            ret.push(this.tm.getConstructById(keys[i]));
2455        }
2456        return ret;
2457    };
2458
2459    /**
2460     * Returns the occurrences in the topic map whose type property equals type.
2461     *
2462     * @returns {Array}
2463     */
2464    TypeInstanceIndex.prototype.getOccurrences = function (type) {
2465        var ret = this.type2occurrences.get(type.getId());
2466        if (!ret) {
2467            return [];
2468        }
2469        return ret.values();
2470    };
2471
2472    /**
2473     * Returns the topics in the topic map used in the type property of
2474     * Occurrences.
2475     *
2476     * @returns {Array} An array of topic types. Note that the array contains
2477     * a reference to the actual topics, not copies of them.
2478     */
2479    TypeInstanceIndex.prototype.getOccurrenceTypes = function () {
2480        var ret = [], keys = this.type2occurrences.keys(), i;
2481        for (i = 0; i < keys.length; i += 1) {
2482            ret.push(this.tm.getConstructById(keys[i]));
2483        }
2484        return ret;
2485    };
2486
2487
2488    /**
2489     * Returns the roles in the topic map whose type property equals type.
2490     *
2491     * @returns {Array}
2492     */
2493    TypeInstanceIndex.prototype.getRoles = function (type) {
2494        var ret = this.type2roles.get(type.getId());
2495        if (!ret) {
2496            return [];
2497        }
2498        return ret.values();
2499    };
2500
2501    /**
2502     * Returns the topics in the topic map used in the type property of Roles.
2503     *
2504     * @returns {Array} An array of topic types. Note that the array contains
2505     * a reference to the actual topics, not copies of them.
2506     */
2507    TypeInstanceIndex.prototype.getRoleTypes = function () {
2508        var ret = [], keys = this.type2roles.keys(), i;
2509        for (i = 0; i < keys.length; i += 1) {
2510            ret.push(this.tm.getConstructById(keys[i]));
2511        }
2512        return ret;
2513    };
2514
2515    /**
2516     * Returns the topics which are an instance of the specified type.
2517     */
2518    TypeInstanceIndex.prototype.getTopics = function (type) {
2519        var ret = this.type2topics.get((type ? type.getId() : 'null'));
2520        if (!ret) {
2521            return [];
2522        }
2523        return ret.values();
2524    };
2525
2526    /**
2527     * Returns the topics which are an instance of the specified types.
2528     * If matchall is true only topics that have all of the listed types
2529     * are returned.
2530     * @returns {Array} A list of Topic objects
2531     */
2532    TypeInstanceIndex.prototype.getTopicsByTypes = function (types, matchall) {
2533        var instances, i, j;
2534        instances = IndexHelper.getForKeys(this.type2topics, types);
2535        if (!matchall) {
2536            return instances;
2537        }
2538        // If matchall is true, we check all values for all types in {types}
2539        // It's a hack, but will do for now
2540        for (i = 0; i < instances.length; i += 1) {
2541            for (j = 0; j < types.length; j += 1) {
2542                if (!ArrayHelper.contains(instances[i].getTypes(), types[j])) {
2543                    instances.splice(i, 1);
2544                    i -= 1;
2545                    break;
2546                }
2547            }
2548        }
2549        return instances;
2550    };
2551
2552    /**
2553     * Returns the topics in topic map which are used as type in an
2554     * "type-instance"-relationship.
2555     */
2556    TypeInstanceIndex.prototype.getTopicTypes = function () {
2557        var ret = [], keys = this.type2topics.keys(), i;
2558        for (i = 0; i < keys.length; i += 1) {
2559            if (keys[i] !== 'null') {
2560                ret.push(this.tm.getConstructById(keys[i]));
2561            }
2562        }
2563        return ret;
2564    };
2565
2566    TypeInstanceIndex.prototype.close = function () {
2567        return;
2568    };
2569
2570
2571    /**
2572     * Index for Scoped statements and their scope. This index provides access
2573     * to Associations, Occurrences, Names, and Variants by their scope
2574     * property and to Topics which are used as theme in a scope.
2575     */
2576    ScopedIndex = function (tm) {
2577        var that = this, eventHandler;
2578        this.tm = tm;
2579        this.theme2associations = new Hash();
2580        this.theme2names = new Hash();
2581        this.theme2occurrences = new Hash();
2582        this.theme2variants = new Hash();
2583        eventHandler = function (eventtype, source, obj) {
2584            var existing, key, unscoped, remove_from_index, add_to_index;
2585            add_to_index = function (hash, source, obj) {
2586                key = (obj.theme ? obj.theme.getId() : 'null');
2587
2588                // check if source exists with theme null, remove it there
2589                // this is the case iff source now has one scoping topic
2590                if (source.getScope().length === 1) {
2591                    unscoped = hash.get('null');
2592                    if (unscoped && unscoped.get(source.getId())) {
2593                        unscoped.remove(source.getId());
2594                        hash.put('null', unscoped);
2595                    }
2596                }
2597                existing = hash.get(key);
2598                if (typeof existing === 'undefined') {
2599                    existing = new Hash();
2600                }
2601                existing.put(source.getId(), source); 
2602                hash.put(key, existing);
2603            };
2604            remove_from_index = function (hash, source, obj) {
2605                key = obj.theme.getId();
2606                existing = hash.get(key);
2607                if (typeof existing !== 'undefined') {
2608                    existing.remove(source.getId()); 
2609                    if (!existing.size()) {
2610                        hash.remove(key);
2611                    }
2612                }
2613            };
2614            switch (eventtype) {
2615            case EventType.ADD_THEME:
2616                if (source.isAssociation()) {
2617                    add_to_index(that.theme2associations, source, obj);
2618                } else if (source.isName()) {
2619                    add_to_index(that.theme2names, source, obj);
2620                } else if (source.isOccurrence()) {
2621                    add_to_index(that.theme2occurrences, source, obj);
2622                } else if (source.isVariant()) {
2623                    add_to_index(that.theme2variants, source, obj);
2624                }
2625                break;
2626            case EventType.REMOVE_THEME:
2627                if (source.isAssociation()) {
2628                    remove_from_index(that.theme2associations, source, obj);
2629                } else if (source.isName()) {
2630                    remove_from_index(that.theme2names, source, obj);
2631                } else if (source.isOccurrence()) {
2632                    remove_from_index(that.theme2occurrences, source, obj);
2633                } else if (source.isVariant()) {
2634                    remove_from_index(that.theme2variants, source, obj);
2635                }
2636                break;
2637            }
2638        };
2639        tm.addThemeEvent.registerHandler(eventHandler);
2640        tm.removeThemeEvent.registerHandler(eventHandler);
2641    };
2642
2643    ScopedIndex.swiss(Index, 'close', 'isAutoUpdated',
2644        'isOpen', 'open', 'reindex');
2645
2646    ScopedIndex.prototype.close = function () {
2647        return;
2648    };
2649
2650    /**
2651     * Returns the Associations in the topic map whose scope property contains
2652     * the specified theme. The return value may be empty but must never be null.
2653     * @param theme can be array or {Topic}
2654     * @param [matchall] boolean
2655     */
2656    ScopedIndex.prototype.getAssociations = function (theme) {
2657        var ret = this.theme2associations.get((theme ? theme.getId() : 'null'));
2658        if (!ret) {
2659            return [];
2660        }
2661        return ret.values();
2662    };
2663
2664    /**
2665     * Returns the Associations in the topic map whose scope property contains
2666     * the specified theme. The return value may be empty but must never be null.
2667     * @param theme can be array or {Topic}
2668     * @param [matchall] boolean
2669     * @throws {IllegalArgumentException} If themes is null.
2670     */
2671    ScopedIndex.prototype.getAssociationsByThemes = function (themes, matchall) {
2672        if (themes === null) {
2673            throw {name: 'IllegalArgumentException',
2674                message: 'ScopedIndex.getAssociationsByThemes cannot be called without themes'};
2675        }
2676        return IndexHelper.getConstructsByThemes(this.theme2associations,
2677            themes, matchall);
2678    };
2679
2680    /**
2681     * Returns the topics in the topic map used in the scope property of
2682     * Associations.
2683     */
2684    ScopedIndex.prototype.getAssociationThemes = function () {
2685        return IndexHelper.getConstructThemes(this.tm, this.theme2associations);
2686    };
2687
2688    /**
2689     * Returns the Names in the topic map whose scope property contains the
2690     * specified theme.
2691     */
2692    ScopedIndex.prototype.getNames = function (theme) {
2693        var ret = this.theme2names.get((theme ? theme.getId() : 'null'));
2694        if (!ret) {
2695            return [];
2696        }
2697        return ret.values();
2698    };
2699
2700    /**
2701     * Returns the Names in the topic map whose scope property equals one of
2702     * those themes at least.
2703     * @throws {IllegalArgumentException} If themes is null.
2704     */
2705    ScopedIndex.prototype.getNamesByThemes = function (themes, matchall) {
2706        if (themes === null) {
2707            throw {name: 'IllegalArgumentException',
2708                message: 'ScopedIndex.getNamesByThemes cannot be called without themes'};
2709        }
2710        return IndexHelper.getConstructsByThemes(this.theme2names,
2711            themes, matchall);
2712    };
2713
2714    /**
2715     * Returns the topics in the topic map used in the scope property of Names.
2716     */
2717    ScopedIndex.prototype.getNameThemes = function () {
2718        return IndexHelper.getConstructThemes(this.tm, this.theme2names);
2719    };
2720
2721    /**
2722     * Returns the Occurrences in the topic map whose scope property contains the
2723     * specified theme.
2724     */
2725    ScopedIndex.prototype.getOccurrences = function (theme) {
2726        var ret = this.theme2occurrences.get((theme ? theme.getId() : 'null'));
2727        if (!ret) {
2728            return [];
2729        }
2730        return ret.values();
2731    };
2732
2733    /**
2734     * Returns the Occurrences in the topic map whose scope property equals one
2735     * of those themes at least.
2736     * @throws {IllegalArgumentException} If themes is null.
2737     */
2738    ScopedIndex.prototype.getOccurrencesByThemes = function (themes, matchall) {
2739        if (themes === null) {
2740            throw {name: 'IllegalArgumentException',
2741                message: 'ScopedIndex.getOccurrencesByThemes cannot be called without themes'};
2742        }
2743        return IndexHelper.getConstructsByThemes(this.theme2occurrences,
2744            themes, matchall);
2745    };
2746
2747    /**
2748     * Returns the topics in the topic map used in the scope property of
2749     * Occurrences.
2750     */
2751    ScopedIndex.prototype.getOccurrenceThemes = function () {
2752        return IndexHelper.getConstructThemes(this.tm, this.theme2occurrences);
2753    };
2754
2755    /**
2756     * Returns the Variants in the topic map whose scope property contains the
2757     * specified theme. The return value may be empty but must never be null.
2758     * @param {Topic} The Topic which must be part of the scope. This must not be
2759     * null.
2760     * @returns {Array} An array of Variants.
2761     * @throws {IllegalArgumentException} If theme is null.
2762     */
2763    ScopedIndex.prototype.getVariants = function (theme) {
2764        if (theme === null) {
2765            throw {name: 'IllegalArgumentException',
2766                message: 'ScopedIndex.getVariants cannot be called without themes'};
2767        }
2768        var ret = this.theme2variants.get((theme ? theme.getId() : 'null'));
2769        if (!ret) {
2770            return [];
2771        }
2772        return ret.values();
2773    };
2774
2775    /**
2776     * Returns the Variants in the topic map whose scope property equals one of
2777     * those themes at least.
2778     * @param {Array} themes Scope of the Variants to be returned.
2779     * @param {boolean} If true the scope property of a variant must match all
2780     * themes, if false one theme must be matched at least.
2781     * @returns {Array} An array of variants
2782     * @throws {IllegalArgumentException} If themes is null.
2783     */
2784    ScopedIndex.prototype.getVariantsByThemes = function (themes, matchall) {
2785        if (themes === null) {
2786            throw {name: 'IllegalArgumentException',
2787                message: 'ScopedIndex.getVariantsByThemes cannot be called without themes'};
2788        }
2789        return IndexHelper.getConstructsByThemes(this.theme2variants,
2790            themes, matchall);
2791    };
2792
2793    /**
2794     * Returns the topics in the topic map used in the scope property of Variants.
2795     * The return value may be empty but must never be null.
2796     * @returns {Array} An array of Topics.
2797     */
2798    ScopedIndex.prototype.getVariantThemes = function () {
2799        return IndexHelper.getConstructThemes(this.tm, this.theme2variants);
2800    };
2801
2802
2803
2804
2805    /**
2806     * @class Helper class that is used to check if constructs belong to
2807     * a given topic map.
2808     */
2809    SameTopicMapHelper = {
2810        /**
2811         * Checks if topic belongs to the topicmap 'topicmap'.
2812         * topic can be instance of Topic or an Array with topics.
2813         * topic map be null.
2814         * @static
2815         * @throws ModelConstraintException if the topic(s) don't
2816         * belong to the same topic map.
2817         * @returns false if the topic was null or true otherwise.
2818         */
2819        assertBelongsTo: function (topicmap, topic) {
2820            var i;
2821            if (!topic) {
2822                return false;
2823            }
2824            if (topic && topic instanceof Topic &&
2825                    !topicmap.equals(topic.getTopicMap())) {
2826                throw {name: 'ModelConstraintException',
2827                    message: 'scope topic belongs to different topic map'};
2828            }
2829            if (topic && topic instanceof Array) {
2830                for (i = 0; i < topic.length; i += 1) {
2831                    if (!topicmap.equals(topic[i].getTopicMap())) {
2832                        throw {name: 'ModelConstraintException',
2833                            message: 'scope topic belong to different topic maps'};
2834                    }
2835                }
2836            }
2837            return true;
2838        }
2839    };
2840
2841    /**
2842     * Helper functions for hashes of hashes
2843     * @ignore
2844     */
2845    IndexHelper = {
2846        getForKeys: function (hash, keys) {
2847            var i, j, tmp = new Hash(), value_hash, value_keys;
2848            for (i = 0; i < keys.length; i += 1) {
2849                value_hash = hash.get(keys[i].getId());
2850                if (value_hash) {
2851                    value_keys = value_hash.keys();
2852                    // we use a hash to store instances to avoid duplicates
2853                    for (j = 0; j < value_keys.length; j += 1) {
2854                        tmp.put(value_hash.get(value_keys[j]).getId(),
2855                                value_hash.get(value_keys[j]));
2856                    }
2857                }
2858            }
2859            return tmp.values();
2860        },
2861
2862        getConstructThemes: function (tm, hash) {
2863            var ret = [], keys = hash.keys(), i;
2864            for (i = 0; i < keys.length; i += 1) {
2865                if (keys[i] !== 'null') {
2866                    ret.push(tm.getConstructById(keys[i]));
2867                }
2868            }
2869            return ret;
2870        },
2871
2872        getConstructsByThemes: function (hash, themes, matchall) {
2873            var constructs, i, j;
2874            constructs = IndexHelper.getForKeys(hash, themes);
2875            if (!matchall) {
2876                return constructs;
2877            }
2878            // If matchall is true, we check all values for all types in {types}
2879            // It's a hack, but will do for now
2880            for (i = 0; i < constructs.length; i += 1) {
2881                for (j = 0; j < themes.length; j += 1) {
2882                    if (!ArrayHelper.contains(constructs[i].getScope(), themes[j])) {
2883                        constructs.splice(i, 1);
2884                        i -= 1;
2885                        break;
2886                    }
2887                }
2888            }
2889            return constructs;
2890        }
2891    };
2892
2893    /**
2894     * Helper functions for arrays. We don't modify the global array
2895     * object to avoid conflicts with other libraries.
2896     * @ignore
2897     */
2898    ArrayHelper = {
2899        /** Checks if arr contains elem */
2900        contains: function (arr, elem) {
2901            for (var key in arr) {
2902                if (arr.hasOwnProperty(key)) {
2903                    if (arr[key].equals(elem)) {
2904                        return true;
2905                    }
2906                }
2907            }
2908            return false;
2909        }
2910    };
2911
2912    /**
2913     * Internal function to add scope. scope may be Array, Topic or null.
2914     * @ignore
2915     * FIXME: Move to a class
2916     */
2917    addScope = function (construct, scope) {
2918        var i;
2919        if (scope && typeof scope === 'object') {
2920            if (scope instanceof Array) {
2921                for (i = 0; i < scope.length; i += 1) {
2922                    construct.addTheme(scope[i]);
2923                }
2924            } else if (scope instanceof Topic) {
2925                construct.addTheme(scope);
2926            }
2927        } else {
2928            construct.getTopicMap().addThemeEvent.fire(construct, {theme: null});
2929        }
2930    };
2931
2932    /**
2933     * Helper class for generating signatures of Topic Maps constructs.
2934     */
2935    SignatureGenerator = {
2936        makeNameValueSignature: function (name) {
2937            return name.getValue();
2938        },
2939
2940        makeNameSignature: function (name) {
2941            return SignatureGenerator.makeNameValueSignature(name) +
2942                '#' + SignatureGenerator.makeTypeSignature(name) +
2943                '#' + SignatureGenerator.makeScopeSignature(name);
2944        },
2945
2946        makeOccurrenceSignature: function (occ) {
2947            return SignatureGenerator.makeOccurrenceValueSignature(occ) +
2948                '#' + SignatureGenerator.makeTypeSignature(occ) +
2949                '#' + SignatureGenerator.makeScopeSignature(occ);
2950        },
2951
2952        makeOccurrenceValueSignature: function (occ) {
2953            return '#' + occ.getValue() + '#' +
2954                (occ.getDatatype() ? occ.getDatatype().getReference() : 'null');
2955        },
2956
2957        makeTypeSignature: function (obj) {
2958            var type = obj.getType();
2959            if (type) {
2960                return type.getId();
2961            } else {
2962                return '';
2963            }
2964        },
2965
2966        makeScopeSignature: function (scope) {
2967            var i, arr = [];
2968            for (i = 0; i < scope.length; i += 1) {
2969                arr.push(scope[i].getId());
2970            }
2971            arr.sort();
2972            return arr.join('#');
2973        },
2974
2975        makeAssociationSignature: function (ass) {
2976            var roles, i, tmp = [];
2977            roles = ass.getRoles();
2978            for (i = 0; i < roles.length; i += 1) {
2979                tmp.push(SignatureGenerator.makeRoleSignature(roles[i]));
2980            }
2981            tmp.sort();
2982       
2983            return '#' + SignatureGenerator.makeTypeSignature(ass) + '#' + tmp.join('#') +
2984                SignatureGenerator.makeScopeSignature(ass);
2985        },
2986
2987        makeRoleSignature: function (role) {
2988            return SignatureGenerator.makeTypeSignature(role) + '#' +
2989                role.getPlayer().getId();
2990        },
2991
2992        makeVariantValueSignature: function (variant) {
2993            return '#' + variant.getValue() + '#' + variant.getDatatype().getReference();
2994        },
2995
2996        makeVariantSignature: function (variant) {
2997            return SignatureGenerator.makeVariantValueSignature(variant) +
2998                '#' + SignatureGenerator.makeScopeSignature(variant);
2999        }
3000    };
3001
3002    /**
3003     * Utility class that removes duplicates according to the TMDM.
3004     */
3005    DuplicateRemover = {
3006        removeTopicMapDuplicates: function (tm) {
3007            var i, topics, associations, sig2ass = new Hash(), sig, existing;
3008            topics = tm.getTopics();
3009            for (i = 0; i < topics.length; i += 1) {
3010                DuplicateRemover.removeOccurrencesDuplicates(topics[i].getOccurrences());
3011                DuplicateRemover.removeNamesDuplicates(topics[i].getNames());
3012            }
3013            associations = tm.getAssociations();
3014            for (i = 0; i < associations.length; i += 1) {
3015                DuplicateRemover.removeAssociationDuplicates(associations[i]);
3016                sig = SignatureGenerator.makeAssociationSignature(associations[i]);
3017                if ((existing = sig2ass.get(sig))) {
3018                    MergeHelper.moveConstructCharacteristics(associations[i], existing);
3019                    MergeHelper.moveRoleCharacteristics(associations[i], existing);
3020                    associations[i].remove();
3021                } else {
3022                    sig2ass.put(sig, associations[i]);
3023                }
3024            }
3025            sig2ass.empty();
3026        },
3027
3028        removeOccurrencesDuplicates: function (occurrences) {
3029            var i, sig2occ = new Hash(), occ, sig, existing;
3030            for (i = 0; i < occurrences.length; i += 1) {
3031                occ = occurrences[i];
3032                sig = SignatureGenerator.makeOccurrenceSignature(occ);
3033                if ((existing = sig2occ.get(sig))) {
3034                    MergeHelper.moveConstructCharacteristics(occ, existing);
3035                    occ.remove();
3036                } else {
3037                    sig2occ.put(sig, occ);
3038                }
3039            }
3040            sig2occ.empty();
3041        },
3042
3043        removeNamesDuplicates: function (names) {
3044            var i, sig2names = new Hash(), name, sig, existing;
3045            for (i = 0; i < names.length; i += 1) {
3046                name = names[i];
3047                DuplicateRemover.removeVariantsDuplicates(name.getVariants());
3048                sig = SignatureGenerator.makeNameSignature(name);
3049                if ((existing = sig2names.get(sig))) {
3050                    MergeHelper.moveConstructCharacteristics(name, existing);
3051                    MergeHelper.moveVariants(name, existing);
3052                    name.remove();
3053                } else {
3054                    sig2names.put(sig, name);
3055                }
3056            }
3057            sig2names.empty();
3058        },
3059
3060        removeVariantsDuplicates: function (variants) {
3061            var i, sig2variants = new Hash(), variant, sig, existing;
3062            for (i = 0; i < variants.length; i += 1) {
3063                variant = variants[i];
3064                sig = SignatureGenerator.makeVariantSignature(variant);
3065                if ((existing = sig2variants.get(sig))) {
3066                    MergeHelper.moveConstructCharacteristics(variant, existing);
3067                    variant.remove();
3068                } else {
3069                    sig2variants.put(sig, variant);
3070                }
3071            }
3072            sig2variants.empty();
3073        },
3074
3075        removeAssociationDuplicates: function (assoc) {
3076            var i, roles = assoc.getRoles(), sig2role = new Hash(), sig, existing;
3077            for (i = 0; i < roles.length; i += 1) {
3078                sig = SignatureGenerator.makeRoleSignature(roles[i]);
3079                if ((existing = sig2role.get(sig))) {
3080                    MergeHelper.moveConstructCharacteristics(roles[i], existing);
3081                    roles[i].remove();
3082                } else {
3083                    sig2role.put(sig, roles[i]);
3084                }
3085            }
3086        }
3087    };
3088
3089    MergeHelper = {
3090        moveTypes: function (arr, target) {
3091            var i;
3092            for (i = 0; i < arr.length; i += 1) {
3093                arr[i].setType(target);
3094            }
3095        },
3096
3097        moveThemes: function (arr, source, target) {
3098            for (var i = 0; i < arr.length; i += 1) {
3099                arr[i].removeTheme(source);
3100                arr[i].addTheme(target);
3101            }
3102        },
3103
3104        moveItemIdentifiers: function (source, target) {
3105            var iis, ii;
3106            iis = source.getItemIdentifiers();
3107            while (iis.length) {
3108                ii = iis[iis.length - 1];
3109                source.removeItemIdentifier(ii);
3110                target.addItemIdentifier(ii);
3111            }
3112        },
3113
3114        /**
3115         * Moves variants from the name source to the name target
3116         */
3117        moveVariants: function (source, target) {
3118            var arr, i, tmp, tmp2, signatures;
3119            arr = target.getVariants();
3120            signatures = {};
3121            for (i = 0; i < arr.length; i += 1) {
3122                signatures[SignatureGenerator.makeVariantSignature(arr[i])] = arr[i];
3123            }
3124            arr = source.getVariants();
3125            for (i = 0; i < arr.length; i += 1) {
3126                tmp = arr[i];
3127                if ((tmp2 = signatures[SignatureGenerator.makeVariantSignature(arr[i])])) {
3128                    MergeHelper.moveItemIdentifiers(tmp, tmp2);
3129                    MergeHelper.moveReifier(tmp, tmp2);
3130                    tmp.remove();
3131                } else {
3132                    target.createVariant(tmp.getValue(), tmp.getDatatype(), tmp.getScope());
3133                }
3134            }
3135        },
3136
3137        moveReifier: function (source, target) {
3138            var r1, r2;
3139            if (source.getReifier() === null) {
3140                return;
3141            } else if (target.getReifier() === null) {
3142                target.setReifier(source.getReifier());
3143            } else {
3144                r1 = source.getReifier();
3145                r2 = target.getReifier();
3146                source.setReifier(null);
3147                r1.mergeIn(r2);
3148            }
3149        },
3150
3151        moveRoleCharacteristics: function (source, target) {
3152            var i, roles, sigs = new Hash();
3153            roles = target.getRoles();
3154            for (i = 0; i < roles.length; i += 1) {
3155                sigs.put(roles[i], SignatureGenerator.makeRoleSignature(roles[i]));
3156            }
3157            roles = source.getRoles();
3158            for (i = 0; i < roles.length; i += 1) {
3159                MergeHelper.moveItemIdentifiers(roles[i],
3160                    sigs.get(SignatureGenerator.makeRoleSignature(roles[i])));
3161                roles[i].remove();
3162            }
3163        },
3164
3165        moveConstructCharacteristics: function (source, target) {
3166            MergeHelper.moveReifier(source, target);
3167            MergeHelper.moveItemIdentifiers(source, target);
3168        }
3169    };
3170
3171    CopyHelper = {
3172        copyAssociations: function (source, target, mergeMap) {
3173        },
3174        copyItemIdentifiers: function (source, target) {
3175        },
3176        copyReifier: function (source, target, mergeMap) {
3177        },
3178        copyScope: function (source, target, mergeMap) {
3179        },
3180        copyTopicMap: function (source, target) {
3181        },
3182        copyTopic: function (sourcetm, targettm, mergeMap) {
3183        },
3184        copyType: function (source, target, mergeMap) {
3185        }
3186    };
3187
3188    TypeInstanceHelper = {
3189        convertAssociationsToType: function (tm) {
3190            var typeInstance, type, instance, associations, index, i, ass, roles;
3191            typeInstance = tm.getTopicBySubjectIdentifier(
3192                tm.createLocator(TMDM.TYPE_INSTANCE));
3193            type = tm.getTopicBySubjectIdentifier(
3194                tm.createLocator(TMDM.TYPE));
3195            instance = tm.getTopicBySubjectIdentifier(
3196                tm.createLocator(TMDM.INSTANCE));
3197            if (!typeInstance || !type || !instance) {
3198                return;
3199            }
3200            index = tm.getIndex('TypeInstanceIndex');
3201            if (!index) {
3202                return;
3203            }
3204            if (!index.isAutoUpdated()) {
3205                index.reindex();
3206            }
3207            associations = index.getAssociations(typeInstance);
3208            for (i = 0; i < associations.length; i += 1) {
3209                ass = associations[i];
3210                if (ass.getScope().length > 0 ||
3211                    ass.getReifier() !== null ||
3212                    ass.getItemIdentifiers().length > 0) {
3213                    continue;
3214                }
3215                roles = ass.getRoles();
3216                if (roles.length !== 2) {
3217                    continue;
3218                }
3219                if (roles[0].getType().equals(type) && roles[1].getType().equals(instance)) {
3220                    roles[1].getPlayer().addType(roles[0].getPlayer());
3221                } else
3222                if (roles[1].getType().equals(type) && roles[0].getType().equals(instance)) {
3223                    roles[0].getPlayer().addType(roles[1].getPlayer());
3224                } else {
3225                    continue;
3226                }
3227                ass.remove();
3228            }
3229        }
3230    };
3231
3232    // Export objects into the TM namespace
3233    return {
3234        TopicMapSystemFactory: TopicMapSystemFactory,
3235        XSD: XSD,
3236        TMDM: TMDM,
3237        Hash: Hash, // needed by CXTM export
3238        Version: Version
3239    };
3240}());
3241
3242// Pollute the global namespace
3243TopicMapSystemFactory = TM.TopicMapSystemFactory; 
3244
3245// Check if we are in a CommonJS environment (e.g. node.js)
3246if (typeof exports === 'object' && exports !== null) {
3247    exports.TopicMapSystemFactory = TopicMapSystemFactory;
3248    exports.TM = TM;
3249}
3250
3251/*jslint browser: true, devel: true, onevar: true, undef: true, nomen: false, eqeqeq: true, plusplus: true, bitwise: true,
3252  regexp: true, newcap: true, immed: true, indent: 4 */
3253/*global TM, window, DOMParser, ActiveXObject*/ 
3254
3255TM.JTM = (function () {
3256    var ReaderImpl, WriterImpl;
3257
3258    ReaderImpl = function (tm) {
3259        var that = this;
3260        this.tm = tm;
3261        this.version = null; // Keep the JTM version number
3262        this.prefixes = {};
3263        this.defaultDatatype = this.tm.createLocator(TM.XSD.string);
3264
3265        this.curieToLocator = function (loc) {
3266            var curie, prefix, pos;
3267            if (that.version === '1.1' &&
3268                loc.substr(0, 1) === '[') {
3269                if (loc.substr(loc.length - 1, 1) !== ']') {
3270                    throw {name: 'InvalidFormat',
3271                        message: 'Invaild CURIE: missing tailing bracket'};
3272                }
3273                curie = loc.substr(1, loc.length - 2);
3274                pos = curie.indexOf(':');
3275                if (pos !== -1) {
3276                    // Lookup prefix and replace with URL
3277                    prefix = curie.substr(0, pos);
3278                    if (that.prefixes[prefix]) {
3279                        loc = that.prefixes[prefix] +
3280                            curie.substr(pos + 1, curie.length - 1);
3281                        return loc;
3282                    } else {
3283                        throw {name: 'InvalidFormat',
3284                            message: 'Missing prefix declaration: ' + prefix};
3285                    }
3286                } else {
3287                    throw {name: 'InvalidFormat',
3288                        message: 'Invaild CURIE: missing colon'};
3289                }
3290            }
3291            return loc;
3292        };
3293
3294        /**
3295        * Internal function that takes a JTM-identifier string as a parameter
3296        * and returns a topic object - either an existing topic or a new topic
3297        * if the requested topic did not exist
3298        * @param {String} locator JTM-identifier
3299        * @throws {InvalidFormat} If the locator could not be parsed.
3300        */
3301        this.getTopicByReference = function (locator) {
3302            if (typeof locator === 'undefined' || locator === null) {
3303                return null;
3304            }
3305            switch (locator.substr(0, 3)) {
3306            case 'si:' :
3307                return this.tm.createTopicBySubjectIdentifier(
3308                    this.tm.createLocator(this.curieToLocator(locator.substr(3))));
3309            case 'sl:' :
3310                return this.tm.createTopicBySubjectLocator(
3311                    this.tm.createLocator(this.curieToLocator(locator.substr(3))));
3312            case 'ii:' :
3313                return this.tm.createTopicByItemIdentifier(
3314                    this.tm.createLocator(this.curieToLocator(locator.substr(3))));
3315            }
3316            throw {name: 'InvalidFormat',
3317                message: 'Invaild topic reference \'' + locator + '\''};
3318        };
3319    };
3320
3321    /**
3322    * Imports a JTM topic map or JTM fragment from a JSON-string.
3323    * name, variant, occurrence and role fragments need the optional parent
3324    * construct as a parameter.
3325    * TODO: Decide if this should be part of tmjs. Add functions for decoding/
3326    * encoding JSON if so.
3327    *
3328    * @param {String} str JSON encoded JTM
3329    * @param {Construct} [parent] Parent construct if the JTM fragment contains
3330    *        a name, variant, occurrence or role.
3331    */
3332    ReaderImpl.prototype.fromString = function (str, parent) {
3333        var obj = JSON.parse(str);
3334        return this.fromObject(obj);
3335    };
3336
3337    /**
3338    * Imports a JTM topic map or JTM fragment.
3339    * name, variant, occurrence and role fragments need the parent construct
3340    * as a parameter.
3341    *
3342    * @param {object} obj with JTM properties
3343    * @param {Construct} [parent] Parent construct if the JTM fragment contains
3344    *        a name, variant, occurrence or role.
3345    */
3346    ReaderImpl.prototype.fromObject = function (obj, parent) {
3347        var ret;
3348        parent = parent || null;
3349        if (obj.version !== '1.0' && obj.version !== '1.1') {
3350            throw {name: 'InvalidFormat',
3351                message: 'Unknown version of JTM: ' + obj.version};
3352        }
3353        this.version = obj.version;
3354        if (obj.version === '1.1' && obj.prefixes) {
3355            this.prefixes = obj.prefixes;
3356            // Check if xsd is defined and if it is valid:
3357            if (obj.prefixes && obj.prefixes.xsd &&
3358                obj.prefixes.xsd !== 'http://www.w3.org/2001/XMLSchema#') {
3359                throw {name: 'InvalidFormat',
3360                    message: 'The XSD prefix MUST have the value "http://www.w3.org/2001/XMLSchema#"'};
3361            }
3362        } else if (obj.prefixes) {
3363            throw {name: 'InvalidFormat',
3364                message: 'Prefixes are invalid in JTM 1.0: ' + obj.version};
3365        }
3366        if (!this.prefixes.xsd) {
3367            this.prefixes.xsd = 'http://www.w3.org/2001/XMLSchema#';
3368        }
3369        if (!obj.item_type) {
3370            throw {name: 'InvalidFormat',
3371                message: 'Missing item_type'};
3372        }
3373        switch (obj.item_type.toLowerCase()) {
3374        case "topicmap":
3375            ret = this.parseTopicMap(obj);
3376            break;
3377        case "topic":
3378            ret = this.parseTopic(obj);
3379            break;
3380        case "name":
3381            ret = this.parseName(parent, obj);
3382            break;
3383        case "variant":
3384            ret = this.parseVariant(parent, obj);
3385            break;
3386        case "occurrence":
3387            ret = this.parseOccurrence(parent, obj);
3388            break;
3389        case "association":
3390            ret = this.parseAssociation(obj);
3391            break;
3392        case "role":
3393            ret = this.parseRole(parent, obj);
3394            break;
3395        default:
3396            throw {name: 'InvalidFormat',
3397                message: 'Unknown item_type property'};
3398        }
3399        return ret;
3400    };
3401
3402    /**
3403     * FIXME: Work in progress. We have to specify *how* the information
3404     * item can be created.
3405     *
3406     * Internal function that parses a parent field. From the JTM spec:
3407     * "The value of the parent member is an array of item identifiers,
3408     * each prefixed by "ii:". For occurrences and names the parent array
3409     * may as well contain subject identifiers prefixed by "si:" and
3410     * subject locators prefixed by "sl:".
3411     */
3412    ReaderImpl.prototype.parseParentAsTopic = function (obj, allowTopic) {
3413        var parent = null, tmp, i;
3414        if (!obj.parent) {
3415            parent = this.tm.createTopic();
3416        } else if (!(obj.parent instanceof Array) || obj.parent.length === 0) {
3417            throw {name: 'InvalidFormat',
3418                message: 'Missing parent topic reference in occurrence'};
3419        }
3420        if (obj.parent) {
3421            for (i = 0; i < obj.parent.length; i += 1) {
3422                tmp = this.getTopicByReference(obj.parent[i]);
3423                if (!parent) {
3424                    parent = tmp;
3425                } else {
3426                    parent.mergeIn(tmp);
3427                }
3428            }
3429        }
3430        return parent;
3431    };
3432
3433    ReaderImpl.prototype.parseTopicMap = function (obj) {
3434        var i, len, arr;
3435        this.parseItemIdentifiers(this.tm, obj.item_identifiers);
3436        this.parseReifier(this.tm, obj.reifier);
3437        if (obj.topics && typeof obj.topics === 'object' && obj.topics instanceof Array) {
3438            arr = obj.topics;
3439            len = arr.length;
3440            for (i = 0; i < len; i += 1) {
3441                this.parseTopic(arr[i]);
3442            }
3443            arr = null;
3444        }
3445        if (obj.associations && typeof obj.associations === 'object' &&
3446            obj.associations instanceof Array) {
3447            arr = obj.associations;
3448            len = arr.length;
3449            for (i = 0; i < len; i += 1) {
3450                this.parseAssociation(arr[i]);
3451            }
3452            arr = null;
3453        }
3454        this.tm.sanitize(); // remove duplicates and convert type-instance associations to types
3455        return true;
3456    };
3457
3458    ReaderImpl.prototype.parseTopic = function (obj) {
3459        var that = this, topic = null, parseIdentifier, arr, i, identifier, type;
3460        parseIdentifier = function (tm, topic, arr, getFunc, createFunc, addFunc) {
3461            var i, len, tmp;
3462            if (arr && typeof arr === 'object' && arr instanceof Array) {
3463                len = arr.length;
3464                for (i = 0; i < len; i += 1) {
3465                    identifier = decodeURI(that.curieToLocator(arr[i]));
3466                    if (!topic) {
3467                        topic = createFunc.apply(tm, [tm.createLocator(identifier)]);
3468                    } else {
3469                        tmp = getFunc.apply(tm, [tm.createLocator(identifier)]);
3470                        if (tmp && tmp.isTopic() && !topic.equals(tmp)) {
3471                            topic.mergeIn(tmp);
3472                        } else if (!(tmp && tmp.isTopic() && topic.equals(tmp))) {
3473                            topic[addFunc](tm.createLocator(identifier));
3474                        }
3475                    }
3476                }
3477            }
3478            return topic;
3479        };
3480        topic = parseIdentifier(this.tm, topic, obj.subject_identifiers,
3481            this.tm.getTopicBySubjectIdentifier,
3482            this.tm.createTopicBySubjectIdentifier, 'addSubjectIdentifier');
3483        topic = parseIdentifier(this.tm, topic, obj.subject_locators,
3484            this.tm.getTopicBySubjectLocator,
3485            this.tm.createTopicBySubjectLocator, 'addSubjectLocator');
3486        topic = parseIdentifier(this.tm, topic, obj.item_identifiers,
3487            this.tm.getConstructByItemIdentifier,
3488            this.tm.createTopicByItemIdentifier, 'addItemIdentifier');
3489
3490        if ((arr = obj.instance_of) && this.version === '1.1') {
3491            for (i = 0; i < arr.length; i += 1) {
3492                type = this.getTopicByReference(arr[i]);
3493                topic.addType(type);
3494            }
3495        } else if (obj.instance_of && this.version === '1.0') {
3496            throw {name: 'InvalidFormat',
3497                message: 'instance_of is invalid in JTM 1.0'};
3498        }
3499
3500        arr = obj.names;
3501        if (arr && typeof arr === 'object' && arr instanceof Array) {
3502            for (i = 0; i < arr.length; i += 1) {
3503                this.parseName(topic, arr[i]);
3504            }
3505        }
3506        arr = obj.occurrences;
3507        if (arr && typeof arr === 'object' && arr instanceof Array) {
3508            for (i = 0; i < arr.length; i += 1) {
3509                this.parseOccurrence(topic, arr[i]);
3510            }
3511        }
3512    };
3513
3514    ReaderImpl.prototype.parseName = function (parent, obj) {
3515        var name, type, scope, arr, i;
3516        if (!parent) {
3517            parent = this.parseParentAsTopic(obj);
3518        }
3519        scope = this.parseScope(obj.scope);
3520        type = this.getTopicByReference(obj.type);
3521        name = parent.createName(obj.value, type, scope);
3522        arr = obj.variants;
3523        if (arr && typeof arr === 'object' && arr instanceof Array) {
3524            for (i = 0; i < arr.length; i += 1) {
3525                this.parseVariant(name, arr[i]);
3526            }
3527        }
3528        this.parseItemIdentifiers(name, obj.item_identifiers);
3529        this.parseReifier(name, obj.reifier);
3530    };
3531
3532    ReaderImpl.prototype.parseVariant = function (parent, obj) {
3533        var variant, scope;
3534        scope = this.parseScope(obj.scope);
3535        variant = parent.createVariant(obj.value, 
3536            obj.datatype ?
3537                this.tm.createLocator(this.curieToLocator(obj.datatype)) :
3538                    this.defaultDatatype, scope);
3539        this.parseItemIdentifiers(variant, obj.item_identifiers);
3540        this.parseReifier(variant, obj.reifier);
3541    };
3542
3543    ReaderImpl.prototype.parseOccurrence = function (parent, obj) {
3544        var occurrence, type, scope;
3545        if (!parent) {
3546            parent = this.parseParentAsTopic(obj);
3547        }
3548        scope = this.parseScope(obj.scope);
3549        type = this.getTopicByReference(obj.type);
3550        occurrence = parent.createOccurrence(type, obj.value,
3551            obj.datatype ?
3552                this.tm.createLocator(this.curieToLocator(obj.datatype)) :
3553                    this.defaultDatatype, scope);
3554        this.parseItemIdentifiers(occurrence, obj.item_identifiers);
3555        this.parseReifier(occurrence, obj.reifier);
3556    };
3557
3558    ReaderImpl.prototype.parseAssociation = function (obj) {
3559        var association, type, scope, arr, i;
3560        scope = this.parseScope(obj.scope);
3561        type = this.getTopicByReference(obj.type);
3562        association = this.tm.createAssociation(type, scope);
3563        arr = obj.roles;
3564        if (arr && typeof arr === 'object' && arr instanceof Array) {
3565            if (arr.length === 0) {
3566                throw {name: 'InvalidFormat',
3567                    message: 'Association needs roles'};
3568            }
3569            for (i = 0; i < arr.length; i += 1) {
3570                this.parseRole(association, arr[i]);
3571            }
3572        } else {
3573            throw {name: 'InvalidFormat',
3574                message: 'Association needs roles'};
3575        }
3576        this.parseItemIdentifiers(association, obj.item_identifiers);
3577        this.parseReifier(association, obj.reifier);
3578    };
3579
3580    ReaderImpl.prototype.parseRole = function (parent, obj) {
3581        var role, type, player;
3582        type = this.getTopicByReference(obj.type);
3583        player = this.getTopicByReference(obj.player);
3584        role = parent.createRole(type, player);
3585        this.parseItemIdentifiers(role, obj.item_identifiers);
3586        this.parseReifier(role, obj.reifier);
3587    };
3588
3589    ReaderImpl.prototype.parseScope = function (arr) {
3590        var i, scope = [];
3591        if (arr && typeof arr === 'object' && arr instanceof Array) {
3592            for (i = 0; i < arr.length; i += 1) {
3593                scope.push(this.getTopicByReference(arr[i]));
3594            }
3595        }
3596        return scope;
3597    };
3598
3599
3600    ReaderImpl.prototype.parseItemIdentifiers = function (construct, arr) {
3601        var i, tm, identifier;
3602        tm = construct.getTopicMap();
3603        if (arr && typeof arr === 'object' && arr instanceof Array) {
3604            for (i = 0; i < arr.length; i += 1) {
3605                identifier = this.curieToLocator(arr[i]);
3606                if (!tm.getConstructByItemIdentifier(tm.createLocator(identifier))) {
3607                    construct.addItemIdentifier(tm.createLocator(identifier));
3608                }
3609            }
3610        }
3611    };
3612
3613    ReaderImpl.prototype.parseReifier = function (construct, reifier) {
3614        var reifierTopic = this.getTopicByReference(reifier);
3615        if (reifierTopic && reifierTopic.getReified() === null || !reifierTopic) {
3616            construct.setReifier(reifierTopic);
3617        } // else: Ignore the case that reifierTopic reifies another item
3618    };
3619
3620    /**
3621    * @class Exports topic maps constructs as JTM 1.0 JavaScript objects.
3622    * See http://www.cerny-online.com/jtm/1.0/ for the JSON Topic Maps specification.
3623    * JSON 1.1 is described at http://www.cerny-online.com/jtm/1.1/
3624    * @param {String} version Version number of the JTM export. Valid values are '1.0'
3625    *                 and '1.1'. Version 1.1 produces more compact files. The default
3626    *                 value is '1.0', but this may change in the future.
3627    */
3628    WriterImpl = function (version) {
3629        var that = this, referenceToCURIEorURI;
3630        this.defaultDatatype = TM.XSD.string;
3631        this.prefixes = new TM.Hash();
3632        this.version = version || '1.0';
3633
3634        referenceToCURIEorURI = function (reference) {
3635            var key, keys, i, value;
3636            if (that.version === '1.0') {
3637                return reference;
3638            }
3639            // TODO Sort keys after descending value length - longest first
3640            // to find the best prefix
3641            keys = that.prefixes.keys();
3642            for (i = 0; i < keys.length; i += 1) {
3643                key = keys[i];
3644                value = that.prefixes.get(key);
3645                if (reference.substring(0, value.length) ===  value) {
3646                    return '[' + key + ':' +
3647                        reference.substr(value.length) + ']';
3648                }
3649            }
3650            return reference;
3651        }; 
3652
3653        /**
3654        * Sets prefixes for JTM 1.1 export. prefixes is an object with the
3655        * prefix as key and its corresponding reference as value.
3656        */
3657        this.setPrefixes = function (prefixes) {
3658            var key;
3659            for (key in prefixes) {
3660                if (prefixes.hasOwnProperty(key)) {
3661                    this.prefixes.put(key, prefixes[key]);
3662                }
3663            }
3664        };
3665
3666        /**
3667         * Generates a JTM reference based on the topics subject identifier,
3668         * subject locator or item identifier (whatever is set, tested in this
3669         * order).
3670         * @returns {string} Representing the topic t, e.g.
3671         *     "si:http://psi.topicmaps.org/iso13250/model/type
3672         */
3673        this.getTopicReference = function (t) {
3674            var arr;
3675            arr = t.getSubjectIdentifiers();
3676            if (arr.length > 0) {
3677                return 'si:' + referenceToCURIEorURI(arr[0].getReference());
3678            }
3679            arr = t.getSubjectLocators();
3680            if (arr.length > 0) {
3681                return 'sl:' + referenceToCURIEorURI(arr[0].getReference());
3682            }
3683            arr = t.getItemIdentifiers();
3684            if (arr.length > 0) {
3685                return 'ii:' + referenceToCURIEorURI(arr[0].getReference());
3686            }
3687            // ModelConstraintExeption: TMDM says that t MUST have on of these
3688        };
3689
3690        this.exportIdentifiers = function (obj, arr, attr) {
3691            var i, len = arr.length;
3692            if (len > 0) {
3693                obj[attr] = [];
3694                for (i = 0; i < len; i += 1) {
3695                    obj[attr].push(referenceToCURIEorURI(arr[i].getReference()));
3696                }
3697            }
3698       
3699        }; 
3700
3701        this.exportScope = function (obj, construct) {
3702            var i, arr = construct.getScope();
3703            if (arr.length > 0) {
3704                obj.scope = [];
3705                for (i = 0; i < arr.length; i += 1) {
3706                    obj.scope.push(that.getTopicReference(arr[i]));
3707                }
3708            }
3709        };
3710
3711        this.exportParent = function (obj, construct) {
3712            var parent = construct.getParent();
3713            that.exportIdentifiers(obj, parent.getItemIdentifiers(), 'parent');
3714        };
3715
3716        this.exportTopicMap = function (m) {
3717            var arr, i, len, obj;
3718            obj = {
3719                topics: [],
3720                associations: []
3721            };
3722            arr = m.getTopics();
3723            len = arr.length;
3724            for (i = 0; i < len; i += 1) {
3725                obj.topics.push(that.exportTopic(arr[i]));
3726            }
3727            arr = m.getAssociations();
3728            len = arr.length;
3729            for (i = 0; i < len; i += 1) {
3730                obj.associations.push(that.exportAssociation(arr[i]));
3731            }
3732            return obj;
3733        };
3734
3735        this.exportTopic = function (t) {
3736            var arr, i, len, obj;
3737            obj = {};
3738            that.exportIdentifiers(obj, t.getSubjectIdentifiers(), 'subject_identifiers');
3739            that.exportIdentifiers(obj, t.getSubjectLocators(), 'subject_locators');
3740            that.exportIdentifiers(obj, t.getItemIdentifiers(), 'item_identifiers');
3741            arr = t.getNames();
3742            len = arr.length;
3743            if (len > 0) {
3744                obj.names = [];
3745                for (i = 0; i < len; i += 1) {
3746                    obj.names.push(that.exportName(arr[i]));
3747                }
3748            }
3749            arr = t.getOccurrences();
3750            len = arr.length;
3751            if (len > 0) {
3752                obj.occurrences = [];
3753                for (i = 0; i < len; i += 1) {
3754                    obj.occurrences.push(that.exportOccurrence(arr[i]));
3755                }
3756            }
3757            arr = t.getTypes();
3758            len = arr.length;
3759            if (len > 0) {
3760                obj.instance_of = [];
3761                for (i = 0; i < len; i += 1) {
3762                    obj.instance_of.push(that.getTopicReference(arr[i]));
3763                }
3764            }
3765            return obj;
3766        };
3767
3768        this.exportName = function (name) {
3769            var arr, i, len, obj, tmp;
3770            obj = {
3771                'value': name.getValue()
3772            };
3773            tmp = name.getType();
3774            if (tmp) {
3775                obj.type = that.getTopicReference(tmp);
3776            }
3777            tmp = name.getReifier();
3778            if (tmp) {
3779                obj.reifier = that.getTopicReference(tmp);
3780            }
3781           
3782            that.exportIdentifiers(obj, name.getItemIdentifiers(), 'item_identifiers');
3783            that.exportScope(obj, name);
3784            arr = name.getVariants();
3785            len = arr.length;
3786            if (len > 0) {
3787                obj.variants = [];
3788                for (i = 0; i < len; i += 1) {
3789                    obj.variants.push(that.exportVariant(arr[i]));
3790                }
3791            }
3792            return obj;
3793        };
3794
3795        this.exportVariant = function (variant) {
3796            var obj, tmp;
3797            obj = {
3798                'value': variant.getValue()
3799            };
3800            tmp = variant.getDatatype();
3801            if (tmp && tmp !== variant.getTopicMap().createLocator(that.defaultDatatype)) {
3802                obj.datatype = referenceToCURIEorURI(tmp.getReference());
3803            }
3804            tmp = variant.getReifier();
3805            if (tmp) {
3806                obj.reifier = that.getTopicReference(tmp);
3807            }
3808           
3809            that.exportIdentifiers(obj, variant.getItemIdentifiers(), 'item_identifiers');
3810            that.exportScope(obj, variant);
3811        };
3812
3813        this.exportOccurrence = function (occ) {
3814            var obj, tmp;
3815            obj = {
3816                value: occ.getValue(),
3817                type: that.getTopicReference(occ.getType())
3818            };
3819            tmp = occ.getDatatype();
3820            if (tmp && tmp !== occ.getTopicMap().createLocator(that.defaultDatatype)) {
3821                obj.datatype = referenceToCURIEorURI(tmp.getReference());
3822            }
3823            tmp = occ.getReifier();
3824            if (tmp) {
3825                obj.reifier = that.getTopicReference(tmp);
3826            }
3827           
3828            that.exportIdentifiers(obj, occ.getItemIdentifiers(), 'item_identifiers');
3829            that.exportScope(obj, occ);
3830            return obj;
3831        };
3832
3833        this.exportAssociation = function (association) {
3834            var arr, i, obj, tmp;
3835            obj = {
3836                type: that.getTopicReference(association.getType()),
3837                roles: []
3838            };
3839            tmp = association.getReifier();
3840            if (tmp) {
3841                obj.reifier = that.getTopicReference(tmp);
3842            }
3843            that.exportIdentifiers(obj, association.getItemIdentifiers(), 'item_identifiers');
3844            that.exportScope(obj, association);
3845            arr = association.getRoles();
3846            for (i = 0; i < arr.length; i += 1) {
3847                obj.roles.push(that.exportRole(arr[i]));
3848            }
3849            return obj;
3850        };
3851
3852        this.exportRole = function (role) {
3853            var obj, tmp;
3854            obj = {
3855                player: that.getTopicReference(role.getPlayer()),
3856                type: that.getTopicReference(role.getType())
3857            };
3858            tmp = role.getReifier();
3859            if (tmp) {
3860                obj.reifier = that.getTopicReference(tmp);
3861            }
3862            that.exportIdentifiers(obj, role.getItemIdentifiers(), 'item_identifiers');
3863            return obj;
3864        };
3865    };
3866
3867    /**
3868    * Returns a JTM JavaScript object representation of construct.
3869    * @param {Construct} construct The topic map construct to be exported. Can be
3870    * TopicMap, Topic, Occurrence, Name, Variant, Association or Role.
3871    * @param {boolean} [includeParent] If true the optional JTM element 'parent' is
3872    * included. Refers to the parent via its item identifier.  If undefined or false,
3873    * the parent element is dropped.
3874    */
3875    WriterImpl.prototype.toObject = function (construct, includeParent) {
3876        var obj, tm, keys, i;
3877        includeParent = includeParent || false;
3878        tm = construct.getTopicMap();
3879
3880        if (construct.isTopicMap()) {
3881            obj = this.exportTopicMap(construct);
3882            obj.item_type = 'topicmap';
3883        } else if (construct.isRole()) {
3884            obj = this.exportRole(construct);
3885            obj.item_type = 'role';
3886        } else if (construct.isTopic()) {
3887            obj = this.exportTopic(construct);
3888            obj.item_type = 'topic';
3889        } else if (construct.isAssociation()) {
3890            obj = this.exportAssociation(construct);
3891            obj.item_type = 'association';
3892        } else if (construct.isOccurrence()) {
3893            obj = this.exportOccurrence(construct);
3894            obj.item_type = 'occurrence';
3895        } else if (construct.isName()) {
3896            obj = this.exportName(construct);
3897            obj.item_type = 'name';
3898        } else if (construct.isVariant()) {
3899            obj = this.exportVariant(construct);
3900            obj.item_type = 'variant';
3901        }
3902        obj.version = this.version;
3903        if (this.version === '1.1' && this.prefixes) {
3904            if (this.prefixes.size()) {
3905                keys = this.prefixes.keys();
3906                obj.prefixes = {};
3907                for (i = 0; i < keys.length; i += 1) {
3908                    obj.prefixes[keys[i]] = this.prefixes.get(keys[i]);
3909                }
3910            }
3911        }
3912        if (!construct.isTopic() && construct.getReifier()) {
3913            obj.reifier = this.getTopicReference(construct.getReifier());
3914        }
3915        if (includeParent && !construct.isTopicMap()) {
3916            this.exportParent(obj, construct);
3917        }
3918        return obj;
3919    };
3920
3921    return {
3922        Reader: ReaderImpl,
3923        Writer: WriterImpl
3924    };
3925}());
Note: See TracBrowser for help on using the repository browser.