/**
  * Add key definition to job configuration
  *
  * @param prop job configuration
  * @param key list name
  * @param set key defintions to add
  */
 private void addKeyDefSetToProperties(
     final Job prop, final String key, final Collection<KeyDef> set) {
   // update value
   final Collection<KeyDef> updated = new ArrayList<KeyDef>(set.size());
   for (final KeyDef file : set) {
     String keys = FileUtils.separatorsToUnix(FileUtils.normalize(prefix + file.keys));
     String href = file.href;
     String source = file.source;
     if (prefix.length() != 0) {
       // cases where keymap is in map ancestor folder
       keys = keys.substring(prefix.length());
       if (href == null) {
         // href = FileUtils.separatorsToUnix(FileUtils.normalize(prefix));
         source = FileUtils.separatorsToUnix(FileUtils.normalize(prefix + source));
       } else {
         if (!exKeyDefMap.containsKey(file.keys)) {
           href = FileUtils.separatorsToUnix(FileUtils.normalize(prefix + href));
         }
         source = FileUtils.separatorsToUnix(FileUtils.normalize(prefix + source));
       }
     }
     final KeyDef keyDef = new KeyDef(keys, href, source);
     updated.add(keyDef);
   }
   // write key definition
   try {
     writeKeydef(new File(tempDir, "keydef.xml"), updated);
   } catch (final DITAOTException e) {
     logger.logError("Failed to write key definition file: " + e.getMessage(), e);
   }
   // write list file
   final Set<String> newSet = new LinkedHashSet<String>(set.size());
   for (final KeyDef keydef : updated) {
     newSet.add(keydef.toString());
   }
   prop.setSet(key, newSet);
   final String fileKey = key.substring(0, key.lastIndexOf("list")) + "file";
   prop.setProperty(fileKey, key.substring(0, key.lastIndexOf("list")) + ".list");
   try {
     prop.writeList(key);
   } catch (final IOException e) {
     logger.logError("Failed to write key list file: " + e.getMessage(), e);
   }
 }
  /**
   * add FlagImangesSet to Properties, which needn't to change the dir level, just ouput to the
   * ouput dir.
   *
   * @param prop job configuration
   * @param key list name
   * @param set relative flag image files
   */
  private void addFlagImagesSetToProperties(
      final Job prop, final String key, final Set<String> set) {
    String value = null;
    final Set<String> newSet = new LinkedHashSet<String>(INT_128);
    for (final String file : set) {
      if (new File(file).isAbsolute()) {
        // no need to append relative path before absolute paths
        newSet.add(FileUtils.normalize(file));
      } else {
        // In ant, all the file separator should be slash, so we need to
        // replace all the back slash with slash.
        newSet.add(
            FileUtils.separatorsToUnix(
                FileUtils.normalize(new StringBuffer().append(file).toString())));
      }
    }

    // write list attribute to file
    final String fileKey = key.substring(0, key.lastIndexOf("list")) + "file";
    prop.setProperty(fileKey, key.substring(0, key.lastIndexOf("list")) + ".list");
    final File list = new File(tempDir, prop.getProperty(fileKey));
    Writer bufferedWriter = null;
    try {
      bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(list)));
      final Iterator<String> it = newSet.iterator();
      while (it.hasNext()) {
        bufferedWriter.write(it.next());
        if (it.hasNext()) {
          bufferedWriter.write("\n");
        }
      }
      bufferedWriter.flush();
      bufferedWriter.close();
    } catch (final FileNotFoundException e) {
      logger.logException(e);
    } catch (final IOException e) {
      logger.logException(e);
    } finally {
      if (bufferedWriter != null) {
        try {
          bufferedWriter.close();
        } catch (final IOException e) {
          logger.logException(e);
        }
      }
    }

    value = StringUtils.assembleString(newSet, COMMA);

    prop.setProperty(key, value);

    // clear set
    set.clear();
    newSet.clear();
  }
  /**
   * Add set of values of job configuration
   *
   * @param prop job configuration
   * @param key list name
   * @param set values to add
   */
  private void addSetToProperties(final Job prop, final String key, final Set<String> set) {
    // update value
    final Set<String> newSet = new LinkedHashSet<String>(INT_128);
    for (final String file : set) {
      if (new File(file).isAbsolute()) {
        // no need to append relative path before absolute paths
        newSet.add(FileUtils.normalize(file));
      } else {
        // In ant, all the file separator should be slash, so we need to
        // replace all the back slash with slash.
        final int index = file.indexOf(EQUAL);
        if (index != -1) {
          // keyname
          final String to = file.substring(0, index);
          final String source = file.substring(index + 1);

          newSet.add(
              FileUtils.separatorsToUnix(
                      FileUtils.normalize(new StringBuffer(prefix).append(to).toString()))
                  + EQUAL
                  + FileUtils.separatorsToUnix(
                      FileUtils.normalize(new StringBuffer(prefix).append(source).toString())));
        } else {
          newSet.add(
              FileUtils.separatorsToUnix(
                  FileUtils.normalize(new StringBuffer(prefix).append(file).toString())));
        }
      }
    }
    prop.setSet(key, newSet);
    // write list file
    final String fileKey = key.substring(0, key.lastIndexOf("list")) + "file";
    prop.setProperty(fileKey, key.substring(0, key.lastIndexOf("list")) + ".list");
    try {
      prop.writeList(key);
    } catch (final IOException e) {
      logger.logError("Failed to write list file: " + e.getMessage(), e);
    }
  }
  /**
   * Update uplevels if needed.
   *
   * @param file file path
   */
  private void updateUplevels(String file) {

    // Added by william on 2009-08-06 for bug:2832696 start
    if (file.contains(STICK)) {
      file = file.substring(0, file.indexOf(STICK));
    }
    // Added by william on 2009-08-06 for bug:2832696 end

    // for uplevels (../../)
    // modified start by wxzhang 20070518
    // ".."-->"../"
    final int lastIndex = FileUtils.separatorsToUnix(FileUtils.normalize(file)).lastIndexOf("../");
    // modified end by wxzhang 20070518
    if (lastIndex != -1) {
      final int newUplevels = lastIndex / 3 + 1;
      uplevels = newUplevels > uplevels ? newUplevels : uplevels;
    }
  }
  private void processParseResult(String currentFile) {
    final Map<String, String> cpMap = reader.getCopytoMap();
    final Map<String, KeyDef> kdMap = reader.getKeysDMap();
    // Added by William on 2010-06-09 for bug:3013079 start
    // the reader's reset method will clear the map.
    final Map<String, String> exKdMap = reader.getExKeysDefMap();
    exKeyDefMap.putAll(exKdMap);
    // Added by William on 2010-06-09 for bug:3013079 end

    // Category non-copyto result and update uplevels accordingly
    for (final String file : reader.getNonCopytoResult()) {
      categorizeResultFile(file);
      updateUplevels(file);
    }

    // Update uplevels for copy-to targets, and store copy-to map.
    // Note: same key(target) copy-to will be ignored.
    for (final String key : cpMap.keySet()) {
      final String value = cpMap.get(key);

      if (copytoMap.containsKey(key)) {
        // edited by Alan on Date:2009-11-02 for Work Item:#1590 start
        /*
         * StringBuffer buff = new StringBuffer();
         * buff.append("Copy-to task [href=\""); buff.append(value);
         * buff.append("\" copy-to=\""); buff.append(key);
         * buff.append("\"] which points to another copy-to target");
         * buff.append(" was ignored.");
         * logger.logWarn(buff.toString());
         */
        final Properties prop = new Properties();
        prop.setProperty("%1", value);
        prop.setProperty("%2", key);
        logger.logWarn(MessageUtils.getMessage("DOTX065W", prop).toString());
        // edited by Alan on Date:2009-11-02 for Work Item:#1590 end
        ignoredCopytoSourceSet.add(value);
      } else {
        updateUplevels(key);
        copytoMap.put(key, value);
      }
    }
    // TODO Added by William on 2009-06-09 for scheme key bug(497)
    schemeSet.addAll(reader.getSchemeRefSet());

    // collect key definitions
    for (final String key : kdMap.keySet()) {
      // key and value.keys will differ when keydef is a redirect to another keydef
      final KeyDef value = kdMap.get(key);
      if (keysDefMap.containsKey(key)) {
        // if there already exists duplicated key definition in
        // different map files.
        // Should only emit this if in a debug mode; comment out for now
        /*
         * Properties prop = new Properties(); prop.put("%1", key);
         * prop.put("%2", value); prop.put("%3", currentFile); logger
         * .logInfo(MessageUtils.getMessage("DOTJ048I",
         * prop).toString());
         */
      } else {
        updateUplevels(key);
        // add the ditamap where it is defined.
        /*
         * try { keydef.write("<keydef ");
         * keydef.write("keys=\""+key+"\" ");
         * keydef.write("href=\""+value+"\" ");
         * keydef.write("source=\""+currentFile+"\"/>");
         * keydef.write("\n"); keydef.flush(); } catch (IOException e) {
         *
         * logger.logException(e); }
         */
        keysDefMap.put(key, new KeyDef(key, value.href, currentFile));
      }
      // TODO Added by William on 2009-06-09 for scheme key bug(532-547)
      // if the current file is also a schema file
      if (schemeSet.contains(currentFile)) {
        // write the keydef into the scheme keydef file
        try {
          schemekeydef.writeStartElement(ELEMENT_KEYDEF);
          schemekeydef.writeAttribute(ATTRIBUTE_KEYS, key);
          if (value.href != null) {
            schemekeydef.writeAttribute(ATTRIBUTE_HREF, value.href);
          }
          schemekeydef.writeAttribute(ATTRIUBTE_SOURCE, currentFile);
          schemekeydef.writeEndElement();
        } catch (final SAXException e) {
          logger.logException(e);
        }
      }
    }

    hrefTargetSet.addAll(reader.getHrefTargets());
    hrefWithIDSet.addAll(reader.getHrefTopicSet());
    chunkTopicSet.addAll(reader.getChunkTopicSet());
    // schemeSet.addAll(reader.getSchemeRefSet());
    conrefTargetSet.addAll(reader.getConrefTargets());
    nonConrefCopytoTargetSet.addAll(reader.getNonConrefCopytoTargets());
    ignoredCopytoSourceSet.addAll(reader.getIgnoredCopytoSourceSet());
    subsidiarySet.addAll(reader.getSubsidiaryTargets());
    outDitaFilesSet.addAll(reader.getOutFilesSet());
    resourceOnlySet.addAll(reader.getResourceOnlySet());

    // Generate topic-scheme dictionary
    if (reader.getSchemeSet() != null && reader.getSchemeSet().size() > 0) {
      Set<String> children = null;
      children = this.schemeDictionary.get(currentFile);
      if (children == null) {
        children = new HashSet<String>();
      }
      children.addAll(reader.getSchemeSet());
      // for Linux support
      currentFile = FileUtils.separatorsToUnix(currentFile);

      this.schemeDictionary.put(currentFile, children);
      final Set<String> hrfSet = reader.getHrefTargets();
      for (final String f : hrfSet) {
        // for Linux support
        final String filename = FileUtils.separatorsToUnix(f);

        children = this.schemeDictionary.get(filename);
        if (children == null) {
          children = new HashSet<String>();
        }
        children.addAll(reader.getSchemeSet());
        this.schemeDictionary.put(filename, children);
      }
    }
  }