/*
* #!
* Ontopia Engine
* #-
* Copyright (C) 2001 - 2013 The Ontopia Project
* #-
* 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 net.ontopia.topicmaps.utils.ltm;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import net.ontopia.infoset.core.LocatorIF;
import net.ontopia.topicmaps.core.AssociationIF;
import net.ontopia.topicmaps.core.AssociationRoleIF;
import net.ontopia.topicmaps.core.TopicNameIF;
import net.ontopia.topicmaps.core.DataTypes;
import net.ontopia.topicmaps.core.OccurrenceIF;
import net.ontopia.topicmaps.core.ReifiableIF;
import net.ontopia.topicmaps.core.ScopedIF;
import net.ontopia.topicmaps.core.TMObjectIF;
import net.ontopia.topicmaps.core.TopicIF;
import net.ontopia.topicmaps.core.TopicMapIF;
import net.ontopia.topicmaps.core.TopicMapWriterIF;
import net.ontopia.topicmaps.core.TypedIF;
import net.ontopia.topicmaps.core.VariantNameIF;
import net.ontopia.topicmaps.core.index.ClassInstanceIndexIF;
import net.ontopia.topicmaps.utils.PSI;
import net.ontopia.topicmaps.utils.TopicStringifiers;
import net.ontopia.utils.DeciderIF;
import net.ontopia.topicmaps.utils.deciders.TMExporterDecider;
import net.ontopia.utils.IteratorComparator;
import net.ontopia.utils.OntopiaRuntimeException;
import net.ontopia.utils.StringUtils;
import net.ontopia.utils.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* PUBLIC: Exports topic maps to the LTM 1.3 interchange format.
* @since 2.2
*/
public class LTMTopicMapWriter implements TopicMapWriterIF {
public static final String PROPERTY_PREFIXES = "prefixes";
public static final String PROPERTY_FILTER = "filter";
public static final String PROPERTY_PRESERVE_IDS = "preserveIds";
static Logger log = LoggerFactory.getLogger(LTMTopicMapWriter.class.getName());
protected String encoding; // the encoding reported on the first line
protected boolean preserveIds;
protected Map<String, Integer> roleCounter;
protected Map<String, Boolean> rolesCounted;
protected Writer out;
protected Calendar calendar;
protected String base;
protected DeciderIF<Object> filter;
// Constrains which topic map constructs should be included in the exported
// Ltm file.
// Compares associations for correct output order.
protected Comparator<AssociationIF> associationComparator;
// Compares base names by their scope and then by their value.
protected Comparator<TopicNameIF> baseNameComparator;
// Compares TMObjects by their elementId.
protected Comparator<TopicIF> elementIdComparator;
// Compares supertype-subtype associations for correct output order.
protected Comparator<AssociationIF> supersubComparator;
// Compares supertype-subtype association roles for correct output order.
protected Comparator<AssociationRoleIF> supersubRoleComparator;
// Compares occurrences for correct output order.
protected Comparator<OccurrenceIF> occurrenceComparator;
// Compares collections of reifying topics by the element ids(in order).
protected Comparator<Collection<TopicIF>> reifierComparator;
// Compares association roles for correct output order.
protected Comparator<AssociationRoleIF> roleComparator;
// Compares collections of scoping topics by the element ids(in order).
protected Comparator<Collection<TopicIF>> scopeComparator;
// Compares topics for correct output order.
protected Comparator<TopicIF> topicComparator;
// Compares variant names for correct output order.
protected Comparator<VariantNameIF> variantComparator;
protected IdManager idManager;
protected String groupString1;
private Map<String, String> prefixes = new HashMap<String, String>();
/**
* PUBLIC: Create an LTMTopicMapWriter that writes to a given
* OutputStream in UTF-8. <b>Warning:</b> Use of this method is
* discouraged, as it is very easy to get character encoding errors
* with this method.
* @param stream Where the output should be written.
*/
public LTMTopicMapWriter(OutputStream stream) throws IOException {
this(stream, "utf-8");
}
/**
* PUBLIC: Create an LTMTopicMapWriter that writes to a given
* OutputStream in the given encoding.
* @param stream Where the output should be written.
* @param encoding The desired character encoding.
*/
public LTMTopicMapWriter(OutputStream stream, String encoding)
throws IOException {
this(new OutputStreamWriter(stream, encoding), encoding);
}
/**
* PUBLIC: Create an LTMTopicMapWriter that writes to a given Writer.
* @param out Where the output should be written.
* @deprecated
*/
public LTMTopicMapWriter(Writer out) {
this(out, null);
}
/**
* PUBLIC: Create an LTMTopicMapWriter that writes to a given Writer.
* @param out Where the output should be written.
* @param encoding The encoding used by the writer. This is the encoding
* that will be declared on the first line of the LTM file. It must be
* reported, because there is no way for the LTMTopicMapWriter to know
* what encoding the writer uses.
* @since 4.0
*/
public LTMTopicMapWriter(Writer out, String encoding) {
this.encoding = encoding;
this.out = out;
calendar = new GregorianCalendar();
associationComparator = new AssociationComparator();
roleComparator = new AssociationRoleFrequencyComparator();
baseNameComparator = new TopicNameComparator();
elementIdComparator = new ElementIdComparator();
supersubComparator = new SupersubComparator();
supersubRoleComparator = new SupersubRoleComparator();
occurrenceComparator = new OccurrenceComparator();
reifierComparator = new CollectionComparator<TopicIF>(new ElementIdComparator());
scopeComparator = new CollectionComparator<TopicIF>(new ElementIdComparator());
topicComparator = new TopicComparator();
variantComparator = new VariantComparator();
this.preserveIds = true;
this.filter = null;
}
/**
* PUBLIC: Set whether IDs should be preserved or generated.
* @param preserveIds Should be set to true if IDs should be preserved.
*/
public void setPreserveIds(boolean preserveIds) {
this.preserveIds = preserveIds;
}
/**
* PUBLIC: Sets the filter that decides which topic map constructs
* are accepted in the exprted ltm. Uses 'filter' to identify
* individual topic constructs as allowed or disallowed. TM
* constructs that depend on the disallowed topics are also
* disallowed.
* @param filter Places constraints on individual topicmap constructs.
*/
public void setFilter(DeciderIF<Object> filter) {
this.filter = new TMExporterDecider(filter);
}
/**
* Determines whether collection contains at least one element that is
* accepted by 'filter'.
* @param collection The collection to search.
* @return true iff 'collectino' contains at least one element that is
* accepted by filter.
*/
private boolean hasUnfiltered(Collection<? extends TMObjectIF> collection) {
Iterator<? extends TMObjectIF> it = collection.iterator();
while (it.hasNext())
if (filterOk(it.next()))
return true;
return false;
}
public boolean addPrefix(String key, String prefix) {
if (key == null) {
log.warn("Attempting to add prefix with null key for prefix '" + prefix + "'");
return false;
}
if (prefix == null) {
log.warn("Attempting to add prefix with null value for key '" + key + "'");
return false;
}
if (prefixes.containsKey(key) && (!prefixes.get(key).equals(prefix))) {
log.warn("Attempting to re-add prefix key '" + key + "' for prefix '" + prefix + "', sticking to original prefix '" + prefixes.get(key));
return false;
}
if (prefixes.containsValue(prefix)) {
log.warn("Attempting to re-add prefix '" + prefix + "' for key '" + key + "', ignoring key for this prefix");
return false;
}
prefixes.put(key, prefix);
return true;
}
/**
* PUBLIC: Writes out the given topic map.
*/
public void write(TopicMapIF tm) throws IOException {
LocatorIF baseLocator = tm.getStore().getBaseAddress();
base = (baseLocator == null) ? null : baseLocator.getExternalForm();
idManager = new IdManager();
ClassInstanceIndexIF classIndex = (ClassInstanceIndexIF)tm.getIndex(
"net.ontopia.topicmaps.core.index.ClassInstanceIndexIF");
// Check if there are any untyped associations, association roles or
// occurrences
boolean existsUntyped = (hasUnfiltered(classIndex.getAssociationRoles(null))
|| hasUnfiltered(classIndex.getAssociations(null)) || hasUnfiltered(classIndex
.getOccurrences(null)));
Collection<TopicIF> topics = tm.getTopics();
recordIds(topics);
// Get all relevant topics sorted.
topics = sort(filterCollection(topics), topicComparator);
// Get the topic(s) that reifies the topicmap.
TopicIF reifier = tm.getReifier();
// FIXME: no need to treat this as a collection anymore
Collection<TopicIF> tmReifiers = (reifier == null ? Collections.<TopicIF>emptySet() : Collections.singleton(reifier));
tmReifiers = filterCollection(tmReifiers);
topics.removeAll(tmReifiers);
// Get all associations.
Collection<AssociationIF> allAssociations = filterCollection(tm.getAssociations());
boolean existsUnspecified = existsUnspecifiedRolePlayer(allAssociations);
// Sort all the topics.
Collection<TopicIF> topicInstances = sort(topics, topicComparator);
// Filter out the topics that are used to type other topics.
Collection<TopicIF> topicTypes = topicTypes(topics, topicInstances);
// Filter out the topics that are used to type association roles.
Collection<TopicIF> roleTypes = roleTypes(allAssociations, topicInstances);
// Filter out the topics that are used to type associations.
Collection<TopicIF> associationTypes = associationTypes(allAssociations,
topicInstances);
// Filter out the topics that are used to type occurrences.
Collection<TopicIF> occurrenceTypes = occurrenceTypes(topics, topicInstances);
// Count up the number of associations that a particular role, e.g.
// "player : type" takes part in.
countRoles(allAssociations);
// Get all the associations in correct output order.
allAssociations = sort(allAssociations, associationComparator);
// Filter out all superclass/subclass associations
TopicIF supersubtype = tm.getTopicBySubjectIdentifier(PSI
.getXTMSuperclassSubclass());
Collection<AssociationIF> supersubAssociations = classIndex.getAssociations(supersubtype);
supersubAssociations = filterCollection(supersubAssociations);
allAssociations.removeAll(supersubAssociations);
// Filter out the associations that have roles reifying the topic map.
Collection<AssociationIF> tmReifierAssociations = playerAssociations(tmReifiers,
allAssociations);
// Output preamble.
if (encoding != null)
out.write("@\"" + encoding + "\"\n");
out.write("#VERSION \"1.3\"\n");
out.write("/*\n Generator: Ontopia");
out.write("\n Date: ");
out.write(minLengthString(calendar.get(Calendar.YEAR), 4));
out.write('-');
out.write(minLengthString(calendar.get(Calendar.MONTH)+1, 2));
out.write('-');
out.write(minLengthString(calendar.get(Calendar.DAY_OF_MONTH), 2));
out.write(' ');
out.write(minLengthString(calendar.get(Calendar.HOUR_OF_DAY), 2));
out.write(':');
out.write(minLengthString(calendar.get(Calendar.MINUTE), 2));
out.write("\n*/\n");
for (String key : prefixes.keySet()) {
out.write("#PREFIX " + key + " @\"" + prefixes.get(key) + "\"\n");
}
// If necessary, output the prefix for untyped topic map constructs.
if (existsUntyped)
out.write("\n#PREFIX untyped @\"http://psi.ontopia.net/ltm/untyped#\"\n");
// If necessary, output prefix for unspecified topic map constructs(roles).
if (existsUnspecified) {
out.write("\n#PREFIX unspecified");
out.write(" @\"http://psi.ontopia.net/ltm/unspecified#\"\n");
}
// If necessary, output the TOPICMAP directive with any topic reification.
Iterator<TopicIF> tmReifiersIt = tmReifiers.iterator();
if (!tmReifiers.isEmpty()) {
out.write("\n/* ----------------- TOPIC MAP ----------------- */\n");
out.write("\n#TOPICMAP");
writeReifiers(tm, out);
out.write("\n");
// Output the topic map reifier(s)(usually one).
groupString1 = "";
tmReifiersIt = tmReifiers.iterator();
// Output the reifiers
while (tmReifiersIt.hasNext())
writeTopic(tmReifiersIt.next(), out, false);
}
// Output all associations that the tm reifier(s) are directly involved in.
groupString1 = "";
Iterator<AssociationIF> tmReifierAssociationsIt = tmReifierAssociations.iterator();
while (tmReifierAssociationsIt.hasNext())
writeAssociation(tmReifierAssociationsIt.next(), out,
false);
out.write("\n/* ----------------- ONTOLOGY ------------------ */\n");
out.write("\n/* ----------------- Topic Types --------------- */\n");
// Output all the topic types.
writeTopics(topicTypes);
out.write("\n/* ----------------- Type Hierarchy ------------ */\n");
// Write all supertype subtype associations
groupString1 = "";
Iterator<AssociationIF> hierarchyIt = sort(supersubAssociations, supersubComparator)
.iterator();
if (hierarchyIt.hasNext())
out.write("\n");
while (hierarchyIt.hasNext()) {
AssociationIF currentAssociation = hierarchyIt.next();
writeSupersub(currentAssociation, out);
}
out.write("\n/* ----------------- Role Types ---------------- */\n");
// Write all remaining association role types.
writeTopics(roleTypes);
out.write("\n/* ----------------- Association Types --------- */\n");
// Write all remaining association types
writeTopics(associationTypes);
out.write("\n/* ----------------- Occurrence Types ---------- */\n");
// Write all remaining occurrence types
writeTopics(occurrenceTypes);
out.write("\n/* ----------------- INSTANCES ----------------- */\n");
out.write("\n/* ----------------- Topics -------------------- */\n");
// Write all remaining instance topics
writeTopics(topicInstances);
out.write("\n/* ----------------- Associations -------------- */\n");
// Write all remaining associations
groupString1 = "";
Iterator<AssociationIF> associationsIt = allAssociations.iterator();
while (associationsIt.hasNext())
writeAssociation(associationsIt.next(), out);
out.flush();
}
/**
* Write a collection of topics.
*/
private void writeTopics(Collection<TopicIF> topics) throws IOException {
groupString1 = "";
Iterator<TopicIF> topicsIt = topics.iterator();
if (topicsIt.hasNext()
&& filterOk(topics.iterator().next().getTypes()))
out.write("\n");
while (topicsIt.hasNext())
writeTopic(topicsIt.next(), out);
}
/**
* Get all the associations of the roles played by 'rolePlayers'. Check if
* each of those associations is in 'associations'. Return those that are, and
* remove them from 'associations'.
* @param rolePlayers The role playing topics of the associations to return.
* @param associations All available associations that may be returned.
* @return The associations of the roles played by 'rolePlayers' and that are
* in 'associations'.
*/
private SortedSet<AssociationIF> playerAssociations(Collection<TopicIF> rolePlayers,
Collection<AssociationIF> associations)
{
SortedSet<AssociationIF> retVal = new TreeSet<AssociationIF>(associationComparator);
Iterator<TopicIF> rolePlayersIt = rolePlayers.iterator();
while (rolePlayersIt.hasNext()) {
Iterator<AssociationRoleIF> rolesIt = rolePlayersIt.next().getRoles().iterator();
while (rolesIt.hasNext()) {
AssociationIF assoc = rolesIt.next().getAssociation();
if (assoc != null && associations.remove(assoc))
retVal.add(assoc);
}
}
return retVal;
}
/**
* Filter out the topics that are used to type other topics.
*/
private SortedSet<TopicIF> topicTypes(Collection<TopicIF> topics, Collection<TopicIF> topicInstances) {
SortedSet<TopicIF> typingTopics = new TreeSet<TopicIF>(topicComparator);
Iterator<TopicIF> it = topics.iterator();
while (it.hasNext()) {
TopicIF currentTopic = it.next();
Iterator<TopicIF> typesIt = currentTopic.getTypes().iterator();
while (typesIt.hasNext()) {
TopicIF currentType = typesIt.next();
if (topicInstances.remove(currentType))
typingTopics.add(currentType);
}
}
return typingTopics;
}
/**
* Filter out the topics that are used to type associations.
*/
private SortedSet<TopicIF> associationTypes(Collection<AssociationIF> associations,
Collection<TopicIF> topicInstances)
{
SortedSet<TopicIF> associationTypes = new TreeSet<TopicIF>(topicComparator);
Iterator<AssociationIF> it = associations.iterator();
while (it.hasNext()) {
TopicIF type = it.next().getType();
if (type != null && topicInstances.remove(type))
associationTypes.add(type);
}
return associationTypes;
}
/**
* Filter out the topics that are used to type association roles.
*/
private SortedSet<TopicIF> roleTypes(Collection<AssociationIF> associations, Collection<TopicIF> topicInstances)
{
SortedSet<TopicIF> roleTypes = new TreeSet<TopicIF>(topicComparator);
Iterator<AssociationIF> it = associations.iterator();
while (it.hasNext()) {
Iterator<TopicIF> typesIt = it.next().getRoleTypes().iterator();
while (typesIt.hasNext()) {
TopicIF currentType = typesIt.next();
if (topicInstances.remove(currentType))
roleTypes.add(currentType);
}
}
return roleTypes;
}
/**
* Filter out the topics that are used to type occurrences. Ignore topics
* whose occurrence instances are not accepted by filterOk().
*/
private SortedSet<TopicIF> occurrenceTypes(Collection<TopicIF> topics, Collection<TopicIF> topicInstances)
{
SortedSet<TopicIF> occurrenceTypes = new TreeSet<TopicIF>(topicComparator);
Iterator<TopicIF> it = topics.iterator();
while (it.hasNext()) {
TopicIF currentTopic = it.next();
Collection<OccurrenceIF> occurrences = currentTopic.getOccurrences();
// Get only occurrences that are accepted by filterOk().
occurrences = filterCollection(occurrences);
Iterator<OccurrenceIF> occurrencesIt = occurrences.iterator();
while (occurrencesIt.hasNext()) {
OccurrenceIF occurrence = occurrencesIt.next();
TopicIF type = occurrence.getType();
if (type != null && filterOk(type) && topicInstances.remove(type))
occurrenceTypes.add(type);
}
}
return occurrenceTypes;
}
/**
* Sort the given collection with the given comparator.
*/
private <E> SortedSet<E> sort(Collection<E> collection, Comparator<? super E> comparator) {
SortedSet<E> sorted = new TreeSet<E>(comparator);
sorted.addAll(collection);
return sorted;
}
private String getElementId(TopicIF topic) {
return idManager.getId(topic);
}
// --------------------------------------------------------------------
// Methods used on Topics
// --------------------------------------------------------------------
/**
* Write the given topic to 'out'. Write a header for each new topic type.
*/
private void writeTopic(TopicIF topic, Writer out) throws IOException {
writeTopic(topic, out, true);
}
/**
* Write the given topic to 'out'. If writeHeaders is true then write a header
* for each new topic type.
*/
private void writeTopic(TopicIF topic, Writer out, boolean writeHeaders)
throws IOException
{
if (filterOk(topic)) {
Collection<TopicIF> types = topic.getTypes();
types = sort(types, elementIdComparator);
types = filterCollection(types);
Iterator<TopicIF> typesIt = types.iterator();
String typeString = "";
String headerType = "";
if (typesIt.hasNext()) {
headerType = getElementId(typesIt.next());
typeString += " : " + headerType;
}
while (typesIt.hasNext())
typeString += " " + getElementId(typesIt.next());
// IF this is the first time a topic of the current type is written
// then write a header comment for this topic type.
if (writeHeaders && !headerType.equals(groupString1)) {
out.write("\n/* -- TT: ");
if (headerType.equals(""))
out.write("(untyped)");
else
out.write(headerType);
out.write(" -- */\n");
}
String id = getElementId(topic);
String idString = "[" + id + typeString;
String baseString = "\n" + createSpaces(idString.length());
// Get, filter and sort the basenames.
Collection<TopicNameIF> baseNames = filterCollection(topic.getTopicNames());
baseNames = filterCollection(baseNames);
baseNames = sort(baseNames, baseNameComparator);
Iterator<TopicNameIF> baseNamesIt = baseNames.iterator();
// Write the base names indented according to topic id and types.
if (baseNamesIt.hasNext()) {
out.write(idString);
out.write(" = ");
writeTopicName(baseNamesIt.next(), out, baseString);
} else
out.write(idString);
while (baseNamesIt.hasNext()) {
out.write(baseString);
out.write(" = ");
writeTopicName(baseNamesIt.next(), out, baseString);
}
// Write the subject locator(if any).
Collection<LocatorIF> subjectLocators = topic.getSubjectLocators();
subjectLocators = filterCollection(subjectLocators);
Iterator<LocatorIF> subjectLocatorsIt = subjectLocators.iterator();
while (subjectLocatorsIt.hasNext()) {
String externalForm = subjectLocatorsIt.next().getExternalForm();
out.write("\n ");
out.write("%\"");
out.write(escapeString(externalForm));
out.write('"');
}
// Write subject indicators, one per line.
Collection<LocatorIF> subjectIndicators = topic.getSubjectIdentifiers();
subjectIndicators = filterCollection(subjectIndicators);
Iterator<LocatorIF> subjectIndicatorsIt = subjectIndicators.iterator();
while (subjectIndicatorsIt.hasNext()) {
String externalForm = subjectIndicatorsIt.next().getExternalForm();
boolean skip = false;
if (prefixes.size() > 0) {
int colonIndex = id.indexOf(":");
if (colonIndex > -1) {
String key = id.substring(0, colonIndex);
String prefix = prefixes.get(key);
String suffix = id.substring(colonIndex + 1);
if (prefix != null) {
skip = externalForm.equals(prefix + suffix);
}
}
}
if (!skip && (base == null || !externalForm.startsWith(base))) {
out.write("\n ");
out.write("@\"");
out.write(escapeString(externalForm));
out.write('"');
}
}
out.write("]\n");
// Write the occurrences of this topic.
Collection<OccurrenceIF> occurrences = topic.getOccurrences();
occurrences = filterCollection(occurrences);
occurrences = sort(occurrences, occurrenceComparator);
Iterator<OccurrenceIF> occurrencesIt = occurrences.iterator();
while (occurrencesIt.hasNext())
writeOccurrence(occurrencesIt.next(), out);
groupString1 = headerType;
}
}
/**
* Write the given association to 'out'. Write headers for every new
* association type.
*/
private void writeAssociation(AssociationIF association, Writer out)
throws IOException
{
writeAssociation(association, out, true);
}
/**
* Write the given association to 'out'. If writeHeaders is true, write
* headers for every new association type.
*/
private void writeAssociation(AssociationIF association, Writer out,
boolean writeHeaders) throws IOException
{
if (filterOk(association)) {
String elementId = lazyTypeElementId(association);
if (writeHeaders && !elementId.equals(groupString1)) {
out.write("\n/* -- AT: ");
out.write(elementId);
out.write(" */\n");
}
out.write(elementId);
out.write("( ");
// Get and sort the roles of this association.
Collection<AssociationRoleIF> roles = sort(association.getRoles(), roleComparator);
Iterator<AssociationRoleIF> rolesIt = roles.iterator();
// Write the roles of this association.
if (rolesIt.hasNext())
writeAssociationRole(rolesIt.next(), out);
if (maxRolesOf(association) == 2 && rolesIt.hasNext()) {
out.write(", ");
writeAssociationRole(rolesIt.next(), out);
}
while (rolesIt.hasNext()) {
out.write(",\n" + repeatString(" ", elementId.length() + 2));
writeAssociationRole(rolesIt.next(), out);
}
out.write(" )");
// Write the names of the scoping topics of this association.
writeScope(association, out);
// Write the names of the reifying topics of this association.
writeReifiers(association, out);
out.write("\n");
groupString1 = elementId;
}
}
private String repeatString(String string, int length) {
return (length == 0) ? "" : string + repeatString(string, length - 1);
}
/**
* Write the given superclass-subclass association to 'out'.
*/
private void writeSupersub(AssociationIF association, Writer out)
throws IOException
{
String elementId = lazyTypeElementId(association);
out.write(elementId);
out.write("( ");
// Get and sort the roles of this association.
Collection<AssociationRoleIF> roles = association.getRoles();
roles = sort(roles, supersubRoleComparator);
Iterator<AssociationRoleIF> rolesIt = roles.iterator();
// Write the association roles.
if (rolesIt.hasNext())
writeAssociationRole(rolesIt.next(), out);
while (rolesIt.hasNext()) {
out.write(", ");
writeAssociationRole(rolesIt.next(), out);
}
out.write(" )");
// Write the names of the scoping topics of this association.
writeScope(association, out);
// Write the names of the reifying topics of this association.
writeReifiers(association, out);
out.write("\n");
groupString1 = elementId;
}
/**
* Write the given association role to 'out'.
*/
private void writeAssociationRole(AssociationRoleIF role, Writer out)
throws IOException
{
out.write(lazyPlayerElementId(role));
out.write(" : " + lazyTypeElementId(role));
// Write the names of the reifying topics of this association role.
writeReifiers(role, out);
}
/**
* Filter a whole collection of objects.
* @param unfiltered The objects to filter.
* @return A new collection containing all objects accepted by the filter, or
* if this.filter is null, returns the original collection.
*/
private <E> Collection<E> filterCollection(Collection<E> unfiltered) {
if (filter == null)
return unfiltered;
Collection<E> retVal = new ArrayList<E>();
Iterator<E> unfilteredIt = unfiltered.iterator();
while (unfilteredIt.hasNext()) {
E current = unfilteredIt.next();
if (filter.ok(current))
retVal.add(current);
}
return retVal;
}
/**
* Filter a single object..
* @param unfiltered The object to filter.
* @return True if the object is accepted by the filter or the filter is null.
* False otherwise.
*/
private boolean filterOk(Object unfiltered) {
if (filter == null)
return true;
return filter.ok(unfiltered);
}
/**
* Get the first VariantNameIF that is scoped by a single topic, having the
* given subject indicator.
* @param variants The variants to search through.
* @param si The subject indicator to search for.
* @return The first matching VariantNameIF, or null if none is found.
*/
private VariantNameIF firstNameWithScopingPSI(Collection<VariantNameIF> variants,
LocatorIF si)
{
VariantNameIF firstName = null;
Iterator<VariantNameIF> it = variants.iterator();
while (firstName == null && it.hasNext()) {
VariantNameIF currentVariant = it.next();
Collection<TopicIF> scope = filterCollection(currentVariant.getScope());
if (scope.size() == 1) {
TopicIF scopingTopic = scope.iterator().next();
Collection<LocatorIF> scopingPSIs = scopingTopic.getSubjectIdentifiers();
if (scopingPSIs.contains(si))
firstName = currentVariant;
}
}
return firstName;
}
/**
* Write the given TopicNameIF to the given Writer, after line breaks with the
* given indentString.
*/
private void writeTopicName(TopicNameIF baseName, Writer out,
String indentString) throws IOException
{
// Write the base name itself.
out.write('"');
out.write(escapeString(baseName.getValue()));
out.write('"');
// Get and sort the variants of this base name.
Collection<VariantNameIF> variants = baseName.getVariants();
variants = filterCollection(variants);
variants = sort(variants, variantComparator);
VariantNameIF sortName = firstNameWithScopingPSI(variants, PSI.getXTMSort());
VariantNameIF displayName = firstNameWithScopingPSI(variants, PSI
.getXTMDisplay());
if (sortName != null) {
variants.remove(sortName);
out.write("; \"");
out.write(escapeString(sortName.getValue()));
out.write('"');
}
if (displayName != null) {
if (sortName == null)
out.write("; ");
variants.remove(displayName);
out.write("; \"");
out.write(escapeString(displayName.getValue()));
out.write('"');
}
// Write the names of the scoping topics of this topic basename.
writeScope(baseName, out);
Iterator<VariantNameIF> variantIt = variants.iterator();
// Write the variants.
while (variantIt.hasNext()) {
out.write(indentString);
out.write(" ");
writeVariant(variantIt.next(), out);
}
}
/**
* Write the given TopicNameIF to the given Writer, after line breaks with the
* given indentString.
*/
private void writeVariant(VariantNameIF variant, Writer out)
throws IOException {
if (ObjectUtils.equals(variant.getDataType(), DataTypes.TYPE_STRING)) {
String value = variant.getValue();
if (value != null) {
out.write("(\"");
out.write(escapeString(value));
out.write('"');
// Write the names of the scoping topics of this variant.
writeScope(variant, out);
out.write(')');
}
} else {
out.write("/* WARNING: LTM 1.3 cannot represent variant names that");
out.write(" are not of xsd:string type, hence cannot represent object [");
out.write(variant.getObjectId());
out.write("] */");
}
}
/**
* Write one given occurrence to 'out'.
*/
private void writeOccurrence(OccurrenceIF occurrence, Writer out)
throws IOException
{
if (filterOk(occurrence)) {
String value = occurrence.getValue();
if (occurrence.getDataType().equals(DataTypes.TYPE_URI)) {
out.write(" {");
out.write(getElementId(occurrence.getTopic()));
out.write(", ");
out.write(lazyTypeElementId(occurrence));
out.write(", ");
out.write("\"");
out.write(occurrence.getLocator().getExternalForm());
out.write("\"");
out.write('}');
} else if (occurrence.getDataType().equals(DataTypes.TYPE_STRING)) {
out.write(" {");
out.write(getElementId(occurrence.getTopic()));
out.write(", ");
out.write(lazyTypeElementId(occurrence));
out.write(", ");
out.write("[[");
out.write(escapeInternalOccurrence(value));
out.write("]]");
out.write('}');
} else {
out.write("/* WARNING: LTM 1.3 cannot represent occurrences that");
out.write(" are not of xsd:string or xsd:anyURI type, hence cannot represent object [");
out.write(occurrence.getObjectId());
out.write("] */");
}
// Write the names of the scoping topics of this occurrence.
writeScope(occurrence, out);
// Write the names of the reifying topics of this occurrence.
writeReifiers(occurrence, out);
out.write("\n");
}
}
private void writeReifiers(ReifiableIF reifiable, Writer out)
throws IOException
{
// Write the reifier.
TopicIF reifier = reifiable.getReifier();
if (reifier != null)
out.write("~ " + getElementId(reifier));
}
/**
* Writes the names of the scoping topics of a given tmObject. Generates
* output like e.g. " / topic1 topic2 topic3".
*/
private void writeScope(ScopedIF tmObject, Writer out) throws IOException {
// Get and sort the scoping topics of this occurrence.
Collection<TopicIF> scope = tmObject.getScope();
// No need to filter scope. All of scope must be filterOk() to reach this
// method.
scope = sort(scope, elementIdComparator);
Iterator<TopicIF> scopeIt = scope.iterator();
// Write the scoping topics.
if (scopeIt.hasNext())
out.write(" /");
while (scopeIt.hasNext())
out.write(" " + getElementId(scopeIt.next()));
}
/**
* Counts one association + role combination(increment counter by 1).
*/
private void count(AssociationIF association, AssociationRoleIF role) {
String longkey = lazyTypeElementId(association) + " : "
+ lazyPlayerElementId(role) + " : " + lazyTypeElementId(role);
if (rolesCounted.get(longkey) == null) {
Integer value = getCount(association, role);
String key = lazyTypeElementId(association) + " : "
+ lazyTypeElementId(role);
roleCounter.put(key, new Integer(value.intValue() + 1));
rolesCounted.put(longkey, Boolean.FALSE);
}
}
private void countMaxRolesOf(AssociationIF association) {
int newCount = association.getRoles().size();
int oldCount = maxRolesOf(association);
if (oldCount < newCount)
roleCounter.put(lazyTypeElementId(association), new Integer(newCount));
}
private int maxRolesOf(AssociationIF association) {
String key = lazyTypeElementId(association);
Integer count = roleCounter.get(key);
return (count == null) ? 0 : count.intValue();
}
/**
* Counts the number of times each role appears within a particular type of
* associations, i.e. how often a triple like "associationType : rolePlayer :
* roleType" is repeated
*/
private void countRoles(Collection<AssociationIF> associations) {
roleCounter = new HashMap<String, Integer>();
rolesCounted = new HashMap<String, Boolean>();
Iterator<AssociationIF> associationsIt = associations.iterator();
while (associationsIt.hasNext()) {
AssociationIF association = associationsIt.next();
countMaxRolesOf(association);
Iterator<AssociationRoleIF> rolesIt = association.getRoles().iterator();
while (rolesIt.hasNext())
count(association, rolesIt.next());
}
}
/**
* Returns true iff there exists an association role with no player.
*/
private boolean existsUnspecifiedRolePlayer(Collection<AssociationIF> associations) {
Iterator<AssociationIF> associationsIt = associations.iterator();
while (associationsIt.hasNext()) {
AssociationIF association = associationsIt.next();
Iterator<AssociationRoleIF> rolesIt = association.getRoles().iterator();
while (rolesIt.hasNext()) {
if (rolesIt.next().getPlayer() == null)
return true;
}
}
return false;
}
/**
* Returns an id string for a given topic, as it should appear when preserving
* IDs.
*/
private String generateId(TopicIF topic) {
String name = TopicStringifiers.toString(topic);
String retVal;
if (name.equals("[No name]"))
retVal = idManager.makeId(topic, "id", true);
else {
String generatedId = StringUtils.normalizeId(name);
if (generatedId == null || isReservedId(generatedId)
|| allSameAs(generatedId, '_'))
retVal = idManager.makeId(topic, "id", true);
else {
retVal = idManager.makeId(topic, generatedId, false);
}
}
return retVal;
}
/**
* Get the count for a given association + role combination.
*/
private Integer getCount(AssociationIF association, AssociationRoleIF role) {
String key = lazyTypeElementId(association) + " : "
+ lazyTypeElementId(role);
Integer retVal = roleCounter.get(key);
if (retVal == null)
return new Integer(0);
return retVal;
}
/**
* Returns the element id of the player of a given associaiton role. For roles
* with no specified player, returns a default string.
*/
private String lazyPlayerElementId(AssociationRoleIF role) {
TopicIF player = role.getPlayer();
if (player == null)
return "unspecified:player";
return getElementId(player);
}
/**
* Returns the element id of the type of a given topicmap object. For untyped
* occurrences, associations and roles, returns a default string for untyped
* topicmap objects.
* @throws OntopiaRuntimeException for other untyped topic map objects.
*/
private String lazyTypeElementId(TypedIF tmObject) {
TopicIF type = tmObject.getType();
if (type == null) {
if (tmObject instanceof AssociationIF)
return "untyped:association";
if (tmObject instanceof AssociationRoleIF)
return "untyped:role";
if (tmObject instanceof OccurrenceIF)
return "untyped:occurrence";
throw new OntopiaRuntimeException("lazyTypeElementId can only be"
+ " applied to associations, association roles or" + " occurrences.");
}
return getElementId(type);
}
/**
* Returns an id string for a given topic, as it should appear when preserving
* IDs.
*/
private String preserveId(TopicIF topic) {
Iterator<LocatorIF> sourceLocators = topic.getItemIdentifiers().iterator();
while (sourceLocators.hasNext()) {
LocatorIF sourceLocator = sourceLocators.next();
String fragmentId = getFragment(sourceLocator);
if (!(fragmentId == null || isReservedId(fragmentId))) {
// If fragmentId ends with _n for some integer n
int lastPos = fragmentId.lastIndexOf('_');
String remaining = fragmentId.substring(lastPos + 1);
if (lastPos > 0 && remaining.length() > 0 && allDigits(remaining))
return idManager.makeId(topic, fragmentId.substring(0, lastPos + 1),
true);
if (fragmentId.length() > 0 &&
!(fragmentId.startsWith("id") &&
fragmentId.length() > 2 &&
allDigits(fragmentId.substring(2))))
return idManager.makeId(topic, fragmentId, false);
}
} // Found no appropriate source locator.
return idManager.makeId(topic, "id", true);
}
/**
* Creates ids for all the topics in 'topics'.
*/
private void recordIds(Collection<TopicIF> topics) {
Iterator<TopicIF> it = topics.iterator();
if (preserveIds) {
while (it.hasNext())
preserveId(it.next());
} else {
while (it.hasNext())
generateId(it.next());
}
}
/**
* Test whether source is a string that only contains digits.
*/
private static boolean allDigits(String source) {
for (int i = 0; i < source.length(); i++)
if (!Character.isDigit(source.charAt(i)))
return false;
return true;
}
/**
* Returns true iff all characters in 'source' are the same as in 'sameAs'.
*/
private static boolean allSameAs(String source, char sameAs) {
for (int i = 0; i < source.length(); i++)
if (source.charAt(i) != sameAs)
return false;
return true;
}
/**
* Returns a new string of a given length, containing all spaces.
*/
private static String createSpaces(int length) {
String retVal = "";
for (int i = 0; i < length; i++)
retVal += " ";
return retVal;
}
/**
* Replace any characters that can couse problems in strings by their
* corresponding unicode escape sequence.
*/
private static String escapeString(String source) {
String retVal = "";
for (int i = 0; i < source.length(); i++) {
char c = source.charAt(i);
if (c == '"')
retVal += "\\u000022";
else
retVal += c;
}
return retVal;
}
/**
* Replace any characters that can couse problems in external occurrences by
* their corresponding unicode escape sequence.
*/
private static String escapeInternalOccurrence(String source) {
String retVal = "";
for (int i = 0; i < source.length(); i++) {
char c = source.charAt(i);
if (c == ']')
retVal += "\\u00005D";
else
retVal += c;
}
return retVal;
}
/**
* Get the fragment identifier from a locator.
*/
private String getFragment(LocatorIF locator) {
String retVal = locator.getAddress();
int lastHashIndex = retVal.lastIndexOf('#') + 1;
if (lastHashIndex != 0)
retVal = retVal.substring(lastHashIndex);
else
retVal = "";
if (!validate((retVal)))
return null;
return retVal;
}
/**
* Checks whether the given id is a valid LTM ID, matching
* [A-Za-z_][-A-Za-z_0-9.]*
*/
private boolean validate(String id) {
if (id.length() == 0)
return false;
char ch = id.charAt(0);
if (!((ch >= 'A' && ch <= 'Z') ||
(ch >= 'a' && ch <= 'z') ||
ch == '_'))
return false;
for (int ix = 1; ix < id.length(); ix++) {
ch = id.charAt(ix);
if (!((ch >= 'A' && ch <= 'Z') ||
(ch >= 'a' && ch <= 'z') ||
(ch >= '0' && ch <= '9') ||
ch == '_' || ch == '-' || ch == '.'))
return false;
}
return true;
}
/**
* Checks if the give character is illegal in LTM identifiers.
* Currently matching non-ascii characters.
* @param id The string to check.
* @return true iff id matches any of the illegal characters.
*/
private static boolean containsIllegalCharacter(String id) {
for (int index = 0; index < id.length(); index++)
if (id.charAt(index) > 127)
return true;
return false;
}
/**
* @return true iff at least one of the subject indicators of the given topic
* matches the given uri
*/
private static boolean lazyHasLocator(TopicIF topic, LocatorIF uri) {
return topic != null && topic.getSubjectIdentifiers().contains(uri);
}
/**
* Test whether a given string is a reserved systematic ID, in other words,
* whether it's on the form "id"("_" | X) {0, .., 9}*, where X is an upper
* case character.
*/
private static boolean isReservedId(String fragmentId) {
return fragmentId.startsWith("id")
&& fragmentId.length() >= 3
&& (Character.isDigit(fragmentId.charAt(2)) || inRange('A', fragmentId
.charAt(2), 'Z')) && allDigits(fragmentId.substring(3));
}
private static boolean inRange(char lowerBound, char it, char upperBound) {
return lowerBound <= it && it <= upperBound;
}
/**
* Compare two strings(using .compareTo()). null strings are ordered before
* non-null strings.
*/
private static int lazyStringCompare(String source1, String source2) {
if (source1 == null) {
if (source2 == null)
return 0;
return -1;
}
if (source2 == null)
return 1;
int retVal = source1.compareToIgnoreCase(source2);
if (retVal == 0)
return source1.compareTo(source2);
return retVal;
}
/**
* Converts a given string to a string of some given minimum length.
*/
private static String minLengthString(int source, int length) {
String retVal = String.valueOf(source);
while (retVal.length() < length)
retVal = "0" + retVal;
return retVal;
}
/**
* Helps manage the creation of systematic and unique ids.
*/
private class IdManager {
private Map<String, Integer> counters;
private Map<TopicIF, String> ids;
private IdManager() {
counters = new HashMap<String, Integer>();
ids = new HashMap<TopicIF, String>();
}
/**
* Get the ID of a given topic. Will return null if no id has been created.
*/
private String getId(TopicIF topic) {
return ids.get(topic);
}
/**
* Create ID for given topic, adding an automtic number suffix if necessary
* @param topic The topic for which to create an id.
* @param baseId the id to be given. If already used, a suffix is added.
* @param forceSuffix A suffix is always added if forceSuffix is true.
*/
private String makeId(TopicIF topic, String baseId, boolean forceSuffix) {
if (prefixes.size() > 0) {
for (LocatorIF psi : topic.getSubjectIdentifiers()) {
String externalForm = psi.getExternalForm();
for (String key : prefixes.keySet()) {
String prefix = prefixes.get(key);
if (externalForm.startsWith(prefix)) {
String suffix = externalForm.substring(prefix.length());
if ((suffix.length() > 0) && !suffix.contains("/")) {
String result = key + ":" + suffix;
ids.put(topic, result);
return result;
}
}
}
}
}
String retVal = baseId;
Integer suffix = (Integer)counters.get(baseId);
if (suffix == null) {
if (forceSuffix) {
retVal = baseId + "1";
suffix = new Integer(2);
} else {
retVal = baseId;
suffix = new Integer(2);
}
} else {
retVal = baseId + String.valueOf(suffix);
suffix = new Integer(suffix.intValue() + 1);
}
counters.put(baseId, suffix);
ids.put(topic, retVal);
return retVal;
}
}
/**
* Comparator for Objects of type TopicIF.
*/
private class TopicComparator implements Comparator<TopicIF> {
public TopicComparator() {
}
public int compare(TopicIF t1, TopicIF t2) {
int retVal = 0;
Iterator<TopicIF> t1TypesIt = sort(t1.getTypes(), elementIdComparator).iterator();
Iterator<TopicIF> t2TypesIt = sort(t2.getTypes(), elementIdComparator).iterator();
if (t1TypesIt.hasNext()) {
TopicIF t1Type = t1TypesIt.next();
if (t2TypesIt.hasNext()) {
TopicIF t2Type = t2TypesIt.next();
retVal = lazyStringCompare(getElementId(t1Type), getElementId(t2Type));
} else
retVal = -1; // Out of types -> ordered first.
} else {
if (t2TypesIt.hasNext())
retVal = 1; // Out of types -> ordered first.
else {
retVal = 0; // Equal types -> equal(w.r.t. types).
}
}
if (retVal == 0) {
retVal = lazyStringCompare(getElementId(t1), getElementId(t2));
}
return retVal;
}
}
/**
* Order TMObjects by their elementId.
*/
private class ElementIdComparator implements Comparator<TopicIF> {
public ElementIdComparator() {
}
public int compare(TopicIF o1, TopicIF o2) {
return lazyStringCompare(getElementId(o1),
getElementId(o2));
}
}
/**
* Comparator for Objects of type AssociationIF.
*/
private class AssociationComparator implements Comparator<AssociationIF> {
private Comparator<Collection<AssociationRoleIF>>
// Compares collections of association roles by role type ids.
associationRoleTypeComparator,
// Compares collections of association roles by role player ids.
associationRolePlayerComparator,
// Compare collections of association roles with deafault comparator.
associationRolesComparator;
public AssociationComparator() {
associationRoleTypeComparator = new CollectionNoSizeComparator<AssociationRoleIF>(
new RoleTypeComparator(), new AssociationRoleFrequencyComparator());
associationRolePlayerComparator = new CollectionComparator<AssociationRoleIF>(
new RolePlayerComparator(), new AssociationRoleFrequencyComparator());
associationRolesComparator = new CollectionComparator<AssociationRoleIF>(
new AssociationRoleComparator(),
new AssociationRoleFrequencyComparator());
}
public int compare(AssociationIF assoc1, AssociationIF assoc2) {
int retVal = lazyStringCompare(lazyTypeElementId(assoc1),
lazyTypeElementId(assoc2));
// Compare the type of each of the roles in turn.
if (retVal == 0)
retVal = associationRoleTypeComparator.compare(assoc1.getRoles(),
assoc2.getRoles());
// Compare the player of each of the roles in turn.
if (retVal == 0)
retVal = associationRolePlayerComparator.compare(assoc1.getRoles(),
assoc2.getRoles());
// Compare the each of the roles in turn.
if (retVal == 0)
retVal = associationRolesComparator.compare(assoc1.getRoles(), assoc2
.getRoles());
// Compare the scoping topics(if any) of the association.
if (retVal == 0)
retVal = scopeComparator.compare(filterCollection(assoc1.getScope()),
filterCollection(assoc2.getScope()));
// Compare the reifier(if there is one) of the association.
if (retVal == 0) {
TopicIF reifier1 = assoc1.getReifier();
Collection<TopicIF> reifiers1 = (reifier1 == null ? Collections.<TopicIF>emptySet() : Collections.singleton(reifier1));
TopicIF reifier2 = assoc2.getReifier();
Collection<TopicIF> reifiers2 = (reifier2 == null ? Collections.<TopicIF>emptySet() : Collections.singleton(reifier2));
reifiers1 = filterCollection(reifiers1);
reifiers2 = filterCollection(reifiers2);
retVal = reifierComparator.compare(reifiers1, reifiers2);
}
if (retVal == 0 && !assoc1.equals(assoc2))
retVal = -1;
return retVal;
}
}
/**
* Compares roles by comparing their types.
*/
private class RoleTypeComparator implements Comparator<AssociationRoleIF> {
public RoleTypeComparator() {
}
public int compare(AssociationRoleIF ar1, AssociationRoleIF ar2) {
int retVal = lazyStringCompare(lazyTypeElementId(ar1),
lazyTypeElementId(ar2));
return retVal;
}
}
/**
* Compares roles by comparing their players.
*/
private class RolePlayerComparator implements Comparator<AssociationRoleIF> {
public RolePlayerComparator() {
}
public int compare(AssociationRoleIF ar1, AssociationRoleIF ar2) {
int retVal = lazyStringCompare(lazyPlayerElementId(ar1),
lazyPlayerElementId(ar2));
return retVal;
}
}
/**
* Compares association roles by comparing how frequently each
* (association-type, role-player, role-type) - triple occurs within this
* topicmap. Then compares using AssociationRoleComparator. Then checks for
* equality(.equals) and, if not equal, orders arbitrarily.
*/
private class AssociationRoleFrequencyComparator implements Comparator<AssociationRoleIF> {
AssociationRoleComparator associationRoleComparator;
public AssociationRoleFrequencyComparator() {
associationRoleComparator = new AssociationRoleComparator();
}
public int compare(AssociationRoleIF ar1, AssociationRoleIF ar2) {
// Lookup how many times the current combination of association,
// role-type and role-player occurrs in this topic map.
Integer count1 = getCount(ar1.getAssociation(), ar1);
Integer count2 = getCount(ar2.getAssociation(), ar2);
// Compare frequencies of(association-type, role-type and role-player).
int retVal = count1.compareTo(count2);
if (retVal == 0)
retVal = associationRoleComparator.compare(ar1, ar2);
// If topics are the same in all other ways, check if they're equal.
if (retVal == 0 && !ar1.equals(ar2))
// If they are not equal, arbitrarily order 1st role first.
retVal = -1;
return retVal;
}
}
/**
* Compares association roles by comparing the IDs of their types, players and
* reifiers respectively.
*/
private class AssociationRoleComparator implements Comparator<AssociationRoleIF> {
public AssociationRoleComparator() {
}
public int compare(AssociationRoleIF ar1, AssociationRoleIF ar2) {
// Compare the IDs of the role types.
int retVal = lazyStringCompare(lazyTypeElementId(ar1),
lazyTypeElementId(ar2));
// Compare the IDs of the roles themselves.
if (retVal == 0)
retVal = lazyStringCompare(lazyPlayerElementId(ar1),
lazyPlayerElementId(ar2));
// Compare the reifier(if any) of the association roles.
if (retVal == 0) {
TopicIF reifier1 = ar1.getReifier();
Collection<TopicIF> reifiers1 = (reifier1 == null ? Collections.<TopicIF>emptySet() : Collections.singleton(reifier1));
TopicIF reifier2 = ar2.getReifier();
Collection<TopicIF> reifiers2 = (reifier2 == null ? Collections.<TopicIF>emptySet() : Collections.singleton(reifier2));
reifiers1 = filterCollection(reifiers1);
reifiers2 = filterCollection(reifiers2);
retVal = reifierComparator.compare(reifiers1, reifiers2);
}
return retVal;
}
}
/**
* Compares topic base names for correct output order.
*/
private class TopicNameComparator implements Comparator<TopicNameIF> {
public TopicNameComparator() {
}
public int compare(TopicNameIF bn1, TopicNameIF bn2) {
if (bn1 == bn2)
return 0;
int retVal = scopeComparator.compare(filterCollection(bn1.getScope()),
filterCollection(bn2.getScope()));
if (retVal == 0)
retVal = lazyStringCompare(bn1.getValue(), bn2.getValue());
return retVal;
}
}
/**
* Compares occurrences for correct output order.
*/
private class OccurrenceComparator implements Comparator<OccurrenceIF> {
public OccurrenceComparator() {
}
public int compare(OccurrenceIF occ1, OccurrenceIF occ2) {
if (occ1 == occ2)
return 0;
int retVal = lazyStringCompare(lazyTypeElementId(occ1),
lazyTypeElementId(occ2));
if (retVal == 0)
retVal = lazyStringCompare(occ1.getValue(), occ2.getValue());
// Compare the scoping topics(if any) of the occurrences.
if (retVal == 0)
retVal = scopeComparator.compare(filterCollection(occ1.getScope()),
filterCollection(occ2.getScope()));
// Compare the reifier(if there is one) of the occurrences.
if (retVal == 0) {
TopicIF reifier1 = occ1.getReifier();
Collection<TopicIF> reifiers1 = (reifier1 == null ? Collections.<TopicIF>emptySet() : Collections.singleton(reifier1));
TopicIF reifier2 = occ2.getReifier();
Collection<TopicIF> reifiers2 = (reifier2 == null ? Collections.<TopicIF>emptySet() : Collections.singleton(reifier2));
reifiers1 = filterCollection(reifiers1);
reifiers2 = filterCollection(reifiers2);
retVal = reifierComparator.compare(reifiers1, reifiers2);
}
if (retVal == 0)
retVal = -1;
return retVal;
}
}
/**
* Compares variant names for correct output order.
*/
private class VariantComparator implements Comparator<VariantNameIF> {
public VariantComparator() {
}
public int compare(VariantNameIF vn1, VariantNameIF vn2) {
if (vn1 == vn2)
return 0;
int retVal = scopeComparator.compare(filterCollection(vn1.getScope()),
filterCollection(vn2.getScope()));
if (retVal == 0)
retVal = lazyStringCompare(vn1.getValue(), vn2.getValue());
return retVal;
}
}
/**
* Comparator for Collections that first compares the elements, and then the
* size of the collection. The Collecitons are sorted, and then compared
* element-wise. If the Collections are of equal size, the one with fewer
* elements is ordered first.
*/
private class CollectionComparator<E> implements Comparator<Collection<E>> {
private Comparator<? super E> betweenComp; // Compares elements within collection.
private Comparator<? super E> withinComp; // Compares elements between two
// collections.
private IteratorComparator<E> iteratorComparator; // Compares elements.
/**
* Constructs a CollectionComparator that uses elementComparator for
* comparison.
* @param elementComparator Compares individual elements, both within a
* colleciton and for elements in two different collections.
*/
public CollectionComparator(Comparator<? super E> elementComparator) {
this(elementComparator, elementComparator);
}
/**
* Constructs a CollectionComparator that uses withinComparator and
* betweenComparator for comparison.
* @param withinComparator Compares individual elements within a collection.
* @param betweenComparator Compares individual elements between two
* collections.
*/
public CollectionComparator(Comparator<? super E> betweenComparator,
Comparator<? super E> withinComparator)
{
this.betweenComp = betweenComparator;
this.withinComp = withinComparator;
iteratorComparator = new IteratorComparator<E>(betweenComp);
}
public int compare(Collection<E> c1, Collection<E> c2) {
if (c1 == c2)
return 0;
return iteratorComparator.compare(sort(c1, withinComp).iterator(), sort(
c2, withinComp).iterator());
}
}
/**
* Comparator for Collections that the each pair of elements until it reaches
* the end of one of the collection, in which case the collections will be
* regarded as _equal_.
*/
private class CollectionNoSizeComparator<E> implements Comparator<Collection<E>> {
private Comparator<? super E> betweenComp; // Compares elements within collection.
private Comparator<? super E> withinComp; // Compares elements between two
// collections.
/**
* Constructs a CollectionComparator that uses elementComparator for
* comparison.
* @param elementComparator Compares individual elements, both within a
* colleciton and for elements in two different collections.
*/
public CollectionNoSizeComparator(Comparator<? super E> elementComparator) {
this(elementComparator, elementComparator);
}
/**
* Constructs a CollectionComparator that uses withinComparator and
* betweenComparator for comparison.
* @param withinComparator Compares individual elements within a collection.
* @param betweenComparator Compares individual elements between two
* collections.
*/
public CollectionNoSizeComparator(Comparator<? super E> betweenComparator,
Comparator<? super E> withinComparator)
{
this.betweenComp = betweenComparator;
this.withinComp = withinComparator;
}
public int compare(Collection<E> o1, Collection<E> o2) {
if (o1 == o2)
return 0;
Collection<E> c1 = sort(o1, withinComp);
Collection<E> c2 = sort(o2, withinComp);
Iterator<E> i1 = c1.iterator();
Iterator<E> i2 = c2.iterator();
// NOTE: Deliberately does not take size into account.
int retVal = 0;
while (retVal == 0 && i1.hasNext() && i2.hasNext())
retVal = betweenComp.compare(i1.next(), i2.next());
return retVal;
}
}
/**
* Comparator for superclass-subclass associations.
*/
private class SupersubComparator implements Comparator<AssociationIF> {
Comparator<Iterator<AssociationRoleIF>> iteratorComparator;
Comparator<AssociationRoleIF> supersubRoleComparator;
public SupersubComparator() {
supersubRoleComparator = new SupersubRoleComparator();
iteratorComparator = new IteratorComparator<AssociationRoleIF>(supersubRoleComparator);
}
public int compare(AssociationIF assoc1, AssociationIF assoc2) {
// Compare the sortes sets of roles element-wise.
return iteratorComparator.compare(sort(assoc1.getRoles(),
supersubRoleComparator).iterator(), sort(assoc2.getRoles(),
supersubRoleComparator).iterator());
}
}
/**
* Compares association roles in supertype-subtype associations correct output
* order.
*/
private class SupersubRoleComparator implements Comparator<AssociationRoleIF> {
public SupersubRoleComparator() {
}
public int compare(AssociationRoleIF ar1, AssociationRoleIF ar2) {
int retVal = 0;
// Get the types.
TopicIF type1 = ar1.getType();
TopicIF type2 = ar2.getType();
if (lazyHasLocator(type1, PSI.getXTMSuperclass())) {
if (!lazyHasLocator(type2, PSI.getXTMSuperclass()))
retVal = -1;
} else {
if (lazyHasLocator(type2, PSI.getXTMSuperclass()))
retVal = 1;
}
if (retVal == 0) {
if (lazyHasLocator(type1, PSI.getXTMSubclass())) {
if (!lazyHasLocator(type2, PSI.getXTMSubclass()))
retVal = -1;
} else {
if (lazyHasLocator(type2, PSI.getXTMSubclass()))
retVal = 1;
}
}
if (retVal == 0)
retVal = lazyStringCompare(lazyPlayerElementId(ar1),
lazyPlayerElementId(ar2));
return retVal;
}
}
/**
* Sets additional properties for LTMTopicMapWriter. Accepted properties:
* <ul><li>'preserveIds' (Boolean), corresponds to {@link #setPreserveIds(boolean)}</li>
* <li>'filter' (DeciderIF), corresponds to {@link #setFilter(net.ontopia.utils.DeciderIF)}</li>
* <li>'prefixes' (Map), each key-value pair is passed to
* {@link #addPrefix(java.lang.String, java.lang.String)} as Strings.</li>
* </ul>
* @param properties
*/
@SuppressWarnings("unchecked")
public void setAdditionalProperties(Map<String, Object> properties) {
Object value = properties.get(PROPERTY_PRESERVE_IDS);
if ((value != null) && (value instanceof Boolean)) {
setPreserveIds((Boolean) value);
}
value = properties.get(PROPERTY_FILTER);
if ((value != null) && (value instanceof DeciderIF)) {
setFilter((DeciderIF) value);
}
value = properties.get(PROPERTY_PREFIXES);
if ((value != null) && (value instanceof Map)) {
Map _prefixes = (Map) value;
for (Object k : _prefixes.entrySet()) {
addPrefix(k.toString(), _prefixes.get(k).toString());
}
}
}
}