/**
  * 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);
   }
 }
  /**
   * Categorize file.
   *
   * <p>If {@code file} parameter contains a pipe character, the pipe character is followed by the
   * format of the file.
   *
   * <p>TODO: Pass format as separate DITA class parameter.
   *
   * @param file file path with optional format
   */
  private void categorizeResultFile(String file) {
    // edited by william on 2009-08-06 for bug:2832696 start
    String lcasefn = null;
    String format = null;
    // has format attribute set
    if (file.contains(STICK)) {
      // get lower case file name
      lcasefn = file.substring(0, file.indexOf(STICK)).toLowerCase();
      // get format attribute
      format = file.substring(file.indexOf(STICK) + 1);
      file = file.substring(0, file.indexOf(STICK));
    } else {
      lcasefn = file.toLowerCase();
    }

    // Added by William on 2010-03-04 for bug:2957938 start
    // avoid files referred by coderef being added into wait list
    if (subsidiarySet.contains(lcasefn)) {
      return;
    }
    // Added by William on 2010-03-04 for bug:2957938 end

    if (FileUtils.isDITAFile(lcasefn)
        && (format == null
            || ATTR_FORMAT_VALUE_DITA.equalsIgnoreCase(format)
            || ATTR_FORMAT_VALUE_DITAMAP.equalsIgnoreCase(format))) {

      addToWaitList(file);
    } else if (!FileUtils.isSupportedImageFile(lcasefn)) {
      // FIXME: Treating all non-image extensions as HTML/resource files is not correct if
      // HTML/resource files
      //        are defined by the file extension. Correct behaviour would be to remove this else
      // block.
      htmlSet.add(file);
    }
    // edited by william on 2009-08-06 for bug:2832696 end
    if (FileUtils.isSupportedImageFile(lcasefn)) {
      imageSet.add(file);
      try {
        final File image = new File(baseInputDir + File.separator + file).getCanonicalFile();
        if (!image.exists()) {
          final Properties prop = new Properties();
          prop.put("%1", image.getAbsolutePath());
          logger.logWarn(MessageUtils.getMessage("DOTX008W", prop).toString());
        }
      } catch (final IOException e) {
        logger.logError(e.getMessage());
      }
    }

    if (FileUtils.isHTMLFile(lcasefn) || FileUtils.isResourceFile(lcasefn)) {
      htmlSet.add(file);
    }
  }
  /**
   * 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);
    }
  }
  private void processFile(String currentFile) throws DITAOTException {
    File fileToParse;
    final File file = new File(currentFile);
    if (file.isAbsolute()) {
      fileToParse = file;
      currentFile = FileUtils.getRelativePath(rootFile, currentFile);
    } else {
      fileToParse = new File(baseInputDir, currentFile);
    }
    try {
      fileToParse = fileToParse.getCanonicalFile();
    } catch (final IOException e1) {
      logger.logError(e1.toString());
    }
    logger.logInfo("Processing " + fileToParse.getAbsolutePath());
    String msg = null;
    final Properties params = new Properties();
    params.put("%1", currentFile);

    if (!fileToParse.exists()) {
      logger.logError(MessageUtils.getMessage("DOTX008E", params).toString());
      return;
    }
    try {
      if (FileUtils.isValidTarget(currentFile.toLowerCase())) {
        reader.setTranstype(transtype);
        reader.setCurrentDir(new File(currentFile).getParent());
        reader.parse(fileToParse);

      } else {
        // edited by Alan on Date:2009-11-02 for Work Item:#1590 start
        // logger.logWarn("Input file name is not valid DITA file name.");
        final Properties prop = new Properties();
        prop.put("%1", fileToParse);
        logger.logWarn(MessageUtils.getMessage("DOTJ053W", params).toString());
        // edited by Alan on Date:2009-11-02 for Work Item:#1590 end
      }

      // don't put it into dita.list if it is invalid
      if (reader.isValidInput()) {
        processParseResult(currentFile);
        categorizeCurrentFile(currentFile);
      } else if (!currentFile.equals(inputFile)) {
        logger.logWarn(MessageUtils.getMessage("DOTJ021W", params).toString());
      }
    } catch (final SAXParseException sax) {

      // To check whether the inner third level is DITAOTBuildException
      // :FATALERROR
      final Exception inner = sax.getException();
      if (inner != null && inner instanceof DITAOTException) { // second
        // level
        logger.logInfo(inner.getMessage());
        throw (DITAOTException) inner;
      }
      if (currentFile.equals(inputFile)) {
        // stop the build if exception thrown when parsing input file.
        final MessageBean msgBean = MessageUtils.getMessage("DOTJ012F", params);
        msg = MessageUtils.getMessage("DOTJ012F", params).toString();
        msg = new StringBuffer(msg).append(":").append(sax.getMessage()).toString();
        throw new DITAOTException(msgBean, sax, msg);
      }
      final StringBuffer buff = new StringBuffer();
      msg = MessageUtils.getMessage("DOTJ013E", params).toString();
      buff.append(msg).append(LINE_SEPARATOR).append(sax.getMessage());
      logger.logError(buff.toString());
    } catch (final Exception e) {

      if (currentFile.equals(inputFile)) {
        // stop the build if exception thrown when parsing input file.
        final MessageBean msgBean = MessageUtils.getMessage("DOTJ012F", params);
        msg = MessageUtils.getMessage("DOTJ012F", params).toString();
        msg = new StringBuffer(msg).append(":").append(e.getMessage()).toString();
        throw new DITAOTException(msgBean, e, msg);
      }
      final StringBuffer buff = new StringBuffer();
      msg = MessageUtils.getMessage("DOTJ013E", params).toString();
      buff.append(msg).append(LINE_SEPARATOR).append(e.getMessage());
      logger.logError(buff.toString());
    }

    if (!reader.isValidInput() && currentFile.equals(inputFile)) {

      if (xmlValidate == true) {
        // stop the build if all content in the input file was filtered
        // out.
        msg = MessageUtils.getMessage("DOTJ022F", params).toString();
        throw new DITAOTException(msg);
      } else {
        // stop the build if the content of the file is not valid.
        msg = MessageUtils.getMessage("DOTJ034F", params).toString();
        throw new DITAOTException(msg);
      }
    }

    doneList.add(currentFile);
    reader.reset();
  }