source: trunk/src/anaToMia/GDL_Widgets/lib/tm.js

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

gdl-frontend: Widgets: added the GWT modules GDL_Tmengine and GDL_TopicMaps_Model to this project

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   
1363        name = new Name(this, value, type);
1364        addScope(name, scope);
1365        this.names.push(name);
1366        return name;
1367    };
1368   
1369    // TODO: @datatype is optional in TMAPI, value may be string or locator.
1370    // Creates an Occurrence for this topic with the specified type, IRI value, and
1371    // scope.
1372    // createOccurrence(Topic type, java.lang.String value, Locator datatype,
1373    // java.util.Collection<Topic> scope)
1374    // Creates an Occurrence for this topic with the specified type, string value,
1375    // and scope.
1376    Topic.prototype.createOccurrence = function (type, value, datatype, scope) {
1377        var occ;
1378        SameTopicMapHelper.assertBelongsTo(this.parnt, type);
1379        SameTopicMapHelper.assertBelongsTo(this.parnt, scope);
1380   
1381        occ = new Occurrence(this, type, value, datatype);
1382        this.parnt.addOccurrenceEvent.fire(occ, {type: type, value: value});
1383        addScope(occ, scope);
1384        this.occurrences.push(occ);
1385        return occ;
1386    };
1387   
1388    /**
1389     * Returns the Names of this topic where the name type is type.
1390     *type is optional.
1391     */
1392    Topic.prototype.getNames = function (type) {
1393        var ret = [], i;
1394
1395        for (i = 0; i < this.names.length; i += 1) {
1396            if (type && this.names[i].getType().equals(type)) {
1397                ret.push(this.names[i]);
1398            } else if (!type) {
1399                ret.push(this.names[i]);
1400            }
1401        }
1402        return ret;
1403    };
1404   
1405    /**
1406     * Returns the Occurrences of this topic where the occurrence type is type. type
1407     * is optional.
1408     * @throws {IllegalArgumentException} If type is null.
1409     */
1410    Topic.prototype.getOccurrences = function (type) {
1411        var ret = [], i;
1412        if (type === null) {
1413            throw {name: 'IllegalArgumentException',
1414                message: 'Topic.getOccurrences cannot be called without type'};
1415        }
1416        for (i = 0; i < this.occurrences.length; i += 1) {
1417            if (type && this.occurrences[i].getType().equals(type)) {
1418                ret.push(this.occurrences[i]);
1419            } else if (!type) {
1420                ret.push(this.occurrences[i]);
1421            }
1422        }
1423        return ret;
1424    };
1425   
1426    Topic.prototype._removeOccurrence = function (occ) {
1427        // remove this from TopicMap.topics
1428        for (var i = 0; i < this.occurrences.length; i += 1) {
1429            if (this.occurrences[i].equals(occ)) {
1430                this.occurrences.splice(i, 1);
1431                break;
1432            }
1433        }
1434        this.getTopicMap()._removeOccurrence(occ);
1435    };
1436   
1437    // Returns the Construct which is reified by this topic.
1438    Topic.prototype.getReified = function (type) {
1439        return this.reified;
1440    };
1441   
1442    Topic.prototype._setReified = function (reified) {
1443        this.reified = reified;
1444    };
1445   
1446    /**
1447     * Returns the roles played by this topic.
1448     * Returns the roles played by this topic where the role type is type.
1449     * assocType is optional
1450     * @throws {IllegalArgumentException} If type or assocType is null.
1451     */
1452    Topic.prototype.getRolesPlayed = function (type, assocType) {
1453        if (type === null) {
1454            throw {name: 'IllegalArgumentException',
1455                message: 'Topic.getRolesPlayed cannot be called without type'};
1456        }
1457        if (assocType === null) {
1458            throw {name: 'IllegalArgumentException',
1459                message: 'Topic.getRolesPlayed cannot be called with assocType===null'};
1460        }
1461        var ret = [], i;
1462        for (i = 0; i < this.rolesPlayed.length; i += 1) {
1463            if (!type) {
1464                ret.push(this.rolesPlayed[i]);
1465            } else if (this.rolesPlayed[i].getType().equals(type)) {
1466                if (assocType &&
1467                    this.rolesPlayed[i].getParent().getType().equals(assocType) ||
1468                    !assocType) {
1469                    ret.push(this.rolesPlayed[i]);
1470                }
1471            }
1472        }
1473        return ret;
1474    };
1475   
1476    // @private Registers role as a role played
1477    // TODO: Rename to _addRolePlayed
1478    Topic.prototype.addRolePlayed = function (role) {
1479        this.rolesPlayed.push(role);
1480    };
1481   
1482    // TODO: Rename to _removeRolePlayed
1483    Topic.prototype.removeRolePlayed = function (role) {
1484        for (var i = 0; i < this.rolesPlayed.length; i += 1) {
1485            if (this.rolesPlayed[i].id === role.id) {
1486                this.rolesPlayed.splice(i, 1);
1487            }
1488        }
1489    };
1490   
1491    /**
1492     * Returns the subject identifiers assigned to this topic.
1493     */
1494    Topic.prototype.getSubjectIdentifiers = function () {
1495        return this.subjectIdentifiers;
1496    };
1497   
1498    /**
1499     * Returns the subject locators assigned to this topic.
1500     */
1501    Topic.prototype.getSubjectLocators = function () {
1502        return this.subjectLocators;
1503    };
1504   
1505    /**
1506     * Returns the types of which this topic is an instance of.
1507     */
1508    Topic.prototype.getTypes = function () {
1509        return this.types;
1510    };
1511   
1512    /**
1513     * Merges another topic into this topic.
1514     * @throws {ModelConstraintException} If the topics reify different
1515     * information items.
1516     * @returns {Topic} The topic itself (for chaining support)
1517     */
1518    Topic.prototype.mergeIn = function (other) {
1519        var arr, i, tmp, tmp2, signatures, tiidx, sidx;
1520        if (this.equals(other)) {
1521            return true;
1522        }
1523
1524        SameTopicMapHelper.assertBelongsTo(this.getTopicMap(), other);
1525        if (this.getReified() && other.getReified() &&
1526            !this.getReified().equals(other.getReified())) {
1527            throw {name: 'ModelConstraintException',
1528                message: 'The topics reify different Topic Maps constructs and cannot be merged!'
1529            };
1530        }
1531
1532        if (!this.getReified() && other.getReified()) {
1533            tmp = other.getReified();
1534            tmp.setReifier(this);
1535        }
1536
1537        // Change all constructs that use other as type
1538        tiidx = this.parnt.typeInstanceIndex;
1539        MergeHelper.moveTypes(tiidx.getOccurrences(other), this);
1540        MergeHelper.moveTypes(tiidx.getNames(other), this);
1541        MergeHelper.moveTypes(tiidx.getAssociations(other), this);
1542        MergeHelper.moveTypes(tiidx.getRoles(other), this);
1543
1544        // Change all topics that have other as type
1545        arr = tiidx.getTopics(other);
1546        for (i = 0; i < arr.length; i += 1) {
1547            arr[i].removeType(other);
1548            arr[i].addType(this);
1549        }
1550
1551        // Change all constructs that use other as theme
1552        sidx = this.parnt.scopedIndex;
1553        MergeHelper.moveThemes(sidx.getAssociations(other), other, this);
1554        MergeHelper.moveThemes(sidx.getOccurrences(other), other, this);
1555        MergeHelper.moveThemes(sidx.getNames(other), other, this);
1556        MergeHelper.moveThemes(sidx.getVariants(other), other, this);
1557
1558        MergeHelper.moveItemIdentifiers(other, this);
1559
1560        arr = other.getSubjectLocators();
1561        while (arr.length) {
1562            tmp = arr[arr.length - 1];
1563            other.removeSubjectLocator(tmp);
1564            this.addSubjectLocator(tmp);
1565        }
1566
1567        arr = other.getSubjectIdentifiers();
1568        while (arr.length) {
1569            tmp = arr[arr.length - 1];
1570            other.removeSubjectIdentifier(tmp);
1571            this.addSubjectIdentifier(tmp);
1572        }
1573
1574        arr = other.getTypes();
1575        while (arr.length) {
1576            tmp = arr[arr.length - 1];
1577            other.removeType(tmp);
1578            this.addType(tmp);
1579        }
1580
1581        // merge roles played
1582        arr = this.getRolesPlayed();
1583        signatures = {};
1584        for (i = 0; i < arr.length; i += 1) {
1585            tmp2 = arr[i].getParent();
1586            signatures[SignatureGenerator.makeAssociationSignature(tmp2)] = tmp2;
1587        }
1588        arr = other.getRolesPlayed();
1589        for (i = 0; i < arr.length; i += 1) {
1590            tmp = arr[i];
1591            tmp.setPlayer(this);
1592            if ((tmp2 = signatures[SignatureGenerator.makeAssociationSignature(tmp.getParent())])) {
1593                MergeHelper.moveItemIdentifiers(tmp.getParent(), tmp2);
1594                MergeHelper.moveReifier(tmp.getParent(), tmp2);
1595                tmp.getParent().remove();
1596            }
1597        }
1598
1599        // merge names
1600        arr = this.getNames();
1601        signatures = {};
1602        for (i = 0; i < arr.length; i += 1) {
1603            signatures[SignatureGenerator.makeNameSignature(arr[i])] = arr[i];
1604        }
1605        arr = other.getNames();
1606        for (i = 0; i < arr.length; i += 1) {
1607            tmp = arr[i];
1608            if ((tmp2 = signatures[SignatureGenerator.makeNameSignature(arr[i])])) {
1609                MergeHelper.moveItemIdentifiers(tmp, tmp2);
1610                MergeHelper.moveReifier(tmp, tmp2);
1611                MergeHelper.moveVariants(tmp, tmp2);
1612                tmp.remove();
1613            } else {
1614                tmp2 = this.createName(tmp.getValue(), tmp.getType(), tmp.getScope());
1615                MergeHelper.moveVariants(tmp, tmp2);
1616            }
1617        }
1618
1619        // merge occurrences
1620        arr = this.getOccurrences();
1621        signatures = {};
1622        for (i = 0; i < arr.length; i += 1) {
1623            signatures[SignatureGenerator.makeOccurrenceSignature(arr[i])] = arr[i];
1624        }
1625        arr = other.getOccurrences();
1626        for (i = 0; i < arr.length; i += 1) {
1627            tmp = arr[i];
1628            if ((tmp2 = signatures[SignatureGenerator.makeOccurrenceSignature(arr[i])])) {
1629                MergeHelper.moveItemIdentifiers(tmp, tmp2);
1630                MergeHelper.moveReifier(tmp, tmp2);
1631                tmp.remove();
1632            } else {
1633                tmp2 = this.createOccurrence(tmp.getType(), tmp.getValue(),
1634                    tmp.getDatatype(), tmp.getScope());
1635                MergeHelper.moveReifier(tmp, tmp2);
1636            }
1637        }
1638
1639        other.remove();
1640        return this;
1641    };
1642   
1643    /**
1644     * Removes this topic from the containing TopicMap instance.
1645     * @throws {TopicInUseException} If the topics is used as reifier,
1646     * occurrence type, name type, association type, role type, topic type,
1647     * association theme, occurrence theme, name theme, variant theme,
1648     * or if it is used as a role player.
1649     */
1650    Topic.prototype.remove = function () {
1651        var tiidx = this.parnt.typeInstanceIndex,
1652            sidx = this.parnt.scopedIndex;
1653        if (this.getReified() ||
1654            tiidx.getOccurrences(this).length ||
1655            tiidx.getNames(this).length ||
1656            tiidx.getAssociations(this).length ||
1657            tiidx.getRoles(this).length ||
1658            tiidx.getTopics(this).length ||
1659            sidx.getAssociations(this).length ||
1660            sidx.getOccurrences(this).length ||
1661            sidx.getNames(this).length ||
1662            sidx.getVariants(this).length ||
1663            this.getRolesPlayed().length) {
1664            throw {name: 'TopicInUseException',
1665                message: '', reporter: this};
1666        }
1667        this.parnt._removeTopic(this);
1668        this.parnt._id2construct.remove(this.id);
1669        this.parnt.removeTopicEvent.fire(this);
1670        this.id = null;
1671        return this.parnt;
1672    };
1673   
1674    /**
1675     * Removes a subject identifier from this topic.
1676     * @returns {Topic} The topic itself (for chaining support)
1677     */
1678    Topic.prototype.removeSubjectIdentifier = function (subjectIdentifier) {
1679        for (var i = 0; i < this.subjectIdentifiers.length; i += 1) {
1680            if (this.subjectIdentifiers[i].getReference() ===
1681                subjectIdentifier.getReference()) {
1682                this.subjectIdentifiers.splice(i, 1);
1683                break;
1684            }
1685        }
1686        this.parnt._sl2topic.remove(subjectIdentifier.getReference());
1687        return this;
1688    };
1689   
1690    /**
1691     * Removes a subject locator from this topic.
1692     * @returns {Topic} The topic itself (for chaining support)
1693     */
1694    Topic.prototype.removeSubjectLocator = function (subjectLocator) {
1695        for (var i = 0; i < this.subjectLocators.length; i += 1) {
1696            if (this.subjectLocators[i].getReference() ===
1697                subjectLocator.getReference()) {
1698                this.subjectLocators.splice(i, 1);
1699                break;
1700            }
1701        }
1702        this.parnt._sl2topic.remove(subjectLocator.getReference());
1703        return this;
1704    };
1705   
1706    /**
1707     * Removes a type from this topic.
1708     * @returns {Topic} The topic itself (for chaining support)
1709     */
1710    Topic.prototype.removeType = function (type) {
1711        for (var i = 0; i < this.types.length; i += 1) {
1712            if (this.types[i].equals(type)) {
1713                this.types.splice(i, 1);
1714                this.parnt.removeTypeEvent.fire(this, {type: type});
1715                break;
1716            }
1717        }
1718    };
1719   
1720    Topic.prototype._removeName = function (name) {
1721        for (var i = 0; i < this.names.length; i += 1) {
1722            if (this.names[i].equals(name)) {
1723                this.names.splice(i, 1);
1724                break;
1725            }
1726        }
1727        this.getTopicMap()._removeName(name);
1728    };
1729   
1730    // --------------------------------------------------------------------------
1731    Occurrence = function (parnt, type, value, datatype) {
1732        this.itemIdentifiers = [];
1733        this.parnt = parnt;
1734        this.type = type;
1735        this.value = value;
1736        this.datatype = datatype ? datatype : this.getTopicMap().createLocator(XSD.string);
1737        this.scope = [];
1738        this.reifier = null;
1739        this.id = this.getTopicMap()._getConstructId();
1740        this.getTopicMap()._id2construct.put(this.id, this);
1741    };
1742   
1743    // mergein Typed, DatatypeAware, Reifiable, Scoped, Construct
1744    Occurrence.swiss(Typed, 'getType', 'setType');
1745    Occurrence.swiss(DatatypeAware, 'decimalValue', 'floatValue',
1746        'getDatatype', 'getValue', 'integerValue', 'locatorValue', 'longValue',
1747        'setValue');
1748    Occurrence.swiss(Reifiable, 'getReifier', 'setReifier');
1749    Occurrence.swiss(Scoped, 'addTheme', 'getScope', 'removeTheme');
1750    Occurrence.swiss(Construct, 'addItemIdentifier', 'equals', 'getId',
1751        'getItemIdentifiers', 'getParent', 'getTopicMap', 'hashCode', 'remove',
1752        'removeItemIdentifier', 'isTopic', 'isAssociation', 'isRole',
1753        'isOccurrence', 'isName', 'isVariant', 'isTopicMap');
1754   
1755    Occurrence.prototype.isOccurrence = function () {
1756        return true;
1757    };
1758   
1759    Occurrence.prototype.getTopicMap = function () {
1760        return this.parnt.getParent();
1761    };
1762   
1763    Occurrence.prototype.remove = function () {
1764        var i;
1765        for (i = 0; i < this.scope.length; i += 1) {
1766            this.parnt.parnt.removeThemeEvent.fire(this, {theme: this.scope[i]});
1767        }
1768        this.parnt.parnt.removeOccurrenceEvent.fire(this);
1769        this.parnt._removeOccurrence(this);
1770        this.id = null;
1771        return this.parnt;
1772    };
1773   
1774    Name = function (parnt, value, type) {
1775        this.itemIdentifiers = [];
1776        this.parnt = parnt;
1777        this.value = value;
1778        this.scope = [];
1779        this.id = this.getTopicMap()._getConstructId();
1780        this.type = type ||
1781            parnt.parnt.createTopicBySubjectIdentifier(
1782                parnt.parnt.createLocator('http://psi.topicmaps.org/iso13250/model/topic-name'));
1783        this.reifier = null;
1784        this.variants = [];
1785        this.getTopicMap()._id2construct.put(this.id, this);
1786        this.parnt.parnt.addNameEvent.fire(this, {type: this.type, value: value});
1787    };
1788   
1789    // mergein Typed, DatatypeAware, Reifiable, Scoped, Construct
1790    Name.swiss(Typed, 'getType', 'setType');
1791    Name.swiss(Reifiable, 'getReifier', 'setReifier');
1792    Name.swiss(Scoped, 'addTheme', 'getScope', 'removeTheme');
1793    Name.swiss(Construct, 'addItemIdentifier', 'equals', 'getId',
1794        'getItemIdentifiers', 'getParent', 'getTopicMap', 'hashCode', 'remove',
1795        'removeItemIdentifier', 'isTopic', 'isAssociation', 'isRole',
1796        'isOccurrence', 'isName', 'isVariant', 'isTopicMap');
1797   
1798    Name.prototype.isName = function () {
1799        return true;
1800    };
1801   
1802    Name.prototype.getTopicMap = function () {
1803        return this.parnt.parnt;
1804    };
1805   
1806    /**
1807     * @throws {ModelConstraintException} If scope is null.
1808     */
1809    Name.prototype.createVariant = function (value, datatype, scope) {
1810        var scope_length = 0, i, variant;
1811        if (typeof scope === 'undefined' || scope === null) {
1812            throw {name: 'ModelConstraintException',
1813            message: 'Creation of a variant with a null scope is not allowed'};
1814        }
1815        if (scope && typeof scope === 'object') {
1816            if (scope instanceof Array) {
1817                scope_length = scope.length;
1818            } else if (scope instanceof Topic) {
1819                scope_length = 1;
1820            }
1821        }
1822       /*
1823        TODO: Compare scope of Name and Variant
1824        if (scope_length <= this.getScope().length) {
1825            // check if the variants scope contains more scoping topics
1826            throw {name: 'ModelConstraintException',
1827            message: 'The variant would be in the same scope as the parent'};
1828        }*/
1829        variant = new Variant(this, value, datatype);
1830        addScope(variant, scope);
1831        for (i = 0; i < this.scope.length; i += 1) {
1832            this.getTopicMap().addThemeEvent.fire(variant,
1833                {theme: this.scope[i]});
1834        }
1835        this.variants.push(variant);
1836        return variant;
1837    };
1838   
1839    /**
1840     * @throws {ModelConstraintException} If value is null.
1841     * @returns {Name} The name itself (for chaining support)
1842     */
1843    Name.prototype.setValue = function (value) {
1844        if (!value) {
1845            throw {name: 'ModelConstraintException',
1846            message: 'Name.setValue(null) is not allowed'};
1847        }
1848        this.value = value;
1849        return this;
1850    };
1851   
1852    Name.prototype.getValue = function (value) {
1853        return this.value;
1854    };
1855   
1856    Name.prototype.remove = function () {
1857        var i;
1858        for (i = 0; i < this.scope.length; i += 1) {
1859            this.parnt.parnt.removeThemeEvent.fire(this, {theme: this.scope[i]});
1860        }
1861        this.parnt.parnt.removeNameEvent.fire(this);
1862        this.parnt._removeName(this);
1863        this.id = null;
1864        return this.parnt;
1865    };
1866   
1867    Name.prototype._removeVariant = function (variant) {
1868        for (var i = 0; i < this.variants.length; i += 1) {
1869            if (this.variants[i].equals(variant)) {
1870                this.variants.splice(i, 1);
1871                break;
1872            }
1873        }
1874        this.getTopicMap()._removeVariant(variant);
1875    };
1876
1877    Name.prototype.getVariants = function () {
1878        return this.variants;
1879    };
1880   
1881    /**
1882     * @throws {ModelConstraintException} If value or datatype is null.
1883     */
1884    Variant = function (parnt, value, datatype) {
1885        if (value === null) {
1886            throw {name: 'ModelConstraintException',
1887                message: 'Creation of a variant with null value is not allowed'};
1888        }
1889        if (datatype === null) {
1890            throw {name: 'ModelConstraintException',
1891            message: 'Creation of a variant with datatype == null is not allowed'};
1892        }
1893        this.itemIdentifiers = [];
1894        this.scope = [];
1895        this.parnt = parnt;
1896        if (typeof value === 'object' && value instanceof Locator) {
1897            this.datatype = this.getTopicMap().createLocator('http://www.w3.org/2001/XMLSchema#anyURI');
1898        } else {
1899            this.datatype = 
1900                this.getTopicMap().createLocator(XSD.string);
1901        }
1902        this.datatype = datatype;
1903        this.reifier = null;
1904        this.value = value;
1905        this.id = this.getTopicMap()._getConstructId();
1906        this.getTopicMap()._id2construct.put(this.id, this);
1907    };
1908   
1909    Variant.swiss(Reifiable, 'getReifier', 'setReifier');
1910    Variant.swiss(Scoped, 'addTheme', 'getScope', 'removeTheme');
1911    Variant.swiss(Construct, 'addItemIdentifier', 'equals', 'getId',
1912        'getItemIdentifiers', 'getParent', 'getTopicMap', 'hashCode', 'remove',
1913        'removeItemIdentifier', 'isTopic', 'isAssociation', 'isRole',
1914        'isOccurrence', 'isName', 'isVariant', 'isTopicMap');
1915    Variant.swiss(DatatypeAware, 'decimalValue', 'floatValue', 'getDatatype',
1916        'getValue', 'integerValue', 'locatorValue', 'longValue', 'setValue');
1917   
1918    Variant.prototype.isVariant = function () {
1919        return true;
1920    };
1921   
1922    Variant.prototype.getTopicMap = function () {
1923        return this.getParent().getParent().getParent();
1924    };
1925   
1926    Variant.prototype.remove = function () {
1927        var i;
1928        for (i = 0; i < this.scope.length; i += 1) {
1929            this.getTopicMap().removeThemeEvent.fire(this, {theme: this.scope[i]});
1930        }
1931        this.getParent()._removeVariant(this);
1932        this.id = null;
1933        return this.parnt;
1934    };
1935   
1936
1937    Role = function (parnt, type, player) {
1938        this.itemIdentifiers = [];
1939        this.parnt = parnt;
1940        this.type = type;
1941        this.player = player;
1942        this.id = this.getTopicMap()._getConstructId();
1943        this.reifier = null;
1944        this.getTopicMap()._id2construct.put(this.id, this);
1945    };
1946   
1947    Role.swiss(Typed, 'getType', 'setType');
1948    Role.swiss(Reifiable, 'getReifier', 'setReifier');
1949    Role.swiss(Construct, 'addItemIdentifier', 'equals', 'getId',
1950        'getItemIdentifiers', 'getParent', 'getTopicMap', 'hashCode', 'remove',
1951        'removeItemIdentifier', 'isTopic', 'isAssociation', 'isRole',
1952        'isOccurrence', 'isName', 'isVariant', 'isTopicMap');
1953   
1954    Role.prototype.isRole = function () {
1955        return true;
1956    };
1957   
1958    Role.prototype.getTopicMap = function () {
1959        return this.getParent().getParent();
1960    };
1961   
1962    Role.prototype.remove = function () {
1963        var parnt = this.parnt;
1964        this.parnt.parnt.removeRoleEvent.fire(this);
1965        this.parnt._removeRole(this);
1966        this.itemIdentifiers = null;
1967        this.parnt = null;
1968        this.type = null;
1969        this.player = null;
1970        this.reifier = null;
1971        this.id = null;
1972        return parnt;
1973    };
1974   
1975    Role.prototype.getPlayer = function () {
1976        return this.player;
1977    };
1978   
1979    /**
1980     * @throws {ModelConstraintException} If player is null.
1981     * @returns {Role} The role itself (for chaining support)
1982     */
1983    Role.prototype.setPlayer = function (player) {
1984        if (!player) {
1985            throw {name: 'ModelConstraintException',
1986            message: 'player i Role.setPlayer cannot be null'};
1987        }
1988        SameTopicMapHelper.assertBelongsTo(this.parnt.parnt, player);
1989        if (this.player.equals(player)) {
1990            return;
1991        }
1992        this.player.removeRolePlayed(this);
1993        player.addRolePlayed(this);
1994        this.player = player;
1995        return this;
1996    };
1997   
1998    Association = function (par) {
1999        this.itemIdentifiers = [];
2000        this.parnt = par;
2001        this.id = this.getTopicMap()._getConstructId();
2002        this.getTopicMap()._id2construct.put(this.id, this);
2003        this.roles = [];
2004        this.scope = [];
2005        this.type = null;
2006        this.reifier = null;
2007    };
2008   
2009    Association.swiss(Typed, 'getType', 'setType');
2010    Association.swiss(Reifiable, 'getReifier', 'setReifier');
2011    Association.swiss(Scoped, 'addTheme', 'getScope', 'removeTheme');
2012    Association.swiss(Construct, 'addItemIdentifier', 'equals', 'getId',
2013        'getItemIdentifiers', 'getParent', 'getTopicMap', 'hashCode', 'remove',
2014        'removeItemIdentifier', 'isTopic', 'isAssociation', 'isRole',
2015        'isOccurrence', 'isName', 'isVariant', 'isTopicMap');
2016   
2017    Association.prototype.isAssociation = function () {
2018        return true;
2019    };
2020   
2021    Association.prototype.getTopicMap = function () {
2022        return this.parnt;
2023    };
2024   
2025    /**
2026     * Creates a new Role representing a role in this association.
2027     * @throws {ModelConstraintException} If type or player is null.
2028     */
2029    Association.prototype.createRole = function (type, player) {
2030        if (!type) {
2031            throw {name: 'ModelConstraintException',
2032                message: 'type i Role.createPlayer cannot be null'};
2033        }
2034        if (!player) {
2035            throw {name: 'ModelConstraintException',
2036                message: 'player i Role.createRole cannot be null'};
2037        }
2038        SameTopicMapHelper.assertBelongsTo(this.parnt, type);
2039        SameTopicMapHelper.assertBelongsTo(this.parnt, player);
2040        var role = new Role(this, type, player);
2041        player.addRolePlayed(role);
2042        this.roles.push(role);
2043        this.parnt.addRoleEvent.fire(role, {type: type, player: player});
2044        return role;
2045    };
2046   
2047    Association.prototype._removeRole = function (role) {
2048        for (var i = 0; i < this.roles.length; i += 1) {
2049            if (role.id === this.roles[i].id) {
2050                this.roles.splice(i, 1);
2051                break;
2052            }
2053        }
2054        role.getPlayer().removeRolePlayed(role);
2055        this.getTopicMap()._removeRole(role);
2056    };
2057   
2058    Association.prototype.remove = function () {
2059        var i;
2060        for (i = 0; i < this.scope.length; i += 1) {
2061            this.parnt.removeThemeEvent.fire(this, {theme: this.scope[i]});
2062        }
2063        this.parnt.removeAssociationEvent.fire(this);
2064        while (this.roles.length) {
2065            this.roles[0].remove();
2066        }
2067        this.id = null;
2068        this.roles = null;
2069        this.parnt._removeAssociation(this);
2070        this.getTopicMap()._ii2construct.remove(this.id);
2071        this.item_identifiers = null;
2072        this.scope = null;
2073        this.type = null;
2074        this.reifier = null;
2075        return this.parnt;
2076    };
2077   
2078    /**
2079     * Returns the roles participating in this association, or, if type
2080     * is given, all roles with the specified type.
2081     * @throws {IllegalArgumentException} If type is null.
2082     */
2083    Association.prototype.getRoles = function (type) {
2084        if (type === null) {
2085            throw {name: 'IllegalArgumentException',
2086                message: 'Topic.getRoles cannot be called with type null'};
2087        }
2088        if (!type) {
2089            return this.roles;
2090        }
2091        var ret = [], i; 
2092        for (i = 0; i < this.roles.length; i += 1) {
2093            if (this.roles[i].getType().equals(type)) {
2094                ret.push(this.roles[i]);
2095            }
2096        }
2097        return ret;
2098    };
2099
2100    /**
2101     * Returns the role types participating in this association.
2102     */
2103    Association.prototype.getRoleTypes = function () {
2104        // Create a hash with the object ids as keys to avoid duplicates
2105        var types = {}, typearr = [], i, t;
2106        for (i = 0; i < this.roles.length; i += 1) {
2107            types[this.roles[i].getType().getId()] =
2108                this.roles[i].getType();
2109        }
2110        for (t in types) {
2111            if (types.hasOwnProperty(t)) {
2112                typearr.push(types[t]);
2113            }
2114        }
2115        return typearr;
2116    };
2117
2118    // ------ ----------------------------------------------------------------
2119    /** @class */
2120    Index = function () {
2121        this.opened = false;
2122    };
2123
2124    /**
2125     * Close the index.
2126     */
2127    Index.prototype.close = function () {
2128        return;
2129    };
2130
2131    /**
2132     * Indicates whether the index is updated automatically.
2133     * @returns {boolean}
2134     */
2135    Index.prototype.isAutoUpdated = function () {
2136        return true;
2137    };
2138
2139    /** Indicates if the index is open.
2140     * @returns {boolean} true if index is already opened, false otherwise.
2141     */
2142    Index.prototype.isOpen = function () {
2143        return this.opened;
2144    };
2145
2146    /**
2147     * Opens the index. This method must be invoked before using any other
2148     * method (aside from isOpen()) exported by this interface or derived
2149     * interfaces.
2150     */
2151    Index.prototype.open = function () {
2152        this.opened = true;
2153    };
2154
2155    /**
2156     * Synchronizes the index with data in the topic map.
2157     */
2158    Index.prototype.reindex = function () {
2159        return;
2160    };
2161
2162    /**
2163     * Creates a new instance of TypeInstanceIndex.
2164     * @class Implementation of the TypeInstanceIndex interface.
2165     */
2166    TypeInstanceIndex = function (tm) {
2167        var eventHandler, that = this;
2168        this.tm = tm;
2169        // we use hash tables of hash tables for our index
2170        this.type2topics = new Hash();
2171        this.type2associations = new Hash();
2172        this.type2roles = new Hash();
2173        this.type2occurrences = new Hash();
2174        this.type2names = new Hash();
2175        this.type2variants = new Hash();
2176        this.opened = false;
2177
2178        eventHandler = function (eventtype, source, obj) {
2179            var existing, untyped, types, type, i;
2180            switch (eventtype) {
2181            case EventType.ADD_ASSOCIATION:
2182                break;
2183            case EventType.ADD_NAME:
2184                existing = that.type2names.get(obj.type.getId());
2185                if (typeof existing === 'undefined') {
2186                    existing = new Hash();
2187                }
2188                existing.put(source.getId(), source);
2189                that.type2names.put(obj.type.getId(), existing);
2190                break;
2191            case EventType.ADD_OCCURRENCE:
2192                existing = that.type2occurrences.get(obj.type.getId());
2193                if (typeof existing === 'undefined') {
2194                    existing = new Hash();
2195                }
2196                existing.put(source.getId(), source);
2197                that.type2occurrences.put(obj.type.getId(), existing);
2198                break;
2199            case EventType.ADD_ROLE:
2200                existing = that.type2roles.get(obj.type.getId());
2201                if (typeof existing === 'undefined') {
2202                    existing = new Hash();
2203                }
2204                existing.put(source.getId(), source);
2205                that.type2roles.put(obj.type.getId(), existing);
2206                break;
2207            case EventType.ADD_TOPIC:
2208                existing = that.type2topics.get('null');
2209                if (typeof existing === 'undefined') {
2210                    existing = new Hash();
2211                }
2212                existing.put(source.getId(), source);
2213                that.type2topics.put('null', existing);
2214                break;
2215            case EventType.ADD_TYPE:
2216                // check if source exists with type null, remove it there
2217                untyped = that.type2topics.get('null');
2218                if (untyped && untyped.get(source.getId())) {
2219                    untyped.remove(source.getId());
2220                    that.type2topics.put('null', untyped);
2221                }
2222                   
2223                existing = that.type2topics.get(obj.type.getId());
2224                if (typeof existing === 'undefined') {
2225                    existing = new Hash();
2226                }
2227                existing.put(source.getId(), source);
2228                that.type2topics.put(obj.type.getId(), existing);
2229                break;
2230            case EventType.REMOVE_ASSOCIATION:
2231                type = source.getType();
2232                if (!type) {
2233                    break;
2234                }
2235                existing = that.type2associations.get(type.getId());
2236                for (i = 0; i < existing.length; i += 1) {
2237                    if (existing[i].equals(source)) {
2238                        existing.splice(i, 1);
2239                        break;
2240                    }   
2241                }
2242                if (existing.length > 0) {
2243                    that.type2associations.put(type.getId(),
2244                            existing);
2245                } else {
2246                    that.type2associations.remove(type.getId());
2247                }
2248                break;
2249            case EventType.REMOVE_NAME:
2250                type = source.getType();
2251                existing = that.type2names.get(type.getId());
2252                existing.remove(source.getId());
2253                if (existing.length > 0) {
2254                    that.type2names.put(type.getId(), existing);
2255                } else {
2256                    that.type2names.remove(type.getId());
2257                }
2258                break;
2259            case EventType.REMOVE_OCCURRENCE:
2260                type = source.getType();
2261                existing = that.type2occurrences.get(type.getId());
2262                existing.remove(source.getId());
2263                if (existing.length > 0) {
2264                    that.type2occurrences.put(type.getId(), existing);
2265                } else {
2266                    that.type2occurrences.remove(type.getId());
2267                }
2268                break;
2269            case EventType.REMOVE_ROLE:
2270                type = source.getType();
2271                existing = that.type2roles.get(type.getId());
2272                existing.remove(source.getId());
2273                if (existing.length > 0) {
2274                    that.type2roles.put(type.getId(), existing);
2275                } else {
2276                    that.type2roles.remove(type.getId());
2277                }
2278                break;
2279            case EventType.REMOVE_TOPIC:
2280                // two cases:
2281                //  topic has types
2282                types = source.getTypes();
2283                for (i = 0; i < types.length; i += 1) {
2284                    existing = that.type2topics.get(types[i].getId());
2285                    existing.remove(source.getId());
2286                    if (!existing.size()) {
2287                        that.type2topics.remove(types[i].getId());
2288                    }
2289                }
2290                // topic used as type
2291                that.type2topics.remove(source.getId());
2292                that.type2associations.remove(source.getId());
2293                that.type2roles.remove(source.getId());
2294                that.type2occurrences.remove(source.getId());
2295                that.type2variants.remove(source.getId());
2296                break;
2297            case EventType.REMOVE_TYPE:
2298                existing = that.type2topics.get(obj.type.getId());
2299                existing.remove(source.getId());
2300                if (!existing.size()) {
2301                    that.type2topics.remove(obj.type.getId());
2302                }
2303                if (source.getTypes().length === 0) {
2304                    untyped = that.type2topics.get('null');
2305                    if (typeof untyped === 'undefined') {
2306                        untyped = new Hash();
2307                    }
2308                    untyped.put(source.getId(), source);
2309                }
2310                break;
2311            case EventType.SET_TYPE:
2312                if (source.isAssociation()) {
2313                    // remove source from type2associations(obj.old.getId());
2314                    if (obj.old) {
2315                        existing = that.type2associations.get(obj.old.getId());
2316                        for (i = 0; i < existing.length; i += 1) {
2317                            if (existing[i].equals(source)) {
2318                                existing.splice(i, 1);
2319                                break;
2320                            }   
2321                        }
2322                        if (existing.length > 0) {
2323                            that.type2associations.put(obj.old.getId(),
2324                                    existing);
2325                        } else {
2326                            that.type2associations.remove(obj.old.getId());
2327                        }
2328                    }
2329                    existing = that.type2associations.get(obj.type.getId());
2330                    if (typeof existing === 'undefined') {
2331                        existing = [];
2332                    }
2333                    existing.push(source);
2334                    that.type2associations.put(obj.type.getId(), existing);
2335                } else if (source.isName()) {
2336                    existing = that.type2names.get(obj.old.getId());
2337                    if (existing) {
2338                        existing.remove(source.getId());
2339                        if (existing.length > 0) {
2340                            that.type2names.put(obj.old.getId(), existing);
2341                        } else {
2342                            that.type2names.remove(obj.old.getId());
2343                        }
2344                    }
2345                    existing = that.type2names.get(obj.type.getId());
2346                    if (typeof existing === 'undefined') {
2347                        existing = new Hash();
2348                    }
2349                    existing.put(source.getId(), source);
2350                    that.type2names.put(obj.type.getId(), existing);
2351                } else if (source.isOccurrence()) {
2352                    existing = that.type2occurrences.get(obj.old.getId());
2353                    if (existing) {
2354                        existing.remove(source.getId());
2355                        if (existing.length > 0) {
2356                            that.type2occurrences.put(obj.old.getId(), existing);
2357                        } else {
2358                            that.type2occurrences.remove(obj.old.getId());
2359                        }
2360                    }
2361                    existing = that.type2occurrences.get(obj.type.getId());
2362                    if (typeof existing === 'undefined') {
2363                        existing = new Hash();
2364                    }
2365                    existing.put(source.getId(), source);
2366                    that.type2occurrences.put(obj.type.getId(), existing);
2367                } else if (source.isRole()) {
2368                    existing = that.type2roles.get(obj.old.getId());
2369                    if (existing) {
2370                        existing.remove(source.getId());
2371                        if (existing.length > 0) {
2372                            that.type2roles.put(obj.old.getId(), existing);
2373                        } else {
2374                            that.type2roles.remove(obj.old.getId());
2375                        }
2376                    }
2377                    existing = that.type2roles.get(obj.type.getId());
2378                    if (typeof existing === 'undefined') {
2379                        existing = new Hash();
2380                    }
2381                    existing.put(source.getId(), source);
2382                    that.type2roles.put(obj.type.getId(), existing);
2383                }
2384                break;
2385            }
2386        };
2387        tm.addAssociationEvent.registerHandler(eventHandler);
2388        tm.addNameEvent.registerHandler(eventHandler);
2389        tm.addOccurrenceEvent.registerHandler(eventHandler);
2390        tm.addRoleEvent.registerHandler(eventHandler);
2391        tm.addTopicEvent.registerHandler(eventHandler);
2392        tm.addTypeEvent.registerHandler(eventHandler);
2393        tm.removeAssociationEvent.registerHandler(eventHandler);
2394        tm.removeNameEvent.registerHandler(eventHandler);
2395        tm.removeOccurrenceEvent.registerHandler(eventHandler);
2396        tm.removeRoleEvent.registerHandler(eventHandler);
2397        tm.removeTopicEvent.registerHandler(eventHandler);
2398        tm.removeTypeEvent.registerHandler(eventHandler);
2399        tm.setTypeEvent.registerHandler(eventHandler);
2400    };
2401
2402    TypeInstanceIndex.swiss(Index, 'close', 'isAutoUpdated',
2403        'isOpen', 'open', 'reindex');
2404         
2405    /**
2406     * Returns the associations in the topic map whose type property equals type.
2407     *
2408     * @param {Topic} type
2409     * @returns {Array} A list of all associations in the topic map with the given type.
2410     */
2411    TypeInstanceIndex.prototype.getAssociations = function (type) {
2412        var ret = this.type2associations.get(type.getId());
2413        if (!ret) {
2414            return [];
2415        }
2416        return ret;
2417    };
2418
2419    /**
2420     * Returns the topics in the topic map used in the type property of Associations.
2421     *
2422     * @returns {Array} A list of all topics that are used as an association type.
2423     */
2424    TypeInstanceIndex.prototype.getAssociationTypes = function () {
2425        var ret = [], keys = this.type2associations.keys(), i;
2426        for (i = 0; i < keys.length; i += 1) {
2427            ret.push(this.tm.getConstructById(keys[i]));
2428        }
2429        return ret;
2430    };
2431
2432    /**
2433     * Returns the topic names in the topic map whose type property equals type.
2434     *
2435     * @param {Topic} type
2436     * @returns {Array}
2437     */
2438    TypeInstanceIndex.prototype.getNames = function (type) {
2439        var ret = this.type2names.get(type.getId());
2440        if (!ret) {
2441            return [];
2442        }
2443        return ret.values();
2444    };
2445
2446    /**
2447     * Returns the topics in the topic map used in the type property of Names.
2448     *
2449     * @returns {Array} An array of topic types. Note that the array contains
2450     * a reference to the actual topics, not copies of them.
2451     */
2452    TypeInstanceIndex.prototype.getNameTypes = function () {
2453        var ret = [], keys = this.type2names.keys(), i;
2454        for (i = 0; i < keys.length; i += 1) {
2455            ret.push(this.tm.getConstructById(keys[i]));
2456        }
2457        return ret;
2458    };
2459
2460    /**
2461     * Returns the occurrences in the topic map whose type property equals type.
2462     *
2463     * @returns {Array}
2464     */
2465    TypeInstanceIndex.prototype.getOccurrences = function (type) {
2466        var ret = this.type2occurrences.get(type.getId());
2467        if (!ret) {
2468            return [];
2469        }
2470        return ret.values();
2471    };
2472
2473    /**
2474     * Returns the topics in the topic map used in the type property of
2475     * Occurrences.
2476     *
2477     * @returns {Array} An array of topic types. Note that the array contains
2478     * a reference to the actual topics, not copies of them.
2479     */
2480    TypeInstanceIndex.prototype.getOccurrenceTypes = function () {
2481        var ret = [], keys = this.type2occurrences.keys(), i;
2482        for (i = 0; i < keys.length; i += 1) {
2483            ret.push(this.tm.getConstructById(keys[i]));
2484        }
2485        return ret;
2486    };
2487
2488
2489    /**
2490     * Returns the roles in the topic map whose type property equals type.
2491     *
2492     * @returns {Array}
2493     */
2494    TypeInstanceIndex.prototype.getRoles = function (type) {
2495        var ret = this.type2roles.get(type.getId());
2496        if (!ret) {
2497            return [];
2498        }
2499        return ret.values();
2500    };
2501
2502    /**
2503     * Returns the topics in the topic map used in the type property of Roles.
2504     *
2505     * @returns {Array} An array of topic types. Note that the array contains
2506     * a reference to the actual topics, not copies of them.
2507     */
2508    TypeInstanceIndex.prototype.getRoleTypes = function () {
2509        var ret = [], keys = this.type2roles.keys(), i;
2510        for (i = 0; i < keys.length; i += 1) {
2511            ret.push(this.tm.getConstructById(keys[i]));
2512        }
2513        return ret;
2514    };
2515
2516    /**
2517     * Returns the topics which are an instance of the specified type.
2518     */
2519    TypeInstanceIndex.prototype.getTopics = function (type) {
2520        var ret = this.type2topics.get((type ? type.getId() : 'null'));
2521        if (!ret) {
2522            return [];
2523        }
2524        return ret.values();
2525    };
2526
2527    /**
2528     * Returns the topics which are an instance of the specified types.
2529     * If matchall is true only topics that have all of the listed types
2530     * are returned.
2531     * @returns {Array} A list of Topic objects
2532     */
2533    TypeInstanceIndex.prototype.getTopicsByTypes = function (types, matchall) {
2534        var instances, i, j;
2535        instances = IndexHelper.getForKeys(this.type2topics, types);
2536        if (!matchall) {
2537            return instances;
2538        }
2539        // If matchall is true, we check all values for all types in {types}
2540        // It's a hack, but will do for now
2541        for (i = 0; i < instances.length; i += 1) {
2542            for (j = 0; j < types.length; j += 1) {
2543                if (!ArrayHelper.contains(instances[i].getTypes(), types[j])) {
2544                    instances.splice(i, 1);
2545                    i -= 1;
2546                    break;
2547                }
2548            }
2549        }
2550        return instances;
2551    };
2552
2553    /**
2554     * Returns the topics in topic map which are used as type in an
2555     * "type-instance"-relationship.
2556     */
2557    TypeInstanceIndex.prototype.getTopicTypes = function () {
2558        var ret = [], keys = this.type2topics.keys(), i;
2559        for (i = 0; i < keys.length; i += 1) {
2560            if (keys[i] !== 'null') {
2561                ret.push(this.tm.getConstructById(keys[i]));
2562            }
2563        }
2564        return ret;
2565    };
2566
2567    TypeInstanceIndex.prototype.close = function () {
2568        return;
2569    };
2570
2571
2572    /**
2573     * Index for Scoped statements and their scope. This index provides access
2574     * to Associations, Occurrences, Names, and Variants by their scope
2575     * property and to Topics which are used as theme in a scope.
2576     */
2577    ScopedIndex = function (tm) {
2578        var that = this, eventHandler;
2579        this.tm = tm;
2580        this.theme2associations = new Hash();
2581        this.theme2names = new Hash();
2582        this.theme2occurrences = new Hash();
2583        this.theme2variants = new Hash();
2584        eventHandler = function (eventtype, source, obj) {
2585            var existing, key, unscoped, remove_from_index, add_to_index;
2586            add_to_index = function (hash, source, obj) {
2587                key = (obj.theme ? obj.theme.getId() : 'null');
2588
2589                // check if source exists with theme null, remove it there
2590                // this is the case iff source now has one scoping topic
2591                if (source.getScope().length === 1) {
2592                    unscoped = hash.get('null');
2593                    if (unscoped && unscoped.get(source.getId())) {
2594                        unscoped.remove(source.getId());
2595                        hash.put('null', unscoped);
2596                    }
2597                }
2598                existing = hash.get(key);
2599                if (typeof existing === 'undefined') {
2600                    existing = new Hash();
2601                }
2602                existing.put(source.getId(), source); 
2603                hash.put(key, existing);
2604            };
2605            remove_from_index = function (hash, source, obj) {
2606                key = obj.theme.getId();
2607                existing = hash.get(key);
2608                if (typeof existing !== 'undefined') {
2609                    existing.remove(source.getId()); 
2610                    if (!existing.size()) {
2611                        hash.remove(key);
2612                    }
2613                }
2614            };
2615            switch (eventtype) {
2616            case EventType.ADD_THEME:
2617                if (source.isAssociation()) {
2618                    add_to_index(that.theme2associations, source, obj);
2619                } else if (source.isName()) {
2620                    add_to_index(that.theme2names, source, obj);
2621                } else if (source.isOccurrence()) {
2622                    add_to_index(that.theme2occurrences, source, obj);
2623                } else if (source.isVariant()) {
2624                    add_to_index(that.theme2variants, source, obj);
2625                }
2626                break;
2627            case EventType.REMOVE_THEME:
2628                if (source.isAssociation()) {
2629                    remove_from_index(that.theme2associations, source, obj);
2630                } else if (source.isName()) {
2631                    remove_from_index(that.theme2names, source, obj);
2632                } else if (source.isOccurrence()) {
2633                    remove_from_index(that.theme2occurrences, source, obj);
2634                } else if (source.isVariant()) {
2635                    remove_from_index(that.theme2variants, source, obj);
2636                }
2637                break;
2638            }
2639        };
2640        tm.addThemeEvent.registerHandler(eventHandler);
2641        tm.removeThemeEvent.registerHandler(eventHandler);
2642    };
2643
2644    ScopedIndex.swiss(Index, 'close', 'isAutoUpdated',
2645        'isOpen', 'open', 'reindex');
2646
2647    ScopedIndex.prototype.close = function () {
2648        return;
2649    };
2650
2651    /**
2652     * Returns the Associations in the topic map whose scope property contains
2653     * the specified theme. The return value may be empty but must never be null.
2654     * @param theme can be array or {Topic}
2655     * @param [matchall] boolean
2656     */
2657    ScopedIndex.prototype.getAssociations = function (theme) {
2658        var ret = this.theme2associations.get((theme ? theme.getId() : 'null'));
2659        if (!ret) {
2660            return [];
2661        }
2662        return ret.values();
2663    };
2664
2665    /**
2666     * Returns the Associations in the topic map whose scope property contains
2667     * the specified theme. The return value may be empty but must never be null.
2668     * @param theme can be array or {Topic}
2669     * @param [matchall] boolean
2670     * @throws {IllegalArgumentException} If themes is null.
2671     */
2672    ScopedIndex.prototype.getAssociationsByThemes = function (themes, matchall) {
2673        if (themes === null) {
2674            throw {name: 'IllegalArgumentException',
2675                message: 'ScopedIndex.getAssociationsByThemes cannot be called without themes'};
2676        }
2677        return IndexHelper.getConstructsByThemes(this.theme2associations,
2678            themes, matchall);
2679    };
2680
2681    /**
2682     * Returns the topics in the topic map used in the scope property of
2683     * Associations.
2684     */
2685    ScopedIndex.prototype.getAssociationThemes = function () {
2686        return IndexHelper.getConstructThemes(this.tm, this.theme2associations);
2687    };
2688
2689    /**
2690     * Returns the Names in the topic map whose scope property contains the
2691     * specified theme.
2692     */
2693    ScopedIndex.prototype.getNames = function (theme) {
2694        var ret = this.theme2names.get((theme ? theme.getId() : 'null'));
2695        if (!ret) {
2696            return [];
2697        }
2698        return ret.values();
2699    };
2700
2701    /**
2702     * Returns the Names in the topic map whose scope property equals one of
2703     * those themes at least.
2704     * @throws {IllegalArgumentException} If themes is null.
2705     */
2706    ScopedIndex.prototype.getNamesByThemes = function (themes, matchall) {
2707        if (themes === null) {
2708            throw {name: 'IllegalArgumentException',
2709                message: 'ScopedIndex.getNamesByThemes cannot be called without themes'};
2710        }
2711        return IndexHelper.getConstructsByThemes(this.theme2names,
2712            themes, matchall);
2713    };
2714
2715    /**
2716     * Returns the topics in the topic map used in the scope property of Names.
2717     */
2718    ScopedIndex.prototype.getNameThemes = function () {
2719        return IndexHelper.getConstructThemes(this.tm, this.theme2names);
2720    };
2721
2722    /**
2723     * Returns the Occurrences in the topic map whose scope property contains the
2724     * specified theme.
2725     */
2726    ScopedIndex.prototype.getOccurrences = function (theme) {
2727        var ret = this.theme2occurrences.get((theme ? theme.getId() : 'null'));
2728        if (!ret) {
2729            return [];
2730        }
2731        return ret.values();
2732    };
2733
2734    /**
2735     * Returns the Occurrences in the topic map whose scope property equals one
2736     * of those themes at least.
2737     * @throws {IllegalArgumentException} If themes is null.
2738     */
2739    ScopedIndex.prototype.getOccurrencesByThemes = function (themes, matchall) {
2740        if (themes === null) {
2741            throw {name: 'IllegalArgumentException',
2742                message: 'ScopedIndex.getOccurrencesByThemes cannot be called without themes'};
2743        }
2744        return IndexHelper.getConstructsByThemes(this.theme2occurrences,
2745            themes, matchall);
2746    };
2747
2748    /**
2749     * Returns the topics in the topic map used in the scope property of
2750     * Occurrences.
2751     */
2752    ScopedIndex.prototype.getOccurrenceThemes = function () {
2753        return IndexHelper.getConstructThemes(this.tm, this.theme2occurrences);
2754    };
2755
2756    /**
2757     * Returns the Variants in the topic map whose scope property contains the
2758     * specified theme. The return value may be empty but must never be null.
2759     * @param {Topic} The Topic which must be part of the scope. This must not be
2760     * null.
2761     * @returns {Array} An array of Variants.
2762     * @throws {IllegalArgumentException} If theme is null.
2763     */
2764    ScopedIndex.prototype.getVariants = function (theme) {
2765        if (theme === null) {
2766            throw {name: 'IllegalArgumentException',
2767                message: 'ScopedIndex.getVariants cannot be called without themes'};
2768        }
2769        var ret = this.theme2variants.get((theme ? theme.getId() : 'null'));
2770        if (!ret) {
2771            return [];
2772        }
2773        return ret.values();
2774    };
2775
2776    /**
2777     * Returns the Variants in the topic map whose scope property equals one of
2778     * those themes at least.
2779     * @param {Array} themes Scope of the Variants to be returned.
2780     * @param {boolean} If true the scope property of a variant must match all
2781     * themes, if false one theme must be matched at least.
2782     * @returns {Array} An array of variants
2783     * @throws {IllegalArgumentException} If themes is null.
2784     */
2785    ScopedIndex.prototype.getVariantsByThemes = function (themes, matchall) {
2786        if (themes === null) {
2787            throw {name: 'IllegalArgumentException',
2788                message: 'ScopedIndex.getVariantsByThemes cannot be called without themes'};
2789        }
2790        return IndexHelper.getConstructsByThemes(this.theme2variants,
2791            themes, matchall);
2792    };
2793
2794    /**
2795     * Returns the topics in the topic map used in the scope property of Variants.
2796     * The return value may be empty but must never be null.
2797     * @returns {Array} An array of Topics.
2798     */
2799    ScopedIndex.prototype.getVariantThemes = function () {
2800        return IndexHelper.getConstructThemes(this.tm, this.theme2variants);
2801    };
2802
2803
2804
2805
2806    /**
2807     * @class Helper class that is used to check if constructs belong to
2808     * a given topic map.
2809     */
2810    SameTopicMapHelper = {
2811        /**
2812         * Checks if topic belongs to the topicmap 'topicmap'.
2813         * topic can be instance of Topic or an Array with topics.
2814         * topic map be null.
2815         * @static
2816         * @throws ModelConstraintException if the topic(s) don't
2817         * belong to the same topic map.
2818         * @returns false if the topic was null or true otherwise.
2819         */
2820        assertBelongsTo: function (topicmap, topic) {
2821            var i;
2822            if (!topic) {
2823                return false;
2824            }
2825            if (topic && topic instanceof Topic &&
2826                    !topicmap.equals(topic.getTopicMap())) {
2827                throw {name: 'ModelConstraintException',
2828                    message: 'scope topic belongs to different topic map'};
2829            }
2830            if (topic && topic instanceof Array) {
2831                for (i = 0; i < topic.length; i += 1) {
2832                    if (!topicmap.equals(topic[i].getTopicMap())) {
2833                        throw {name: 'ModelConstraintException',
2834                            message: 'scope topic belong to different topic maps'};
2835                    }
2836                }
2837            }
2838            return true;
2839        }
2840    };
2841
2842    /**
2843     * Helper functions for hashes of hashes
2844     * @ignore
2845     */
2846    IndexHelper = {
2847        getForKeys: function (hash, keys) {
2848            var i, j, tmp = new Hash(), value_hash, value_keys;
2849            for (i = 0; i < keys.length; i += 1) {
2850                value_hash = hash.get(keys[i].getId());
2851                if (value_hash) {
2852                    value_keys = value_hash.keys();
2853                    // we use a hash to store instances to avoid duplicates
2854                    for (j = 0; j < value_keys.length; j += 1) {
2855                        tmp.put(value_hash.get(value_keys[j]).getId(),
2856                                value_hash.get(value_keys[j]));
2857                    }
2858                }
2859            }
2860            return tmp.values();
2861        },
2862
2863        getConstructThemes: function (tm, hash) {
2864            var ret = [], keys = hash.keys(), i;
2865            for (i = 0; i < keys.length; i += 1) {
2866                if (keys[i] !== 'null') {
2867                    ret.push(tm.getConstructById(keys[i]));
2868                }
2869            }
2870            return ret;
2871        },
2872
2873        getConstructsByThemes: function (hash, themes, matchall) {
2874            var constructs, i, j;
2875            constructs = IndexHelper.getForKeys(hash, themes);
2876            if (!matchall) {
2877                return constructs;
2878            }
2879            // If matchall is true, we check all values for all types in {types}
2880            // It's a hack, but will do for now
2881            for (i = 0; i < constructs.length; i += 1) {
2882                for (j = 0; j < themes.length; j += 1) {
2883                    if (!ArrayHelper.contains(constructs[i].getScope(), themes[j])) {
2884                        constructs.splice(i, 1);
2885                        i -= 1;
2886                        break;
2887                    }
2888                }
2889            }
2890            return constructs;
2891        }
2892    };
2893
2894    /**
2895     * Helper functions for arrays. We don't modify the global array
2896     * object to avoid conflicts with other libraries.
2897     * @ignore
2898     */
2899    ArrayHelper = {
2900        /** Checks if arr contains elem */
2901        contains: function (arr, elem) {
2902            for (var key in arr) {
2903                if (arr.hasOwnProperty(key)) {
2904                    if (arr[key].equals(elem)) {
2905                        return true;
2906                    }
2907                }
2908            }
2909            return false;
2910        }
2911    };
2912
2913    /**
2914     * Internal function to add scope. scope may be Array, Topic or null.
2915     * @ignore
2916     * FIXME: Move to a class
2917     */
2918    addScope = function (construct, scope) {
2919        var i;
2920        if (scope && typeof scope === 'object') {
2921            if (scope instanceof Array) {
2922                for (i = 0; i < scope.length; i += 1) {
2923                    construct.addTheme(scope[i]);
2924                }
2925            } else if (scope instanceof Topic) {
2926                construct.addTheme(scope);
2927            }
2928        } else {
2929            construct.getTopicMap().addThemeEvent.fire(construct, {theme: null});
2930        }
2931    };
2932
2933    /**
2934     * Helper class for generating signatures of Topic Maps constructs.
2935     */
2936    SignatureGenerator = {
2937        makeNameValueSignature: function (name) {
2938            return name.getValue();
2939        },
2940
2941        makeNameSignature: function (name) {
2942            return SignatureGenerator.makeNameValueSignature(name) +
2943                '#' + SignatureGenerator.makeTypeSignature(name) +
2944                '#' + SignatureGenerator.makeScopeSignature(name);
2945        },
2946
2947        makeOccurrenceSignature: function (occ) {
2948            return SignatureGenerator.makeOccurrenceValueSignature(occ) +
2949                '#' + SignatureGenerator.makeTypeSignature(occ) +
2950                '#' + SignatureGenerator.makeScopeSignature(occ);
2951        },
2952
2953        makeOccurrenceValueSignature: function (occ) {
2954            return '#' + occ.getValue() + '#' +
2955                (occ.getDatatype() ? occ.getDatatype().getReference() : 'null');
2956        },
2957
2958        makeTypeSignature: function (obj) {
2959            var type = obj.getType();
2960            if (type) {
2961                return type.getId();
2962            } else {
2963                return '';
2964            }
2965        },
2966
2967        makeScopeSignature: function (scope) {
2968            var i, arr = [];
2969            for (i = 0; i < scope.length; i += 1) {
2970                arr.push(scope[i].getId());
2971            }
2972            arr.sort();
2973            return arr.join('#');
2974        },
2975
2976        makeAssociationSignature: function (ass) {
2977            var roles, i, tmp = [];
2978            roles = ass.getRoles();
2979            for (i = 0; i < roles.length; i += 1) {
2980                tmp.push(SignatureGenerator.makeRoleSignature(roles[i]));
2981            }
2982            tmp.sort();
2983       
2984            return '#' + SignatureGenerator.makeTypeSignature(ass) + '#' + tmp.join('#') +
2985                SignatureGenerator.makeScopeSignature(ass);
2986        },
2987
2988        makeRoleSignature: function (role) {
2989            return SignatureGenerator.makeTypeSignature(role) + '#' +
2990                role.getPlayer().getId();
2991        },
2992
2993        makeVariantValueSignature: function (variant) {
2994            return '#' + variant.getValue() + '#' + variant.getDatatype().getReference();
2995        },
2996
2997        makeVariantSignature: function (variant) {
2998            return SignatureGenerator.makeVariantValueSignature(variant) +
2999                '#' + SignatureGenerator.makeScopeSignature(variant);
3000        }
3001    };
3002
3003    /**
3004     * Utility class that removes duplicates according to the TMDM.
3005     */
3006    DuplicateRemover = {
3007        removeTopicMapDuplicates: function (tm) {
3008            var i, topics, associations, sig2ass = new Hash(), sig, existing;
3009            topics = tm.getTopics();
3010            for (i = 0; i < topics.length; i += 1) {
3011                DuplicateRemover.removeOccurrencesDuplicates(topics[i].getOccurrences());
3012                DuplicateRemover.removeNamesDuplicates(topics[i].getNames());
3013            }
3014            associations = tm.getAssociations();
3015            for (i = 0; i < associations.length; i += 1) {
3016                DuplicateRemover.removeAssociationDuplicates(associations[i]);
3017                sig = SignatureGenerator.makeAssociationSignature(associations[i]);
3018                if ((existing = sig2ass.get(sig))) {
3019                    MergeHelper.moveConstructCharacteristics(associations[i], existing);
3020                    MergeHelper.moveRoleCharacteristics(associations[i], existing);
3021                    associations[i].remove();
3022                } else {
3023                    sig2ass.put(sig, associations[i]);
3024                }
3025            }
3026            sig2ass.empty();
3027        },
3028
3029        removeOccurrencesDuplicates: function (occurrences) {
3030            var i, sig2occ = new Hash(), occ, sig, existing;
3031            for (i = 0; i < occurrences.length; i += 1) {
3032                occ = occurrences[i];
3033                sig = SignatureGenerator.makeOccurrenceSignature(occ);
3034                if ((existing = sig2occ.get(sig))) {
3035                    MergeHelper.moveConstructCharacteristics(occ, existing);
3036                    occ.remove();
3037                } else {
3038                    sig2occ.put(sig, occ);
3039                }
3040            }
3041            sig2occ.empty();
3042        },
3043
3044        removeNamesDuplicates: function (names) {
3045            var i, sig2names = new Hash(), name, sig, existing;
3046            for (i = 0; i < names.length; i += 1) {
3047                name = names[i];
3048                DuplicateRemover.removeVariantsDuplicates(name.getVariants());
3049                sig = SignatureGenerator.makeNameSignature(name);
3050                if ((existing = sig2names.get(sig))) {
3051                    MergeHelper.moveConstructCharacteristics(name, existing);
3052                    MergeHelper.moveVariants(name, existing);
3053                    name.remove();
3054                } else {
3055                    sig2names.put(sig, name);
3056                }
3057            }
3058            sig2names.empty();
3059        },
3060
3061        removeVariantsDuplicates: function (variants) {
3062            var i, sig2variants = new Hash(), variant, sig, existing;
3063            for (i = 0; i < variants.length; i += 1) {
3064                variant = variants[i];
3065                sig = SignatureGenerator.makeVariantSignature(variant);
3066                if ((existing = sig2variants.get(sig))) {
3067                    MergeHelper.moveConstructCharacteristics(variant, existing);
3068                    variant.remove();
3069                } else {
3070                    sig2variants.put(sig, variant);
3071                }
3072            }
3073            sig2variants.empty();
3074        },
3075
3076        removeAssociationDuplicates: function (assoc) {
3077            var i, roles = assoc.getRoles(), sig2role = new Hash(), sig, existing;
3078            for (i = 0; i < roles.length; i += 1) {
3079                sig = SignatureGenerator.makeRoleSignature(roles[i]);
3080                if ((existing = sig2role.get(sig))) {
3081                    MergeHelper.moveConstructCharacteristics(roles[i], existing);
3082                    roles[i].remove();
3083                } else {
3084                    sig2role.put(sig, roles[i]);
3085                }
3086            }
3087        }
3088    };
3089
3090    MergeHelper = {
3091        moveTypes: function (arr, target) {
3092            var i;
3093            for (i = 0; i < arr.length; i += 1) {
3094                arr[i].setType(target);
3095            }
3096        },
3097
3098        moveThemes: function (arr, source, target) {
3099            for (var i = 0; i < arr.length; i += 1) {
3100                arr[i].removeTheme(source);
3101                arr[i].addTheme(target);
3102            }
3103        },
3104
3105        moveItemIdentifiers: function (source, target) {
3106            var iis, ii;
3107            iis = source.getItemIdentifiers();
3108            while (iis.length) {
3109                ii = iis[iis.length - 1];
3110                source.removeItemIdentifier(ii);
3111                target.addItemIdentifier(ii);
3112            }
3113        },
3114
3115        /**
3116         * Moves variants from the name source to the name target
3117         */
3118        moveVariants: function (source, target) {
3119            var arr, i, tmp, tmp2, signatures;
3120            arr = target.getVariants();
3121            signatures = {};
3122            for (i = 0; i < arr.length; i += 1) {
3123                signatures[SignatureGenerator.makeVariantSignature(arr[i])] = arr[i];
3124            }
3125            arr = source.getVariants();
3126            for (i = 0; i < arr.length; i += 1) {
3127                tmp = arr[i];
3128                if ((tmp2 = signatures[SignatureGenerator.makeVariantSignature(arr[i])])) {
3129                    MergeHelper.moveItemIdentifiers(tmp, tmp2);
3130                    MergeHelper.moveReifier(tmp, tmp2);
3131                    tmp.remove();
3132                } else {
3133                    target.createVariant(tmp.getValue(), tmp.getDatatype(), tmp.getScope());
3134                }
3135            }
3136        },
3137
3138        moveReifier: function (source, target) {
3139            var r1, r2;
3140            if (source.getReifier() === null) {
3141                return;
3142            } else if (target.getReifier() === null) {
3143                target.setReifier(source.getReifier());
3144            } else {
3145                r1 = source.getReifier();
3146                r2 = target.getReifier();
3147                source.setReifier(null);
3148                r1.mergeIn(r2);
3149            }
3150        },
3151
3152        moveRoleCharacteristics: function (source, target) {
3153            var i, roles, sigs = new Hash();
3154            roles = target.getRoles();
3155            for (i = 0; i < roles.length; i += 1) {
3156                sigs.put(roles[i], SignatureGenerator.makeRoleSignature(roles[i]));
3157            }
3158            roles = source.getRoles();
3159            for (i = 0; i < roles.length; i += 1) {
3160                MergeHelper.moveItemIdentifiers(roles[i],
3161                    sigs.get(SignatureGenerator.makeRoleSignature(roles[i])));
3162                roles[i].remove();
3163            }
3164        },
3165
3166        moveConstructCharacteristics: function (source, target) {
3167            MergeHelper.moveReifier(source, target);
3168            MergeHelper.moveItemIdentifiers(source, target);
3169        }
3170    };
3171
3172    CopyHelper = {
3173        copyAssociations: function (source, target, mergeMap) {
3174        },
3175        copyItemIdentifiers: function (source, target) {
3176        },
3177        copyReifier: function (source, target, mergeMap) {
3178        },
3179        copyScope: function (source, target, mergeMap) {
3180        },
3181        copyTopicMap: function (source, target) {
3182        },
3183        copyTopic: function (sourcetm, targettm, mergeMap) {
3184        },
3185        copyType: function (source, target, mergeMap) {
3186        }
3187    };
3188
3189    TypeInstanceHelper = {
3190        convertAssociationsToType: function (tm) {
3191            var typeInstance, type, instance, associations, index, i, ass, roles;
3192            typeInstance = tm.getTopicBySubjectIdentifier(
3193                tm.createLocator(TMDM.TYPE_INSTANCE));
3194            type = tm.getTopicBySubjectIdentifier(
3195                tm.createLocator(TMDM.TYPE));
3196            instance = tm.getTopicBySubjectIdentifier(
3197                tm.createLocator(TMDM.INSTANCE));
3198            if (!typeInstance || !type || !instance) {
3199                return;
3200            }
3201            index = tm.getIndex('TypeInstanceIndex');
3202            if (!index) {
3203                return;
3204            }
3205            if (!index.isAutoUpdated()) {
3206                index.reindex();
3207            }
3208            associations = index.getAssociations(typeInstance);
3209            for (i = 0; i < associations.length; i += 1) {
3210                ass = associations[i];
3211                if (ass.getScope().length > 0 ||
3212                    ass.getReifier() !== null ||
3213                    ass.getItemIdentifiers().length > 0) {
3214                    continue;
3215                }
3216                roles = ass.getRoles();
3217                if (roles.length !== 2) {
3218                    continue;
3219                }
3220                if (roles[0].getType().equals(type) && roles[1].getType().equals(instance)) {
3221                    roles[1].getPlayer().addType(roles[0].getPlayer());
3222                } else
3223                if (roles[1].getType().equals(type) && roles[0].getType().equals(instance)) {
3224                    roles[0].getPlayer().addType(roles[1].getPlayer());
3225                } else {
3226                    continue;
3227                }
3228                ass.remove();
3229            }
3230        }
3231    };
3232
3233    // Export objects into the TM namespace
3234    return {
3235        TopicMapSystemFactory: TopicMapSystemFactory,
3236        XSD: XSD,
3237        TMDM: TMDM,
3238        Hash: Hash, // needed by CXTM export
3239        Version: Version
3240    };
3241}());
3242
3243// Pollute the global namespace
3244TopicMapSystemFactory = TM.TopicMapSystemFactory; 
3245
3246// Check if we are in a CommonJS environment (e.g. node.js)
3247if (typeof exports === 'object' && exports !== null) {
3248    exports.TopicMapSystemFactory = TopicMapSystemFactory;
3249    exports.TM = TM;
3250}
3251
3252/*jslint browser: true, devel: true, onevar: true, undef: true, nomen: false, eqeqeq: true, plusplus: true, bitwise: true,
3253  regexp: true, newcap: true, immed: true, indent: 4 */
3254/*global TM, window, DOMParser, ActiveXObject*/ 
3255
3256TM.JTM = (function () {
3257    var ReaderImpl, WriterImpl;
3258
3259    ReaderImpl = function (tm) {
3260        var that = this;
3261        this.tm = tm;
3262        this.version = null; // Keep the JTM version number
3263        this.prefixes = {};
3264        this.defaultDatatype = this.tm.createLocator(TM.XSD.string);
3265
3266        this.curieToLocator = function (loc) {
3267            var curie, prefix, pos;
3268            if (that.version === '1.1' &&
3269                loc.substr(0, 1) === '[') {
3270                if (loc.substr(loc.length - 1, 1) !== ']') {
3271                    throw {name: 'InvalidFormat',
3272                        message: 'Invaild CURIE: missing tailing bracket'};
3273                }
3274                curie = loc.substr(1, loc.length - 2);
3275                pos = curie.indexOf(':');
3276                if (pos !== -1) {
3277                    // Lookup prefix and replace with URL
3278                    prefix = curie.substr(0, pos);
3279                    if (that.prefixes[prefix]) {
3280                        loc = that.prefixes[prefix] +
3281                            curie.substr(pos + 1, curie.length - 1);
3282                        return loc;
3283                    } else {
3284                        throw {name: 'InvalidFormat',
3285                            message: 'Missing prefix declaration: ' + prefix};
3286                    }
3287                } else {
3288                    throw {name: 'InvalidFormat',
3289                        message: 'Invaild CURIE: missing colon'};
3290                }
3291            }
3292            return loc;
3293        };
3294
3295        /**
3296        * Internal function that takes a JTM-identifier string as a parameter
3297        * and returns a topic object - either an existing topic or a new topic
3298        * if the requested topic did not exist
3299        * @param {String} locator JTM-identifier
3300        * @throws {InvalidFormat} If the locator could not be parsed.
3301        */
3302        this.getTopicByReference = function (locator) {
3303            if (typeof locator === 'undefined' || locator === null) {
3304                return null;
3305            }
3306            switch (locator.substr(0, 3)) {
3307            case 'si:' :
3308                return this.tm.createTopicBySubjectIdentifier(
3309                    this.tm.createLocator(this.curieToLocator(locator.substr(3))));
3310            case 'sl:' :
3311                return this.tm.createTopicBySubjectLocator(
3312                    this.tm.createLocator(this.curieToLocator(locator.substr(3))));
3313            case 'ii:' :
3314                return this.tm.createTopicByItemIdentifier(
3315                    this.tm.createLocator(this.curieToLocator(locator.substr(3))));
3316            }
3317            throw {name: 'InvalidFormat',
3318                message: 'Invaild topic reference \'' + locator + '\''};
3319        };
3320    };
3321
3322    /**
3323    * Imports a JTM topic map or JTM fragment from a JSON-string.
3324    * name, variant, occurrence and role fragments need the optional parent
3325    * construct as a parameter.
3326    * TODO: Decide if this should be part of tmjs. Add functions for decoding/
3327    * encoding JSON if so.
3328    *
3329    * @param {String} str JSON encoded JTM
3330    * @param {Construct} [parent] Parent construct if the JTM fragment contains
3331    *        a name, variant, occurrence or role.
3332    */
3333    ReaderImpl.prototype.fromString = function (str, parent) {
3334        var obj = JSON.parse(str);
3335        return this.fromObject(obj);
3336    };
3337
3338    /**
3339    * Imports a JTM topic map or JTM fragment.
3340    * name, variant, occurrence and role fragments need the parent construct
3341    * as a parameter.
3342    *
3343    * @param {object} obj with JTM properties
3344    * @param {Construct} [parent] Parent construct if the JTM fragment contains
3345    *        a name, variant, occurrence or role.
3346    */
3347    ReaderImpl.prototype.fromObject = function (obj, parent) {
3348        var ret;
3349        parent = parent || null;
3350        if (obj.version !== '1.0' && obj.version !== '1.1') {
3351            throw {name: 'InvalidFormat',
3352                message: 'Unknown version of JTM: ' + obj.version};
3353        }
3354        this.version = obj.version;
3355        if (obj.version === '1.1' && obj.prefixes) {
3356            this.prefixes = obj.prefixes;
3357            // Check if xsd is defined and if it is valid:
3358            if (obj.prefixes && obj.prefixes.xsd &&
3359                obj.prefixes.xsd !== 'http://www.w3.org/2001/XMLSchema#') {
3360                throw {name: 'InvalidFormat',
3361                    message: 'The XSD prefix MUST have the value "http://www.w3.org/2001/XMLSchema#"'};
3362            }
3363        } else if (obj.prefixes) {
3364            throw {name: 'InvalidFormat',
3365                message: 'Prefixes are invalid in JTM 1.0: ' + obj.version};
3366        }
3367        if (!this.prefixes.xsd) {
3368            this.prefixes.xsd = 'http://www.w3.org/2001/XMLSchema#';
3369        }
3370        if (!obj.item_type) {
3371            throw {name: 'InvalidFormat',
3372                message: 'Missing item_type'};
3373        }
3374        switch (obj.item_type.toLowerCase()) {
3375        case "topicmap":
3376            ret = this.parseTopicMap(obj);
3377            break;
3378        case "topic":
3379            ret = this.parseTopic(obj);
3380            break;
3381        case "name":
3382            ret = this.parseName(parent, obj);
3383            break;
3384        case "variant":
3385            ret = this.parseVariant(parent, obj);
3386            break;
3387        case "occurrence":
3388            ret = this.parseOccurrence(parent, obj);
3389            break;
3390        case "association":
3391            ret = this.parseAssociation(obj);
3392            break;
3393        case "role":
3394            ret = this.parseRole(parent, obj);
3395            break;
3396        default:
3397            throw {name: 'InvalidFormat',
3398                message: 'Unknown item_type property'};
3399        }
3400        return ret;
3401    };
3402
3403    /**
3404     * FIXME: Work in progress. We have to specify *how* the information
3405     * item can be created.
3406     *
3407     * Internal function that parses a parent field. From the JTM spec:
3408     * "The value of the parent member is an array of item identifiers,
3409     * each prefixed by "ii:". For occurrences and names the parent array
3410     * may as well contain subject identifiers prefixed by "si:" and
3411     * subject locators prefixed by "sl:".
3412     */
3413    ReaderImpl.prototype.parseParentAsTopic = function (obj, allowTopic) {
3414        var parent = null, tmp, i;
3415        if (!obj.parent) {
3416            parent = this.tm.createTopic();
3417        } else if (!(obj.parent instanceof Array) || obj.parent.length === 0) {
3418            throw {name: 'InvalidFormat',
3419                message: 'Missing parent topic reference in occurrence'};
3420        }
3421        if (obj.parent) {
3422            for (i = 0; i < obj.parent.length; i += 1) {
3423                tmp = this.getTopicByReference(obj.parent[i]);
3424                if (!parent) {
3425                    parent = tmp;
3426                } else {
3427                    parent.mergeIn(tmp);
3428                }
3429            }
3430        }
3431        return parent;
3432    };
3433
3434    ReaderImpl.prototype.parseTopicMap = function (obj) {
3435        var i, len, arr;
3436        this.parseItemIdentifiers(this.tm, obj.item_identifiers);
3437        this.parseReifier(this.tm, obj.reifier);
3438        if (obj.topics && typeof obj.topics === 'object' && obj.topics instanceof Array) {
3439            arr = obj.topics;
3440            len = arr.length;
3441            for (i = 0; i < len; i += 1) {
3442                this.parseTopic(arr[i]);
3443            }
3444            arr = null;
3445        }
3446        if (obj.associations && typeof obj.associations === 'object' &&
3447            obj.associations instanceof Array) {
3448            arr = obj.associations;
3449            len = arr.length;
3450            for (i = 0; i < len; i += 1) {
3451                this.parseAssociation(arr[i]);
3452            }
3453            arr = null;
3454        }
3455        this.tm.sanitize(); // remove duplicates and convert type-instance associations to types
3456        return true;
3457    };
3458
3459    ReaderImpl.prototype.parseTopic = function (obj) {
3460        var that = this, topic = null, parseIdentifier, arr, i, identifier, type;
3461        parseIdentifier = function (tm, topic, arr, getFunc, createFunc, addFunc) {
3462            var i, len, tmp;
3463            if (arr && typeof arr === 'object' && arr instanceof Array) {
3464                len = arr.length;
3465                for (i = 0; i < len; i += 1) {
3466                    identifier = decodeURI(that.curieToLocator(arr[i]));
3467                    if (!topic) {
3468                        topic = createFunc.apply(tm, [tm.createLocator(identifier)]);
3469                    } else {
3470                        tmp = getFunc.apply(tm, [tm.createLocator(identifier)]);
3471                        if (tmp && tmp.isTopic() && !topic.equals(tmp)) {
3472                            topic.mergeIn(tmp);
3473                        } else if (!(tmp && tmp.isTopic() && topic.equals(tmp))) {
3474                            topic[addFunc](tm.createLocator(identifier));
3475                        }
3476                    }
3477                }
3478            }
3479            return topic;
3480        };
3481        topic = parseIdentifier(this.tm, topic, obj.subject_identifiers,
3482            this.tm.getTopicBySubjectIdentifier,
3483            this.tm.createTopicBySubjectIdentifier, 'addSubjectIdentifier');
3484        topic = parseIdentifier(this.tm, topic, obj.subject_locators,
3485            this.tm.getTopicBySubjectLocator,
3486            this.tm.createTopicBySubjectLocator, 'addSubjectLocator');
3487        topic = parseIdentifier(this.tm, topic, obj.item_identifiers,
3488            this.tm.getConstructByItemIdentifier,
3489            this.tm.createTopicByItemIdentifier, 'addItemIdentifier');
3490
3491        if ((arr = obj.instance_of) && this.version === '1.1') {
3492            for (i = 0; i < arr.length; i += 1) {
3493                type = this.getTopicByReference(arr[i]);
3494                topic.addType(type);
3495            }
3496        } else if (obj.instance_of && this.version === '1.0') {
3497            throw {name: 'InvalidFormat',
3498                message: 'instance_of is invalid in JTM 1.0'};
3499        }
3500
3501        arr = obj.names;
3502        if (arr && typeof arr === 'object' && arr instanceof Array) {
3503            for (i = 0; i < arr.length; i += 1) {
3504                this.parseName(topic, arr[i]);
3505            }
3506        }
3507        arr = obj.occurrences;
3508        if (arr && typeof arr === 'object' && arr instanceof Array) {
3509            for (i = 0; i < arr.length; i += 1) {
3510                this.parseOccurrence(topic, arr[i]);
3511            }
3512        }
3513    };
3514
3515    ReaderImpl.prototype.parseName = function (parent, obj) {
3516        var name, type, scope, arr, i;
3517        if (!parent) {
3518            parent = this.parseParentAsTopic(obj);
3519        }
3520        scope = this.parseScope(obj.scope);
3521        type = this.getTopicByReference(obj.type);
3522        name = parent.createName(obj.value, type, scope);
3523        arr = obj.variants;
3524        if (arr && typeof arr === 'object' && arr instanceof Array) {
3525            for (i = 0; i < arr.length; i += 1) {
3526                this.parseVariant(name, arr[i]);
3527            }
3528        }
3529        this.parseItemIdentifiers(name, obj.item_identifiers);
3530        this.parseReifier(name, obj.reifier);
3531    };
3532
3533    ReaderImpl.prototype.parseVariant = function (parent, obj) {
3534        var variant, scope;
3535        scope = this.parseScope(obj.scope);
3536        variant = parent.createVariant(obj.value, 
3537            obj.datatype ?
3538                this.tm.createLocator(this.curieToLocator(obj.datatype)) :
3539                    this.defaultDatatype, scope);
3540        this.parseItemIdentifiers(variant, obj.item_identifiers);
3541        this.parseReifier(variant, obj.reifier);
3542    };
3543
3544    ReaderImpl.prototype.parseOccurrence = function (parent, obj) {
3545        var occurrence, type, scope;
3546        if (!parent) {
3547            parent = this.parseParentAsTopic(obj);
3548        }
3549        scope = this.parseScope(obj.scope);
3550        type = this.getTopicByReference(obj.type);
3551        occurrence = parent.createOccurrence(type, obj.value,
3552            obj.datatype ?
3553                this.tm.createLocator(this.curieToLocator(obj.datatype)) :
3554                    this.defaultDatatype, scope);
3555        this.parseItemIdentifiers(occurrence, obj.item_identifiers);
3556        this.parseReifier(occurrence, obj.reifier);
3557    };
3558
3559    ReaderImpl.prototype.parseAssociation = function (obj) {
3560        var association, type, scope, arr, i;
3561        scope = this.parseScope(obj.scope);
3562        type = this.getTopicByReference(obj.type);
3563        association = this.tm.createAssociation(type, scope);
3564        arr = obj.roles;
3565        if (arr && typeof arr === 'object' && arr instanceof Array) {
3566            if (arr.length === 0) {
3567                throw {name: 'InvalidFormat',
3568                    message: 'Association needs roles'};
3569            }
3570            for (i = 0; i < arr.length; i += 1) {
3571                this.parseRole(association, arr[i]);
3572            }
3573        } else {
3574            throw {name: 'InvalidFormat',
3575                message: 'Association needs roles'};
3576        }
3577        this.parseItemIdentifiers(association, obj.item_identifiers);
3578        this.parseReifier(association, obj.reifier);
3579    };
3580
3581    ReaderImpl.prototype.parseRole = function (parent, obj) {
3582        var role, type, player;
3583        type = this.getTopicByReference(obj.type);
3584        player = this.getTopicByReference(obj.player);
3585        role = parent.createRole(type, player);
3586        this.parseItemIdentifiers(role, obj.item_identifiers);
3587        this.parseReifier(role, obj.reifier);
3588    };
3589
3590    ReaderImpl.prototype.parseScope = function (arr) {
3591        var i, scope = [];
3592        if (arr && typeof arr === 'object' && arr instanceof Array) {
3593            for (i = 0; i < arr.length; i += 1) {
3594                scope.push(this.getTopicByReference(arr[i]));
3595            }
3596        }
3597        return scope;
3598    };
3599
3600
3601    ReaderImpl.prototype.parseItemIdentifiers = function (construct, arr) {
3602        var i, tm, identifier;
3603        tm = construct.getTopicMap();
3604        if (arr && typeof arr === 'object' && arr instanceof Array) {
3605            for (i = 0; i < arr.length; i += 1) {
3606                identifier = this.curieToLocator(arr[i]);
3607                if (!tm.getConstructByItemIdentifier(tm.createLocator(identifier))) {
3608                    construct.addItemIdentifier(tm.createLocator(identifier));
3609                }
3610            }
3611        }
3612    };
3613
3614    ReaderImpl.prototype.parseReifier = function (construct, reifier) {
3615        var reifierTopic = this.getTopicByReference(reifier);
3616        if (reifierTopic && reifierTopic.getReified() === null || !reifierTopic) {
3617            construct.setReifier(reifierTopic);
3618        } // else: Ignore the case that reifierTopic reifies another item
3619    };
3620
3621    /**
3622    * @class Exports topic maps constructs as JTM 1.0 JavaScript objects.
3623    * See http://www.cerny-online.com/jtm/1.0/ for the JSON Topic Maps specification.
3624    * JSON 1.1 is described at http://www.cerny-online.com/jtm/1.1/
3625    * @param {String} version Version number of the JTM export. Valid values are '1.0'
3626    *                 and '1.1'. Version 1.1 produces more compact files. The default
3627    *                 value is '1.0', but this may change in the future.
3628    */
3629    WriterImpl = function (version) {
3630        var that = this, referenceToCURIEorURI;
3631        this.defaultDatatype = TM.XSD.string;
3632        this.prefixes = new TM.Hash();
3633        this.version = version || '1.0';
3634
3635        referenceToCURIEorURI = function (reference) {
3636            var key, keys, i, value;
3637            if (that.version === '1.0') {
3638                return reference;
3639            }
3640            // TODO Sort keys after descending value length - longest first
3641            // to find the best prefix
3642            keys = that.prefixes.keys();
3643            for (i = 0; i < keys.length; i += 1) {
3644                key = keys[i];
3645                value = that.prefixes.get(key);
3646                if (reference.substring(0, value.length) ===  value) {
3647                    return '[' + key + ':' +
3648                        reference.substr(value.length) + ']';
3649                }
3650            }
3651            return reference;
3652        }; 
3653
3654        /**
3655        * Sets prefixes for JTM 1.1 export. prefixes is an object with the
3656        * prefix as key and its corresponding reference as value.
3657        */
3658        this.setPrefixes = function (prefixes) {
3659            var key;
3660            for (key in prefixes) {
3661                if (prefixes.hasOwnProperty(key)) {
3662                    this.prefixes.put(key, prefixes[key]);
3663                }
3664            }
3665        };
3666
3667        /**
3668         * Generates a JTM reference based on the topics subject identifier,
3669         * subject locator or item identifier (whatever is set, tested in this
3670         * order).
3671         * @returns {string} Representing the topic t, e.g.
3672         *     "si:http://psi.topicmaps.org/iso13250/model/type
3673         */
3674        this.getTopicReference = function (t) {
3675            var arr;
3676            arr = t.getSubjectIdentifiers();
3677            if (arr.length > 0) {
3678                return 'si:' + referenceToCURIEorURI(arr[0].getReference());
3679            }
3680            arr = t.getSubjectLocators();
3681            if (arr.length > 0) {
3682                return 'sl:' + referenceToCURIEorURI(arr[0].getReference());
3683            }
3684            arr = t.getItemIdentifiers();
3685            if (arr.length > 0) {
3686                return 'ii:' + referenceToCURIEorURI(arr[0].getReference());
3687            }
3688            // ModelConstraintExeption: TMDM says that t MUST have on of these
3689        };
3690
3691        this.exportIdentifiers = function (obj, arr, attr) {
3692            var i, len = arr.length;
3693            if (len > 0) {
3694                obj[attr] = [];
3695                for (i = 0; i < len; i += 1) {
3696                    obj[attr].push(referenceToCURIEorURI(arr[i].getReference()));
3697                }
3698            }
3699       
3700        }; 
3701
3702        this.exportScope = function (obj, construct) {
3703            var i, arr = construct.getScope();
3704            if (arr.length > 0) {
3705                obj.scope = [];
3706                for (i = 0; i < arr.length; i += 1) {
3707                    obj.scope.push(that.getTopicReference(arr[i]));
3708                }
3709            }
3710        };
3711
3712        this.exportParent = function (obj, construct) {
3713            var parent = construct.getParent();
3714            that.exportIdentifiers(obj, parent.getItemIdentifiers(), 'parent');
3715        };
3716
3717        this.exportTopicMap = function (m) {
3718            var arr, i, len, obj;
3719            obj = {
3720                topics: [],
3721                associations: []
3722            };
3723            arr = m.getTopics();
3724            len = arr.length;
3725            for (i = 0; i < len; i += 1) {
3726                obj.topics.push(that.exportTopic(arr[i]));
3727            }
3728            arr = m.getAssociations();
3729            len = arr.length;
3730            for (i = 0; i < len; i += 1) {
3731                obj.associations.push(that.exportAssociation(arr[i]));
3732            }
3733            return obj;
3734        };
3735
3736        this.exportTopic = function (t) {
3737            var arr, i, len, obj;
3738            obj = {};
3739            that.exportIdentifiers(obj, t.getSubjectIdentifiers(), 'subject_identifiers');
3740            that.exportIdentifiers(obj, t.getSubjectLocators(), 'subject_locators');
3741            that.exportIdentifiers(obj, t.getItemIdentifiers(), 'item_identifiers');
3742            arr = t.getNames();
3743            len = arr.length;
3744            if (len > 0) {
3745                obj.names = [];
3746                for (i = 0; i < len; i += 1) {
3747                    obj.names.push(that.exportName(arr[i]));
3748                }
3749            }
3750            arr = t.getOccurrences();
3751            len = arr.length;
3752            if (len > 0) {
3753                obj.occurrences = [];
3754                for (i = 0; i < len; i += 1) {
3755                    obj.occurrences.push(that.exportOccurrence(arr[i]));
3756                }
3757            }
3758            arr = t.getTypes();
3759            len = arr.length;
3760            if (len > 0) {
3761                obj.instance_of = [];
3762                for (i = 0; i < len; i += 1) {
3763                    obj.instance_of.push(that.getTopicReference(arr[i]));
3764                }
3765            }
3766            return obj;
3767        };
3768
3769        this.exportName = function (name) {
3770            var arr, i, len, obj, tmp;
3771            obj = {
3772                'value': name.getValue()
3773            };
3774            tmp = name.getType();
3775            if (tmp) {
3776                obj.type = that.getTopicReference(tmp);
3777            }
3778            tmp = name.getReifier();
3779            if (tmp) {
3780                obj.reifier = that.getTopicReference(tmp);
3781            }
3782           
3783            that.exportIdentifiers(obj, name.getItemIdentifiers(), 'item_identifiers');
3784            that.exportScope(obj, name);
3785            arr = name.getVariants();
3786            len = arr.length;
3787            if (len > 0) {
3788                obj.variants = [];
3789                for (i = 0; i < len; i += 1) {
3790                    obj.variants.push(that.exportVariant(arr[i]));
3791                }
3792            }
3793            return obj;
3794        };
3795
3796        this.exportVariant = function (variant) {
3797            var obj, tmp;
3798            obj = {
3799                'value': variant.getValue()
3800            };
3801            tmp = variant.getDatatype();
3802            if (tmp && tmp !== variant.getTopicMap().createLocator(that.defaultDatatype)) {
3803                obj.datatype = referenceToCURIEorURI(tmp.getReference());
3804            }
3805            tmp = variant.getReifier();
3806            if (tmp) {
3807                obj.reifier = that.getTopicReference(tmp);
3808            }
3809           
3810            that.exportIdentifiers(obj, variant.getItemIdentifiers(), 'item_identifiers');
3811            that.exportScope(obj, variant);
3812        };
3813
3814        this.exportOccurrence = function (occ) {
3815            var obj, tmp;
3816            obj = {
3817                value: occ.getValue(),
3818                type: that.getTopicReference(occ.getType())
3819            };
3820            tmp = occ.getDatatype();
3821            if (tmp && tmp !== occ.getTopicMap().createLocator(that.defaultDatatype)) {
3822                obj.datatype = referenceToCURIEorURI(tmp.getReference());
3823            }
3824            tmp = occ.getReifier();
3825            if (tmp) {
3826                obj.reifier = that.getTopicReference(tmp);
3827            }
3828           
3829            that.exportIdentifiers(obj, occ.getItemIdentifiers(), 'item_identifiers');
3830            that.exportScope(obj, occ);
3831            return obj;
3832        };
3833
3834        this.exportAssociation = function (association) {
3835            var arr, i, obj, tmp;
3836            obj = {
3837                type: that.getTopicReference(association.getType()),
3838                roles: []
3839            };
3840            tmp = association.getReifier();
3841            if (tmp) {
3842                obj.reifier = that.getTopicReference(tmp);
3843            }
3844            that.exportIdentifiers(obj, association.getItemIdentifiers(), 'item_identifiers');
3845            that.exportScope(obj, association);
3846            arr = association.getRoles();
3847            for (i = 0; i < arr.length; i += 1) {
3848                obj.roles.push(that.exportRole(arr[i]));
3849            }
3850            return obj;
3851        };
3852
3853        this.exportRole = function (role) {
3854            var obj, tmp;
3855            obj = {
3856                player: that.getTopicReference(role.getPlayer()),
3857                type: that.getTopicReference(role.getType())
3858            };
3859            tmp = role.getReifier();
3860            if (tmp) {
3861                obj.reifier = that.getTopicReference(tmp);
3862            }
3863            that.exportIdentifiers(obj, role.getItemIdentifiers(), 'item_identifiers');
3864            return obj;
3865        };
3866    };
3867
3868    /**
3869    * Returns a JTM JavaScript object representation of construct.
3870    * @param {Construct} construct The topic map construct to be exported. Can be
3871    * TopicMap, Topic, Occurrence, Name, Variant, Association or Role.
3872    * @param {boolean} [includeParent] If true the optional JTM element 'parent' is
3873    * included. Refers to the parent via its item identifier.  If undefined or false,
3874    * the parent element is dropped.
3875    */
3876    WriterImpl.prototype.toObject = function (construct, includeParent) {
3877        var obj, tm, keys, i;
3878        includeParent = includeParent || false;
3879        tm = construct.getTopicMap();
3880
3881        if (construct.isTopicMap()) {
3882            obj = this.exportTopicMap(construct);
3883            obj.item_type = 'topicmap';
3884        } else if (construct.isRole()) {
3885            obj = this.exportRole(construct);
3886            obj.item_type = 'role';
3887        } else if (construct.isTopic()) {
3888            obj = this.exportTopic(construct);
3889            obj.item_type = 'topic';
3890        } else if (construct.isAssociation()) {
3891            obj = this.exportAssociation(construct);
3892            obj.item_type = 'association';
3893        } else if (construct.isOccurrence()) {
3894            obj = this.exportOccurrence(construct);
3895            obj.item_type = 'occurrence';
3896        } else if (construct.isName()) {
3897            obj = this.exportName(construct);
3898            obj.item_type = 'name';
3899        } else if (construct.isVariant()) {
3900            obj = this.exportVariant(construct);
3901            obj.item_type = 'variant';
3902        }
3903        obj.version = this.version;
3904        if (this.version === '1.1' && this.prefixes) {
3905            if (this.prefixes.size()) {
3906                keys = this.prefixes.keys();
3907                obj.prefixes = {};
3908                for (i = 0; i < keys.length; i += 1) {
3909                    obj.prefixes[keys[i]] = this.prefixes.get(keys[i]);
3910                }
3911            }
3912        }
3913        if (!construct.isTopic() && construct.getReifier()) {
3914            obj.reifier = this.getTopicReference(construct.getReifier());
3915        }
3916        if (includeParent && !construct.isTopicMap()) {
3917            this.exportParent(obj, construct);
3918        }
3919        return obj;
3920    };
3921
3922    return {
3923        Reader: ReaderImpl,
3924        Writer: WriterImpl
3925    };
3926}());
Note: See TracBrowser for help on using the repository browser.