/**
  * Retuns the subject locators of the topic.
  *
  * @param topic The topic.
  * @return A (maybe empty) array of subject locators.
  */
 private String[] _getSubjectLocators(final Topic topic) {
   final Locator[] slos = topic.getSubjectLocators().toArray(new Locator[0]);
   final String[] sloArray = new String[slos.length];
   for (int i = 0; i < slos.length; i++) {
     sloArray[i] = slos[i].toExternalForm();
   }
   return sloArray;
 }
 /**
  * Returns the subject identifiers of the topic.
  *
  * @param topic The topic.
  * @return A (maybe empty) sorted array of IRIs.
  */
 private String[] _getSubjectIdentifiers(final Topic topic) {
   final Locator[] sids = topic.getSubjectIdentifiers().toArray(new Locator[0]);
   final String[] sidArray = new String[sids.length];
   for (int i = 0; i < sids.length; i++) {
     sidArray[i] = sids[i].toExternalForm();
   }
   Arrays.sort(sidArray);
   return sidArray;
 }
 /**
  * Returns topic references to the types of the topic.
  *
  * @param topic The topic.
  * @return A (maybe empty) sorted array of topic references.
  */
 private String[] _getTypes(final Topic topic) {
   final Topic[] types = topic.getTypes().toArray(new Topic[0]);
   final String[] typeArray = new String[types.length];
   for (int i = 0; i < types.length; i++) {
     typeArray[i] = _getTopicReference(types[i]);
   }
   Arrays.sort(typeArray);
   return typeArray;
 }
 /**
  * Generates the prefixes for the specified topic (incl. its played roles and occurrences and
  * names).
  *
  * @param topic The topic to generate the prefixes for.
  */
 private void _registerPrefixes(final Topic topic) {
   _generateTopicReferences(topic.getTypes());
   for (Occurrence occ : topic.getOccurrences()) {
     _generatePrefixes(occ);
   }
   for (Name name : topic.getNames()) {
     _generatePrefixes(name);
     for (Variant variant : name.getVariants()) {
       _generatePrefixes(variant);
     }
   }
   for (Role rolePlayed : topic.getRolesPlayed()) {
     Association assoc = rolePlayed.getParent();
     _generatePrefixes(assoc);
     for (Role role : assoc.getRoles()) {
       _getTopicReference(role.getType());
     }
   }
 }
 /**
  * Main entry point for writing TM/XML.
  *
  * <p>This method generates the prefixes and writes TM/XML.
  *
  * @param topics The topics to write.
  * @param topicMap The topic map to which all topics belong to or <tt>null</tt>.
  * @throws IOException In case of an error.
  */
 private void _write(final Iterable<Topic> topics, final TopicMap topicMap) throws IOException {
   _registerPrefixes(topics);
   _startTopicMap(topicMap);
   final Iterator<Topic> iter = topics.iterator();
   if (iter.hasNext()) {
     final Topic topic = iter.next();
     final TopicMap tm = topic.getTopicMap();
     // Assuming that the locator can be used for all topics even if
     // they originate from different topic maps.
     _defaultNameTypeLocator = tm.createLocator(TMDM.TOPIC_NAME);
     _writeTopic(topic);
     while (iter.hasNext()) {
       _writeTopic(iter.next());
     }
   }
   _endTopicMap();
   _prefix2IRI.clear();
   _iri2Prefix.clear();
   _topic2Reference.clear();
   _exportedAssocIds.clear();
 }
 /**
  * Returns an item identifier fragment from the topic iff the topic has an item identifier which
  * starts with the base locator.
  *
  * @return The topic id or {@code null} if no item identifier was found.
  */
 private String _getTopicIdentifier(final Topic topic) {
   String id = null;
   for (Locator loc : topic.getItemIdentifiers()) {
     String reference = loc.getReference();
     if (!reference.startsWith(_baseIRI)) {
       continue;
     }
     int fragIdx = reference.indexOf('#');
     if (fragIdx < 0) {
       continue;
     }
     id = reference.substring(fragIdx + 1);
     if (id != null && isValidNCName(id)) {
       break;
     }
     id = null;
   }
   return id;
 }
  /**
   * Writes the specified topic.
   *
   * @param topic The topic to serialize.
   * @throws IOException In case of an error.
   */
  private void _writeTopic(final Topic topic) throws IOException {
    final String[] types = _getTypes(topic);
    final String[] sids = _getSubjectIdentifiers(topic);
    final String[] slos = _getSubjectLocators(topic);
    if (topic.getSubjectIdentifiers().contains(_defaultNameTypeLocator)
        && sids.length == 1
        && slos.length == 0
        && topic.getRolesPlayed().isEmpty()
        && topic.getOccurrences().isEmpty()
        && topic.getNames().isEmpty()
        && types.length == 0) {
      return;
    }
    final String element = types.length > 0 ? types[0] : _UNTYPED_TOPIC;
    // Only add id iff no sids and slos are available
    if (sids.length == 0 && slos.length == 0) {
      _attrs.clear();
      super.addAttribute("id", super.getId(topic));
      _out.startElement(element, _attrs);
    } else {
      final String id = _getTopicIdentifier(topic);
      if (id != null) {
        _attrs.clear();
        super.addAttribute("id", id);
        _out.startElement(element, _attrs);
      } else {
        _out.startElement(element);
      }
    }
    for (String sid : sids) {
      _out.dataElement(_EL_SID, sid);
    }
    for (String slo : slos) {
      _out.dataElement(_EL_SLO, slo);
    }
    for (Name name : topic.getNames()) {
      _writeName(name);
    }
    for (Occurrence occ : topic.getOccurrences()) {
      _writeOccurrence(occ);
    }
    // Type-instance relationships
    for (int i = 1; i < types.length; i++) {
      _attrs.clear();
      super.addAttribute("role", _INSTANCE);
      super.addAttribute("otherrole", _TYPE);
      super.addAttribute("topicref", types[i]);
      _out.emptyElement(_TYPE_INSTANCE, _attrs);
    }
    for (Role playedRole : topic.getRolesPlayed()) {
      final Association assoc = playedRole.getParent();
      if (_exportedAssocIds.contains(assoc.getId())
          || super.isTypeInstanceAssociation(assoc, assoc.getRoles())) {
        continue;
      }
      _exportedAssocIds.add(assoc.getId());
      _attrs.clear();
      _addScopeAndReifier(assoc);
      _addToAttributes("role", playedRole.getType());
      final String assocElement = _type(assoc);
      final int arity = assoc.getRoles().size();
      if (arity == 1) {
        _out.emptyElement(assocElement, _attrs);
      } else if (arity == 2) {
        for (Role role : assoc.getRoles()) {
          if (!role.equals(playedRole)) {
            _addToAttributes("topicref", role.getPlayer());
            _addToAttributes("otherrole", role.getType());
          }
        }
        _out.emptyElement(assocElement, _attrs);
      } else {
        // n-ary association
        _out.startElement(assocElement, _attrs);
        for (Role role : assoc.getRoles()) {
          if (role.equals(playedRole)) {
            continue;
          }
          _attrs.clear();
          _addToAttributes("topicref", role.getPlayer());
          _out.emptyElement(_type(role), _attrs);
        }

        _out.endElement(assocElement);
      }
    }
    _out.endElement(element);
  }