From: <lh...@us...> - 2009-03-05 15:34:57
|
Revision: 278 http://tinytim.svn.sourceforge.net/tinytim/?rev=278&view=rev Author: lheuer Date: 2009-03-05 15:34:47 +0000 (Thu, 05 Mar 2009) Log Message: ----------- Added experimental CTM topic map serializer Added Paths: ----------- tinytim-mio/trunk/src/main/java/org/tinytim/mio/CTMTopicMapWriter.java Added: tinytim-mio/trunk/src/main/java/org/tinytim/mio/CTMTopicMapWriter.java =================================================================== --- tinytim-mio/trunk/src/main/java/org/tinytim/mio/CTMTopicMapWriter.java (rev 0) +++ tinytim-mio/trunk/src/main/java/org/tinytim/mio/CTMTopicMapWriter.java 2009-03-05 15:34:47 UTC (rev 278) @@ -0,0 +1,1193 @@ +/* + * Copyright 2009 Lars Heuer (heuer[at]semagia.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.tinytim.mio; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +import org.tinytim.internal.api.IIndexManagerAware; +import org.tinytim.internal.api.ILiteral; +import org.tinytim.internal.api.ILiteralAware; +import org.tinytim.internal.api.IName; +import org.tinytim.internal.api.IOccurrence; +import org.tinytim.internal.api.IScope; +import org.tinytim.internal.api.IScoped; +import org.tinytim.internal.api.IVariant; +import org.tinytim.voc.Namespace; +import org.tinytim.voc.TMDM; +import org.tinytim.voc.XSD; + +import org.tmapi.core.Association; +import org.tmapi.core.DatatypeAware; +import org.tmapi.core.Locator; +import org.tmapi.core.Name; +import org.tmapi.core.Occurrence; +import org.tmapi.core.Reifiable; +import org.tmapi.core.Role; +import org.tmapi.core.Scoped; +import org.tmapi.core.Topic; +import org.tmapi.core.TopicMap; +import org.tmapi.core.Typed; +import org.tmapi.core.Variant; +import org.tmapi.index.TypeInstanceIndex; + +/** + * {@link TopicMapWriter} implementation that is able to serialize topic maps + * into a + * <a href="http://www.isotopicmaps.org/ctm/">Compact Topic Maps (CTM) 1.0</a> + * representation. + * + * @author Lars Heuer (heuer[at]semagia.com) <a href="http://www.semagia.com/">Semagia</a> + * @version $Rev:$ - $Date:$ + */ +public class CTMTopicMapWriter implements TopicMapWriter { + + private static final Logger LOG = Logger.getLogger(CTMTopicMapWriter.class.getName()); + + private static final Pattern _ID_PATTERN = Pattern.compile("[A-Za-z_](\\.*[\\-A-Za-z_0-9])*"); + private final Writer _out; +// private final String _baseIRI; + private final String _encoding; + private Topic _defaultNameType; + //TODO: Add setters/getters + private boolean _exportIIDs = false; + private boolean _prettify = true; + private String _title; + private String _author; + private String _license; + private String _comment; + private final Comparator<Topic> _topicComparator; + private final Comparator<Association> _assocComparator; + private final Comparator<Occurrence> _occComparator; + private final Comparator<Name> _nameComparator; + private final Comparator<Set<Topic>> _scopeComparator; + private final Comparator<Locator> _locComparator; + private final Comparator<Set<Locator>> _locSetComparator; + private final Comparator<Role> _roleComparator; + private final Comparator<Variant> _variantComparator; + private final Map<Topic, TopicReference> _topic2Reference; + + + public CTMTopicMapWriter(final OutputStream out) throws IOException { + this(out, "utf-8"); + } + + public CTMTopicMapWriter(final OutputStream out, final String encoding) throws IOException { + this(new OutputStreamWriter(out, encoding), encoding); + } + + private CTMTopicMapWriter(final Writer writer, final String encoding) { + _out = writer; +// if (baseIRI == null) { +// throw new IllegalArgumentException("The base IRI must not be null"); +// } +// _baseIRI = baseIRI; + if (encoding == null) { + throw new IllegalArgumentException("The encoding must not be null"); + } + _encoding = encoding; + _topic2Reference = new HashMap<Topic, TopicReference>(200); + _scopeComparator = new ScopeComparator(); + _locSetComparator = new LocatorSetComparator(); + _locComparator = new LocatorComparator(); + _topicComparator = new TopicComparator(); + _assocComparator = new AssociationComparator(); + _occComparator = new OccurrenceComparator(); + _nameComparator = new NameComparator(); + _roleComparator = new RoleComparator(); + _variantComparator = new VariantComparator(); + } + + /** + * Sets the title of the topic map which appears at the top of the file. + * + * @param title The title of the topic map. + */ + public void setTitle(String title) { + _title = title; + } + + /** + * Returns the title of the topic map. + * + * @return The title or <tt>null</tt> if no title was set. + */ + public String getTitle() { + return _title; + } + + /** + * Sets the author which appears at the top of the file. + * + * @param author The author. + */ + public void setAuthor(String author) { + _author = author; + } + + /** + * Returns the author. + * + * @return The author or <tt>null</tt> if no author was set. + */ + public String getAuthor() { + return _author; + } + + /** + * Sets the license which should appear on top of the file. + * <p> + * The license of the topic map. This could be a name or an IRI or both, i.e. + * "Creative Commons-License <http://creativecommons.org/licenses/by-nc-sa/3.0/>". + * </p> + * + * @param license The license. + */ + public void setLicense(String license) { + _license = license; + } + + /** + * Returns the license. + * + * @return The license or <tt>null</tt> if no license was set. + */ + public String getLicense() { + return _license; + } + + /** + * Sets a file comment. + * <p> + * The comment could describe the topic map, or provide an additional + * copyright notice, or SVN/CVS keywords etc. + * </p> + * + * @param comment The comment. + */ + public void setComment(String comment) { + _comment = comment; + } + + /** + * Returns the comment. + * + * @return The comment or <tt>null</tt> if no comment was set. + */ + public String getComment() { + return _comment; + } + + /* (non-Javadoc) + * @see org.tinytim.mio.TopicMapWriter#write(org.tmapi.core.TopicMap) + */ + @Override + public void write(TopicMap topicMap) throws IOException { + _defaultNameType = topicMap.getTopicBySubjectIdentifier(TMDM.TOPIC_NAME); + _out.write("%encoding \"" + _encoding + "\""); + _newline(); + _out.write("%version 1.0"); + _writeFileHeader(); + _out.write(")#"); + _newline(); + _newline(); + _writeSection("Prefixes"); + _out.write("%prefix xsd <" + Namespace.XSD + ">"); + _newline(); + Collection<Topic> topics = new ArrayList<Topic>(topicMap.getTopics()); + final boolean removeDefaultNameType = _defaultNameType != null + && _defaultNameType.getSubjectIdentifiers().size() == 1 + && _defaultNameType.getSubjectLocators().size() == 0 + && _defaultNameType.getTypes().size() == 0 + && _defaultNameType.getNames().size() == 0 + && _defaultNameType.getOccurrences().size() == 0 + && _defaultNameType.getRolesPlayed().size() == 0 + && _defaultNameType.getReified() == null; + if (removeDefaultNameType) { + topics.remove(_defaultNameType); + } + if (topicMap.getReifier() != null) { + // Special handling of the tm reifier to avoid an additional + // whitespace character in front of the ~ + Topic reifier = topicMap.getReifier(); + _writeSection("Topic Map"); + _out.write("~ "); + _writeTopicRef(reifier); + _newline(); + _writeTopic(reifier); + topics.remove(reifier); + } + TypeInstanceIndex tiIdx = ((IIndexManagerAware) topicMap).getIndexManager().getTypeInstanceIndex(); + if (!tiIdx.isAutoUpdated()) { + tiIdx.reindex(); + } + _writeSection("ONTOLOGY"); + _writeOntologyTypes(tiIdx.getTopicTypes(), topics, "Topic Types"); + _writeOntologyTypes(tiIdx.getAssociationTypes(), topics, "Association Types"); + _writeOntologyTypes(tiIdx.getRoleTypes(), topics, "Role Types"); + _writeOntologyTypes(tiIdx.getOccurrenceTypes(), topics, "Occurrence Types"); + Collection<Topic> nameTypes = new ArrayList<Topic>(tiIdx.getNameTypes()); + if (removeDefaultNameType) { + nameTypes.remove(_defaultNameType); + } + _writeOntologyTypes(nameTypes, topics, "Name Types"); + tiIdx.close(); + _newline(); + _writeSection("INSTANCES"); + _writeSection("Topics"); + _writeTopics(topics); + Collection<Association> assocs = new ArrayList<Association>(topicMap.getAssociations()); + if (!assocs.isEmpty()) { + Association[] assocArray = assocs.toArray(new Association[assocs.size()]); + _writeSection("Associations"); + Arrays.sort(assocArray, _assocComparator); + for (Association assoc: assocArray) { + _writeAssociation(assoc); + } + } + _newline(); + _out.write("# Thanks for using tinyTiM -- http://tinytim.sourceforge.net/ :)"); + _newline(); + _out.flush(); + } + + private void _writeFileHeader() throws IOException { + _newline(); + _newline(); + _out.write("#("); + _newline(); + if (_title != null) { + _out.write("Title: " + _title); + _newline(); + } + if (_author != null) { + _out.write("Author: " + _author); + _newline(); + } + if (_license != null) { + _out.write("License: " + _license); + _newline(); + } + if (_comment != null) { + _newline(); + _out.write(_comment); + _newline(); + } + _newline(); + _out.write("Generated by tinyTiM -- http://tinytim.sourceforge.net/"); + _newline(); + _newline(); + } + + /** + * If <tt>topics</tt> is not empty, the topics will be removed from + * <tt>allTopics</tt> and written out under the specified section <tt>title</tt>. + * + * @param topics + * @param allTopics + * @param title + * @throws IOException + */ + private void _writeOntologyTypes(Collection<Topic> topics, Collection<Topic> allTopics, String title) throws IOException { + if (topics.isEmpty()) { + return; + } + allTopics.removeAll(topics); + _writeSection(title); + _writeTopics(topics); + } + + /** + * Sorts the specified collection and serializes the topics. + * + * @param topics An unordered collection of topics. + * @throws IOException If an error occurs. + */ + private void _writeTopics(Collection<Topic> topics) throws IOException { + Topic[] topicArray = topics.toArray(new Topic[topics.size()]); + Arrays.sort(topicArray, _topicComparator); + for (Topic topic: topicArray) { + _writeTopic(topic); + } + } + + private void _writeTopic(Topic topic) throws IOException { + final TopicReference mainIdentity = _getTopicReference(topic); +// if ((ref.type == TopicReference.ID +// || ref.type == TopicReference.IID) +// && _hasNoCharacteristics(topic)) { +// return; +// } + _newline(); + boolean wantSemicolon = false; + _writeTopicRef(mainIdentity); + _out.write(' '); + for (Topic type: topic.getTypes()) { + _writeTypeInstance(type, wantSemicolon); + wantSemicolon = true; + } + for (Name name: _getNames(topic)) { + _writeName((IName) name, wantSemicolon); + wantSemicolon = true; + } + for (Occurrence occ: _getOccurrences(topic)) { + _writeOccurrence((IOccurrence) occ, wantSemicolon); + wantSemicolon = true; + } + for (TopicReference sid: _getSubjectIdentifiers(topic)) { + _writeTopicRef(sid, wantSemicolon); + wantSemicolon = true; + } + for (TopicReference slo: _getSubjectLocators(topic)) { + _writeTopicRef(slo, wantSemicolon); + wantSemicolon = true; + } + if (_exportIIDs) { + TopicReference[] iids = _getItemIdentifiers(topic); + if ((mainIdentity.type == TopicReference.ID + || mainIdentity.type == TopicReference.IID) + && iids.length == 1) { + //TODO + } + else { + for (TopicReference iid: iids) { + _writeTopicRef(iid, wantSemicolon); + wantSemicolon = true; + } + } + } + if (wantSemicolon) { + _out.write(' '); + } + _out.write("."); + _newline(); + } + +// private boolean _hasNoCharacteristics(Topic topic) { +// return topic.getTypes().isEmpty() +// && topic.getNames().isEmpty() +// && topic.getOccurrences().isEmpty() +// && !(_exportIIDs || topic.getItemIdentifiers().isEmpty()); +// } + + private TopicReference[] _getSubjectIdentifiers(Topic topic) { + return _getLocators(topic, TopicReference.SID, topic.getSubjectIdentifiers()); + } + + private TopicReference[] _getSubjectLocators(Topic topic) { + return _getLocators(topic, TopicReference.SLO, topic.getSubjectLocators()); + } + + private TopicReference[] _getItemIdentifiers(Topic topic) { + return _getLocators(topic, TopicReference.IID, topic.getItemIdentifiers()); + } + + private TopicReference[] _getLocators(Topic topic, int kind, Set<Locator> locs) { + if (locs.isEmpty()) { + return new TopicReference[0]; + } + Collection<TopicReference> refs = new ArrayList<TopicReference>(locs.size()); + if (kind == TopicReference.SID) { + for (Locator loc: locs) { + refs.add(TopicReference.createSubjectIdentifier(loc.toExternalForm())); + } + } + else if (kind == TopicReference.IID) { + for (Locator loc: locs) { + refs.add(TopicReference.createItemIdentifier(loc.toExternalForm())); + } + } + else if (kind == TopicReference.SLO) { + for (Locator loc: locs) { + refs.add(TopicReference.createSubjectLocator(loc.toExternalForm())); + } + } + refs.remove(_getTopicReference(topic)); + TopicReference[] refArray = refs.toArray(new TopicReference[refs.size()]); + //Arrays.sort(locArray, _locComparator); + return refArray; + } + + private Name[] _getNames(Topic topic) { + Set<Name> names_ = topic.getNames(); + Name[] names = names_.toArray(new Name[names_.size()]); + Arrays.sort(names, _nameComparator); + return names; + } + + /** + * + * + * @param topic + * @return + */ + private Occurrence[] _getOccurrences(Topic topic) { + Set<Occurrence> occs_ = topic.getOccurrences(); + Occurrence[] occs = occs_.toArray(new Occurrence[occs_.size()]); + Arrays.sort(occs, _occComparator); + return occs; + } + + private void _writeTypeInstance(Topic type, boolean wantSemicolon) throws IOException { + _writeSemicolon(wantSemicolon); + _out.write("isa "); + _writeTopicRef(type); + } + +// private void _writeSupertypeSubtype(Topic supertype, boolean wantSemicolon) throws IOException { +// _writeSemicolon(wantSemicolon); +// _out.write("ako "); +// _writeTopicRef(supertype); +// } + + private void _writeOccurrence(IOccurrence occ, boolean wantSemicolon) throws IOException { + _writeSemicolon(wantSemicolon); + _writeTopicRef(occ.getType()); + _out.write(": "); + _writeLiteral(occ.getLiteral()); + _writeScope(occ); + _writeReifier(occ); + } + + private void _writeName(IName name, boolean wantSemicolon) throws IOException { + _writeSemicolon(wantSemicolon); + _out.write("- "); + Topic type = name.getType(); + if (!type.equals(_defaultNameType)) { + _writeTopicRef(type); + _out.write(": "); + } + _writeString(name.getValue()); + _writeScope(name); + _writeReifier(name); + Variant[] variants = name.getVariants().toArray(new Variant[0]); + Arrays.sort(variants, _variantComparator); + for (Variant variant: variants) { + _writeVariant((IVariant) variant); + } + } + + private void _writeVariant(IVariant variant) throws IOException { + _out.write(" ("); + _writeLiteral(variant.getLiteral()); + _writeScope(variant); + _out.write(')'); + } + + private void _writeAssociation(Association assoc) throws IOException { + _newline(); + _writeTopicRef(assoc.getType()); + _out.write('('); + Role[] roles = assoc.getRoles().toArray(new Role[0]); + Arrays.sort(roles, _roleComparator); + for (Role role: roles) { + _writeTopicRef(role.getType()); + _out.write(": "); + _writeTopicRef(role.getPlayer()); + if (role.getReifier() != null) { + _out.write(" #( If you found a reason why a role should be reified, write us )# "); + _writeReifier(role); + } + } + _out.write(')'); + _writeScope((IScoped) assoc); + _writeReifier(assoc); + _newline(); + } + + private void _writeSemicolon(boolean wantSemicolon) throws IOException { + if (wantSemicolon) { + _out.write(';'); + _newline(); + if (_prettify) { + _out.write(" "); + } + } + } + + /** + * + * + * @param scoped + * @throws IOException + */ + private void _writeScope(IScoped scoped) throws IOException { + IScope scope = scoped.getScopeObject(); + if (!scope.isUnconstrained()) { + Topic[] themes = scope.asSet().toArray(new Topic[scope.size()]); + Arrays.sort(themes, _topicComparator); + _out.write(" @"); + boolean wantComma = false; + for (Topic theme: themes) { + if (wantComma) { + _out.write(", "); + } + _writeTopicRef(theme); + wantComma = true; + } + } + } + + /** + * + * + * @param topic + * @throws IOException + */ + private void _writeTopicRef(Topic topic) throws IOException { + _writeTopicRef(_getTopicReference(topic)); + } + + private void _writeTopicRef(TopicReference topicRef) throws IOException { + _writeTopicRef(topicRef, false); + } + + private void _writeTopicRef(TopicReference topicRef, boolean wantSemicolon) throws IOException { + _writeSemicolon(wantSemicolon); + switch (topicRef.type) { + case TopicReference.ID: + _out.write(topicRef.reference); + return; + case TopicReference.IID: + _out.write('^'); + break; + case TopicReference.SLO: + _out.write("= "); + break; + case TopicReference.SID: + break; + default: + throw new RuntimeException("Internal error: Cannot match topic reference type " + topicRef.type); + } + _writeLocator(topicRef.reference); + } + + private void _writeString(String string) throws IOException { + //TODO: Escape + _out.write('"'); + _out.write(string); + _out.write('"'); + } + + private void _writeLocator(String reference) throws IOException { + _out.write("<" + reference + ">"); + } + + private TopicReference _getTopicReference(Topic topic) { + TopicReference ref = _topic2Reference.get(topic); + if (ref == null) { + final boolean hasIIds = !(_exportIIDs || topic.getItemIdentifiers().isEmpty()); + final boolean hasSids = !topic.getSubjectIdentifiers().isEmpty(); + final boolean hasSlos = !topic.getSubjectLocators().isEmpty(); + if (!hasIIds) { + if (hasSids) { + ref = hasSlos ? null : TopicReference.createSubjectIdentifier(topic.getSubjectIdentifiers().iterator().next().toExternalForm()); + } + else if (hasSlos) { + ref = TopicReference.createSubjectLocator(topic.getSubjectLocators().iterator().next().toExternalForm()); + } + } + if (ref == null) { + if (topic.getItemIdentifiers().size() == 1) { + final String iid = topic.getItemIdentifiers().iterator().next().toExternalForm(); + int idx = iid.lastIndexOf('#'); + if (idx > 0) { + String id = iid.substring(idx + 1); + ref = _isValidId(id) ? TopicReference.createId(id) + : TopicReference.createItemIdentifier("#" + id); + } + else { + ref = TopicReference.createItemIdentifier(iid); + } + } + } + if (ref == null) { + String iri = null; + for (Locator iid: topic.getItemIdentifiers()) { + String addr = iid.getReference(); + int idx = addr.lastIndexOf('#'); + if (idx < 0) { + continue; + } + else { + String id = addr.substring(idx+1); + iri = _isValidId(id) ? id : null; + } + } + if (iri == null) { + iri = "id-" + topic.getId(); + } + ref = TopicReference.createId(iri); + } + _topic2Reference.put(topic, ref); + } + return ref; + } + + private boolean _isValidId(String id) { + return _ID_PATTERN.matcher(id).matches(); + } + + /** + * Writes a literal. + * + * If the datatype is xsd:anyURI or xsd:string, the datatype is omitted. + * If the datatype is natively supported by CTM (like xsd:integer, xsd:decimal) + * the quotes and the datatype are omitted. + * + * @param lit The literal to serialize. + * @throws IOException In case of an error. + */ + private void _writeLiteral(ILiteral lit) throws IOException { + final Locator datatype = lit.getDatatype(); + final String value = lit.getValue(); + if (XSD.ANY_URI.equals(datatype)) { + _writeLocator(value); + } + else if (!XSD.STRING.equals(datatype) + && _isNativelySupported(lit)) { + _out.write(value); + } + else { + _writeString(value); + if (!XSD.STRING.equals(datatype)) { + _out.write("^^"); + String datatypeIRI = datatype.toExternalForm(); + if (datatypeIRI.startsWith(Namespace.XSD)) { + _out.write("xsd:"); + _out.write(datatypeIRI.substring(datatypeIRI.lastIndexOf('#')+1)); + } + else { + _writeLocator(datatypeIRI); + } + } + } + } + + private boolean _isNativelySupported(ILiteral literal) { + Locator datatype = literal.getDatatype(); + return XSD.STRING.equals(datatype) + || XSD.ANY_URI.equals(datatype) + || XSD.DECIMAL.equals(datatype) + || XSD.INTEGER.equals(datatype) + || XSD.DATE.equals(datatype) + || XSD.DATE_TIME.equals(datatype) + || (XSD.DOUBLE.equals(datatype) + && ("INF".equals(literal.getValue())) + || "-INF".equals(literal.getValue())); + } + + /** + * Writes the reifier if <tt>reifiable</tt> is reified. + * + * @param reifiable The reifiable construct. + * @throws IOException If an error occurs. + */ + private void _writeReifier(Reifiable reifiable) throws IOException { + Topic reifier = reifiable.getReifier(); + if (reifier == null) { + return; + } + _out.write(" ~ "); + _writeTopicRef(reifier); + } + + private void _newline() throws IOException { + _out.write('\n'); + } + + private void _writeSection(String name) throws IOException { + _newline(); + _newline(); + _out.write("#-- " + name); + _newline(); + } + + /** + * Writes a warning msg to the log. + * + * This method is used to inform the user that the serialized topic map + * is not valid. + * + * @param msg The warning message. + */ + private static void _reportInvalid(final String msg) { + LOG.warning("Invalid CTM: '" + msg + "'"); + } + + + + private static final class TopicReference { + static final int + ID = 0, + SID = 1, + SLO = 2, + IID = 3; + final int type; + final String reference; + + private TopicReference(int type, String reference) { + this.type = type; + this.reference = reference; + } + + public static TopicReference createId(String reference) { + return new TopicReference(ID, reference); + } + + public static TopicReference createSubjectIdentifier(String reference) { + return new TopicReference(SID, reference); + } + + public static TopicReference createSubjectLocator(String reference) { + return new TopicReference(SLO, reference); + } + + public static TopicReference createItemIdentifier(String reference) { + return new TopicReference(IID, reference); + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TopicReference)) { + return false; + } + TopicReference other = (TopicReference) obj; + return (type == other.type && reference.equals(other.reference)); + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return type + reference.hashCode(); + } + + } + + + /* + * Comparators. + */ + + /** + * Topic comparator. + * - Topics with less types are considered less than others with types. + * - Topics with less subject identifiers are considered less than others with sids. + * - Topics with less subject locators are considered less than other with slos + * - Topics with less item identifiers are less than others with iids + * + */ + private final class TopicComparator implements Comparator<Topic> { + + /* (non-Javadoc) + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + @Override + public int compare(Topic o1, Topic o2) { + if (o1 == o2) { + return 0; + } + int res = o1.getTypes().size() - o2.getTypes().size(); + if (res == 0) { + res = _locSetComparator.compare(o1.getSubjectIdentifiers(), o2.getSubjectIdentifiers()); + if (res == 0) { + res = _locSetComparator.compare(o1.getSubjectLocators(), o2.getSubjectLocators()); + if (res == 0) { + res = _locSetComparator.compare(o1.getItemIdentifiers(), o2.getItemIdentifiers()); + } + } + } + return res; + } + } + + /** + * Abstract comparator that provides some utility methods which handle common + * comparisons. + */ + private abstract class AbstractComparator<T> implements Comparator<T> { + int compareString(String o1, String o2) { + if (o1 == null && o2 != null) { + _reportInvalid("The first string value is null"); + return -1; + } + if (o1 != null && o2 == null) { + _reportInvalid("The second string value is null"); + return +1; + } + return o1.compareTo(o2); + } + /** + * Extracts the type of the typed Topic Maps constructs and compares + * the topics. + * + * @param o1 The first typed Topic Maps construct. + * @param o2 The second typed Topic Maps construct. + * @return A negative integer, zero, or a positive integer as the + * first argument is less than, equal to, or greater than the + * second. + */ + int compareType(Typed o1, Typed o2) { + return _topicComparator.compare(o1.getType(), o2.getType()); + } + /** + * Extracts the scope of the scoped Topic Maps constructs and compares + * them. + * + * @param o1 The first scoped Topic Maps construct. + * @param o2 The second scoped Topic Maps construct. + * @return A negative integer, zero, or a positive integer as the + * first argument is less than, equal to, or greater than the + * second. + */ + int compareScope(Scoped o1, Scoped o2) { + return _scopeComparator.compare(o1.getScope(), o2.getScope()); + } + /** + * Extracts the reifier of the reifiable Topic Maps constructs and + * compares them. + * + * @param o1 The first reifiable Topic Maps construct. + * @param o2 The second reifiable Topic Maps construct. + * @return A negative integer, zero, or a positive integer as the + * first argument is less than, equal to, or greater than the + * second. + */ + int compareReifier(Reifiable o1, Reifiable o2) { + Topic reifier1 = o1.getReifier(); + Topic reifier2 = o2.getReifier(); + int res = 0; + if (reifier1 == null) { + res = reifier2 == null ? 0 : -1; + } + else if (reifier2 == null) { + res = 1; + } + return res != 0 ? res : _topicComparator.compare(reifier1, reifier2); + } + } + + /** + * Enhances the {@link AbstractComparator} with a method to compare the + * value and datatype of an occurrence or variant. + */ + private abstract class AbstractDatatypeAwareComparator<T> extends AbstractComparator<T> { + /** + * Compares the value and datatype of the occurrences / variants. + * + * @param o1 The first occurrence / variant. + * @param o2 The second occurrence / variant. + * @return A negative integer, zero, or a positive integer as the + * first argument is less than, equal to, or greater than the + * second. + */ + int compareValueDatatype(DatatypeAware o1, DatatypeAware o2) { + ILiteral lit1 = ((ILiteralAware) o1).getLiteral(); + ILiteral lit2 = ((ILiteralAware) o2).getLiteral(); + int res = 0; + if (_isNativelySupported(lit1)) { + res = _isNativelySupported(lit2) ? 0 : -1; + } + else if (_isNativelySupported(lit2)) { + res = 1; + } + if (res == 0) { + res = compareString(lit1.getDatatype().getReference(), lit2.getDatatype().getReference()); + if (res == 0) { + res = compareString(lit1.getValue(), lit2.getValue()); + } + } + return res; + } + } + + /** + * Association comparator. + * + */ + private final class AssociationComparator extends AbstractComparator<Association> { + + private Comparator<Set<Role>> _roleSetComparator; + + AssociationComparator() { + //_roleSetComparator = new RoleSetComparator(); + } + + /* (non-Javadoc) + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + @Override + public int compare(Association o1, Association o2) { + if (o1 == o2) { + return 0; + } + int res = compareType(o1, o2); + if (res == 0) { + //res = _roleSetComparator.compare(o1.getRoles(), o2.getRoles()); + if (res == 0) { + res = compareScope(o1, o2); + } + } + return res; + } + } + + /** + * Role comparator which ignores the parent association. This comparator + * is meant to be used for roles where the parent is known to be equal or + * unequal. + */ + private class RoleComparator extends AbstractComparator<Role> { + + /* (non-Javadoc) + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + @Override + public int compare(Role o1, Role o2) { + if (o1 == o2) { + return 0; + } + int res = compareType(o1, o2); + if (res == 0) { + res = _topicComparator.compare(o1.getPlayer(), o2.getPlayer()); + } + return res; + } + } + + /** + * Occurrence comparator. + * - Occs in the UCS are less than ones with a special scope. + * - Occs which are not reified are less than ones which are reified. + */ + private final class OccurrenceComparator extends AbstractDatatypeAwareComparator<Occurrence> { + + /* (non-Javadoc) + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + @Override + public int compare(Occurrence o1, Occurrence o2) { + if (o1 == o2) { + return 0; + } + int res = compareType(o1, o2); + if (res == 0) { + res = compareScope(o1, o2); + if (res == 0) { + res = compareReifier(o1, o2); + if (res == 0) { + res = compareValueDatatype(o1, o2); + } + } + } + return res; + } + + } + + /** + * Name comparator. + * - Names with the default name type are less than names with a non-standard type. + * - Names in the UCS are less than ones with a special scope. + * - Names with no variants are less than ones with variants + * - Names which are not reified are less than ones which are reified. + */ + private final class NameComparator extends AbstractComparator<Name> { + + /* (non-Javadoc) + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + @Override + public int compare(Name o1, Name o2) { + if (o1 == o2) { + return 0; + } + int res = compareType(o1, o2); + if (res == 0) { + res = compareScope(o1, o2); + if (res == 0) { + res = o1.getVariants().size() - o2.getVariants().size(); + if (res == 0) { + res = compareReifier(o1, o2); + if (res == 0) { + res = compareString(o1.getValue(), o2.getValue()); + } + } + } + } + return res; + } + + /* (non-Javadoc) + * @see org.tinytim.mio.CTMTopicMapWriter.AbstractComparator#compareType(org.tmapi.core.Typed, org.tmapi.core.Typed) + */ + @Override + int compareType(Typed o1, Typed o2) { + Topic type1 = o1.getType(); + Topic type2 = o2.getType(); + int res = 0; + if (type1.equals(_defaultNameType)) { + res = type2.equals(type1) ? 0 : -1; + } + else if (type2.equals(_defaultNameType)) { + res = 1; + } + return res != 0 ? res : super.compareType(o1, o2); + } + + } + + /** + * Variant comparator. + * - Variants with a lesser scope size are less. + * - Variants which are not reified are less than ones which are reified. + */ + private final class VariantComparator extends AbstractDatatypeAwareComparator<Variant> { + /* (non-Javadoc) + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + @Override + public int compare(Variant o1, Variant o2) { + if (o1 == o2) { + return 0; + } + int res = compareScope(o1, o2); + if (res == 0) { + res = compareReifier(o1, o2); + if (res == 0) { + res = compareValueDatatype(o1, o2); + } + } + return res; + } + } + + /** + * Comparator which compares the size of the provided set. + * + * Iff the size of the sets are equal, another comparison method is used + * to compare the content of the sets. + */ + private abstract class AbstractSetComparator<T> implements Comparator<Set<T>> { + + /* (non-Javadoc) + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + @Override + public int compare(Set<T> o1, Set<T> o2) { + int s1 = o1.size(); + int s2 = o2.size(); + int res = s1 - s2; + if (res == 0) { + res = compareContent(o1, o2, s1); + } + return res; + } + + /** + * Called iff the size of the sets is equal. + * + * This method is used to compare the content of the sets. + * + * @param o1 The first set. + * @param o2 The second set. + * @param size The size of the set(s). + * @return A negative integer, zero, or a positive integer as the + * first argument is less than, equal to, or greater than the + * second. + */ + abstract int compareContent(Set<T> o1, Set<T> o2, int size); + } + + /** + * Compares the scope of two scoped Topic Maps constructs. + */ + private final class ScopeComparator extends AbstractSetComparator<Topic> { + + @Override + int compareContent(Set<Topic> o1, Set<Topic> o2, int size) { + int res = 0 ; + Topic[] topics1 = o1.toArray(new Topic[size]); + Topic[] topics2 = o2.toArray(new Topic[size]); + Arrays.sort(topics1, _topicComparator); + Arrays.sort(topics2, _topicComparator); + for (int i=0; i < size && res == 0; i++) { + res = _topicComparator.compare(topics1[i], topics2[i]); + } + return res; + } + } + + /** + * Compares {@link org.tmapi.core.Locator}s. + */ + private final class LocatorComparator implements Comparator<Locator> { + + /* (non-Javadoc) + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + @Override + public int compare(Locator o1, Locator o2) { + if (o1 == o2) { + return 0; + } + return o1.getReference().compareTo(o2.getReference()); + } + + } + + /** + * Comparator for sets of {@link org.tmapi.core.Locator}s. + */ + private final class LocatorSetComparator extends AbstractSetComparator<Locator> { + + /* (non-Javadoc) + * @see org.tinytim.mio.CTMTopicMapWriter.AbstractSetComparator#compareContent(java.util.Set, java.util.Set, int) + */ + @Override + int compareContent(Set<Locator> o1, Set<Locator> o2, int size) { + int res = 0; + Locator[] locs1 = o1.toArray(new Locator[size]); + Locator[] locs2 = o2.toArray(new Locator[size]); + Arrays.sort(locs1, _locComparator); + Arrays.sort(locs2, _locComparator); + for (int i=0; i < size && res == 0; i++) { + res = _locComparator.compare(locs1[i], locs2[i]); + } + return res; + } + } + +} Property changes on: tinytim-mio/trunk/src/main/java/org/tinytim/mio/CTMTopicMapWriter.java ___________________________________________________________________ Added: svn:keywords + Rev Date Id Added: svn:eol-style + native This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |