class LTMTopicMapWriter implements TopicMapWriterIF {

  @Override
  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();
    
    if (closeWriter) {
      out.close();
    }
  }

  public  LTMTopicMapWriter(File file);
  public  LTMTopicMapWriter(File file, String encoding);
  public  LTMTopicMapWriter(OutputStream stream);
  public  LTMTopicMapWriter(OutputStream stream, String encoding);
  public  LTMTopicMapWriter(Writer out, String encoding);

}