/** @author Brian Wing Shun Chan */
public class ExtInfoBuilder {

  public static void main(String[] args) throws Exception {
    if (args.length == 3) {
      new ExtInfoBuilder(args[0], args[1], args[2]);
    } else {
      throw new IllegalArgumentException();
    }
  }

  public ExtInfoBuilder(String basedir, String outputDir, String servletContextName)
      throws Exception {

    DirectoryScanner ds = new DirectoryScanner();

    ds.setBasedir(basedir);
    ds.setExcludes(
        new String[] {
          ".svn/**",
          "**/.svn/**",
          "ext-impl/ext-impl.jar",
          "ext-impl/src/**",
          "ext-service/ext-service.jar",
          "ext-service/src/**",
          "ext-util-bridges/ext-util-bridges.jar",
          "ext-util-bridges/src/**",
          "ext-util-java/ext-util-java.jar",
          "ext-util-java/src/**",
          "ext-util-taglib/ext-util-taglib.jar",
          "ext-util-taglib/src/**",
          "liferay-plugin-package.properties"
        });

    ds.scan();

    String[] files = ds.getIncludedFiles();

    Arrays.sort(files);

    Element rootElement = new ElementImpl(DocumentHelper.createElement("ext-info"));

    Document document = new DocumentImpl(DocumentHelper.createDocument());

    document.setRootElement(rootElement);

    DocUtil.add(rootElement, "servlet-context-name", servletContextName);

    Element filesElement = rootElement.addElement("files");

    for (String file : files) {
      DocUtil.add(
          filesElement, "file", StringUtil.replace(file, StringPool.BACK_SLASH, StringPool.SLASH));
    }

    _fileUtil.write(outputDir + "/ext-" + servletContextName + ".xml", document.formattedString());
  }

  private static final FileImpl _fileUtil = FileImpl.getInstance();
}
/**
 * @author Brian Wing Shun Chan
 * @author Igor Spasic
 * @author Wesley Gong
 * @author Hugo Huijser
 */
public abstract class BaseSourceProcessor implements SourceProcessor {

  public BaseSourceProcessor() {
    portalSource = _isPortalSource();

    try {
      _properties = _getProperties();
    } catch (Exception e) {
      ReflectionUtil.throwException(e);
    }
  }

  @Override
  public void format(boolean useProperties, boolean printErrors, boolean autoFix) throws Exception {

    _init(useProperties, printErrors, autoFix);

    format();

    sourceFormatterHelper.close();
  }

  @Override
  public String format(String fileName, boolean useProperties, boolean printErrors, boolean autoFix)
      throws Exception {

    try {
      _init(useProperties, printErrors, autoFix);

      return format(fileName);
    } finally {
      sourceFormatterHelper.close();
    }
  }

  @Override
  public List<String> getErrorMessages() {
    List<String> errorMessages = new ArrayList<String>();

    for (Map.Entry<String, List<String>> entry : _errorMessagesMap.entrySet()) {

      errorMessages.addAll(entry.getValue());
    }

    return errorMessages;
  }

  @Override
  public SourceMismatchException getFirstSourceMismatchException() {
    return _firstSourceMismatchException;
  }

  protected static boolean isExcluded(List<String> exclusions, String absolutePath) {

    return isExcluded(exclusions, absolutePath, -1);
  }

  protected static boolean isExcluded(List<String> exclusions, String absolutePath, int lineCount) {

    return isExcluded(exclusions, absolutePath, lineCount, null);
  }

  protected static boolean isExcluded(
      List<String> exclusions, String absolutePath, int lineCount, String javaTermName) {

    if (ListUtil.isEmpty(exclusions)) {
      return false;
    }

    String absolutePathWithJavaTermName = null;

    if (Validator.isNotNull(javaTermName)) {
      absolutePathWithJavaTermName = absolutePath + StringPool.AT + javaTermName;
    }

    String absolutePathWithLineCount = null;

    if (lineCount > 0) {
      absolutePathWithLineCount = absolutePath + StringPool.AT + lineCount;
    }

    for (String exclusion : exclusions) {
      if (absolutePath.endsWith(exclusion)
          || ((absolutePathWithJavaTermName != null)
              && absolutePathWithJavaTermName.endsWith(exclusion))
          || ((absolutePathWithLineCount != null)
              && absolutePathWithLineCount.endsWith(exclusion))) {

        return true;
      }
    }

    return false;
  }

  protected static void processErrorMessage(String fileName, String message) {
    List<String> errorMessages = _errorMessagesMap.get(fileName);

    if (errorMessages == null) {
      errorMessages = new ArrayList<String>();
    }

    errorMessages.add(message);

    _errorMessagesMap.put(fileName, errorMessages);
  }

  protected void checkEmptyCollection(String line, String fileName, int lineCount) {

    // LPS-46028

    Matcher matcher = emptyCollectionPattern.matcher(line);

    if (matcher.find()) {
      String collectionType = TextFormatter.format(matcher.group(1), TextFormatter.J);

      processErrorMessage(
          fileName, "Use Collections.empty" + collectionType + "(): " + fileName + " " + lineCount);
    }
  }

  protected void checkIfClauseParentheses(String ifClause, String fileName, int lineCount) {

    int quoteCount = StringUtil.count(ifClause, StringPool.QUOTE);

    if ((quoteCount % 2) == 1) {
      return;
    }

    ifClause = stripQuotes(ifClause, CharPool.QUOTE);

    ifClause = stripQuotes(ifClause, CharPool.APOSTROPHE);

    if (ifClause.contains(StringPool.DOUBLE_SLASH)
        || ifClause.contains("/*")
        || ifClause.contains("*/")) {

      return;
    }

    if (hasRedundantParentheses(ifClause, "||", "&&")
        || hasRedundantParentheses(ifClause, "&&", "||")) {

      processErrorMessage(fileName, "redundant parentheses: " + fileName + " " + lineCount);
    }

    ifClause = stripRedundantParentheses(ifClause);

    int level = 0;
    int max = StringUtil.count(ifClause, StringPool.OPEN_PARENTHESIS);
    int previousParenthesisPos = -1;

    int[] levels = new int[max];

    for (int i = 0; i < ifClause.length(); i++) {
      char c = ifClause.charAt(i);

      if ((c == CharPool.OPEN_PARENTHESIS) || (c == CharPool.CLOSE_PARENTHESIS)) {

        if (previousParenthesisPos != -1) {
          String s = ifClause.substring(previousParenthesisPos + 1, i);

          if (hasMissingParentheses(s)) {
            processErrorMessage(fileName, "missing parentheses: " + fileName + " " + lineCount);
          }
        }

        previousParenthesisPos = i;

        if (c == CharPool.OPEN_PARENTHESIS) {
          levels[level] = i;

          level += 1;
        } else {
          int posOpenParenthesis = levels[level - 1];

          if (level > 1) {
            char nextChar = ifClause.charAt(i + 1);
            char previousChar = ifClause.charAt(posOpenParenthesis - 1);

            if (!Character.isLetterOrDigit(nextChar)
                && (nextChar != CharPool.PERIOD)
                && !Character.isLetterOrDigit(previousChar)) {

              String s = ifClause.substring(posOpenParenthesis + 1, i);

              if (hasRedundantParentheses(s)) {
                processErrorMessage(
                    fileName, "redundant parentheses: " + fileName + " " + lineCount);
              }
            }

            if ((previousChar == CharPool.OPEN_PARENTHESIS)
                && (nextChar == CharPool.CLOSE_PARENTHESIS)) {

              processErrorMessage(fileName, "redundant parentheses: " + fileName + " " + lineCount);
            }
          }

          level -= 1;
        }
      }
    }
  }

  protected void checkInefficientStringMethods(
      String line, String fileName, String absolutePath, int lineCount) {

    if (isRunsOutsidePortal(absolutePath)) {
      return;
    }

    String methodName = "toLowerCase";

    int pos = line.indexOf(".toLowerCase()");

    if (pos == -1) {
      methodName = "toUpperCase";

      pos = line.indexOf(".toUpperCase()");
    }

    if ((pos == -1) && !line.contains("StringUtil.equalsIgnoreCase(")) {
      methodName = "equalsIgnoreCase";

      pos = line.indexOf(".equalsIgnoreCase(");
    }

    if (pos != -1) {
      processErrorMessage(
          fileName, "Use StringUtil." + methodName + ": " + fileName + " " + lineCount);
    }
  }

  protected void checkLanguageKeys(String fileName, String content, Pattern pattern)
      throws IOException {

    String fileExtension = fileUtil.getExtension(fileName);

    if (!portalSource || fileExtension.equals("vm")) {
      return;
    }

    if (_portalLanguageProperties == null) {
      _portalLanguageProperties = new Properties();

      ClassLoader classLoader = BaseSourceProcessor.class.getClassLoader();

      InputStream inputStream = classLoader.getResourceAsStream("content/Language.properties");

      _portalLanguageProperties.load(inputStream);
    }

    Matcher matcher = pattern.matcher(content);

    while (matcher.find()) {
      String[] languageKeys = getLanguageKeys(matcher);

      for (String languageKey : languageKeys) {
        if (Validator.isNumber(languageKey)
            || languageKey.endsWith(StringPool.DASH)
            || languageKey.endsWith(StringPool.OPEN_BRACKET)
            || languageKey.endsWith(StringPool.PERIOD)
            || languageKey.endsWith(StringPool.UNDERLINE)
            || languageKey.startsWith(StringPool.DASH)
            || languageKey.startsWith(StringPool.OPEN_BRACKET)
            || languageKey.startsWith(StringPool.OPEN_CURLY_BRACE)
            || languageKey.startsWith(StringPool.PERIOD)
            || languageKey.startsWith(StringPool.UNDERLINE)
            || _portalLanguageProperties.containsKey(languageKey)) {

          continue;
        }

        Properties languageProperties = getLanguageProperties(fileName);

        if ((languageProperties == null) || !languageProperties.containsKey(languageKey)) {

          processErrorMessage(
              fileName, "missing language key: " + languageKey + StringPool.SPACE + fileName);
        }
      }
    }
  }

  protected void checkStringBundler(String line, String fileName, int lineCount) {

    if ((!line.startsWith("sb.append(") && !line.contains("SB.append(")) || !line.endsWith(");")) {

      return;
    }

    int pos = line.indexOf(".append(");

    line = line.substring(pos + 8, line.length() - 2);

    line = stripQuotes(line, CharPool.QUOTE);

    if (!line.contains(" + ")) {
      return;
    }

    String[] lineParts = StringUtil.split(line, " + ");

    for (String linePart : lineParts) {
      int closeParenthesesCount = StringUtil.count(linePart, StringPool.CLOSE_PARENTHESIS);
      int openParenthesesCount = StringUtil.count(linePart, StringPool.OPEN_PARENTHESIS);

      if (closeParenthesesCount != openParenthesesCount) {
        return;
      }

      if (Validator.isNumber(linePart)) {
        return;
      }
    }

    processErrorMessage(fileName, "plus: " + fileName + " " + lineCount);
  }

  protected abstract String doFormat(
      File file, String fileName, String absolutePath, String content) throws Exception;

  protected String fixCompatClassImports(String absolutePath, String content) throws IOException {

    if (portalSource
        || !_usePortalCompatImport
        || absolutePath.contains("/ext-")
        || absolutePath.contains("/portal-compat-shared/")) {

      return content;
    }

    Map<String, String> compatClassNamesMap = getCompatClassNamesMap();

    String newContent = content;

    for (Map.Entry<String, String> entry : compatClassNamesMap.entrySet()) {
      String compatClassName = entry.getKey();
      String extendedClassName = entry.getValue();

      Pattern pattern = Pattern.compile(extendedClassName + "\\W");

      while (true) {
        Matcher matcher = pattern.matcher(newContent);

        if (!matcher.find()) {
          break;
        }

        newContent =
            newContent.substring(0, matcher.start())
                + compatClassName
                + newContent.substring(matcher.end() - 1);
      }
    }

    return newContent;
  }

  protected String fixCopyright(String content, String absolutePath, String fileName)
      throws IOException {

    if (_copyright == null) {
      _copyright = getContent("copyright.txt", 4);
    }

    String copyright = _copyright;

    if (fileName.endsWith(".vm") || Validator.isNull(copyright)) {
      return content;
    }

    if (_oldCopyright == null) {
      _oldCopyright = getContent("old-copyright.txt", 4);
    }

    if (Validator.isNotNull(_oldCopyright) && content.contains(_oldCopyright)) {

      content = StringUtil.replace(content, _oldCopyright, copyright);

      processErrorMessage(fileName, "old (c): " + fileName);
    }

    if (!content.contains(copyright)) {
      String customCopyright = getCustomCopyright(absolutePath);

      if (Validator.isNotNull(customCopyright)) {
        copyright = customCopyright;
      }

      if (!content.contains(copyright)) {
        processErrorMessage(fileName, "(c): " + fileName);
      }
    }

    if (fileName.endsWith(".jsp") || fileName.endsWith(".jspf")) {
      content =
          StringUtil.replace(content, "<%\n" + copyright + "\n%>", "<%--\n" + copyright + "\n--%>");
    }

    int x = content.indexOf("* Copyright (c) 2000-");

    if (x == -1) {
      return content;
    }

    int y = content.indexOf("Liferay", x);

    String contentCopyrightYear = content.substring(x, y);

    x = copyright.indexOf("* Copyright (c) 2000-");

    if (x == -1) {
      return content;
    }

    y = copyright.indexOf("Liferay", x);

    String copyrightYear = copyright.substring(x, y);

    return StringUtil.replace(content, contentCopyrightYear, copyrightYear);
  }

  protected String fixIncorrectParameterTypeForLanguageUtil(
      String content, boolean autoFix, String fileName) {

    if (portalSource) {
      return content;
    }

    String expectedParameter = getProperty("languageutil.expected.parameter");
    String incorrectParameter = getProperty("languageutil.incorrect.parameter");

    if (!content.contains("LanguageUtil.format(" + incorrectParameter + ", ")
        && !content.contains("LanguageUtil.get(" + incorrectParameter + ", ")) {

      return content;
    }

    if (autoFix) {
      content =
          StringUtil.replace(
              content,
              new String[] {
                "LanguageUtil.format(" + incorrectParameter + ", ",
                "LanguageUtil.get(" + incorrectParameter + ", "
              },
              new String[] {
                "LanguageUtil.format(" + expectedParameter + ", ",
                "LanguageUtil.get(" + expectedParameter + ", "
              });
    } else {
      processErrorMessage(
          fileName,
          "(Unicode)LanguageUtil.format/get methods require "
              + expectedParameter
              + " parameter instead of "
              + incorrectParameter
              + " "
              + fileName);
    }

    return content;
  }

  protected String fixSessionKey(String fileName, String content, Pattern pattern) {

    Matcher matcher = pattern.matcher(content);

    if (!matcher.find()) {
      return content;
    }

    String newContent = content;

    do {
      String match = matcher.group();

      String s = null;

      if (pattern.equals(sessionKeyPattern)) {
        s = StringPool.COMMA;
      } else if (pattern.equals(taglibSessionKeyPattern)) {
        s = "key=";
      }

      int x = match.indexOf(s);

      if (x == -1) {
        continue;
      }

      x = x + s.length();

      String substring = match.substring(x).trim();

      String quote = StringPool.BLANK;

      if (substring.startsWith(StringPool.APOSTROPHE)) {
        quote = StringPool.APOSTROPHE;
      } else if (substring.startsWith(StringPool.QUOTE)) {
        quote = StringPool.QUOTE;
      } else {
        continue;
      }

      int y = match.indexOf(quote, x);
      int z = match.indexOf(quote, y + 1);

      if ((y == -1) || (z == -1)) {
        continue;
      }

      String prefix = match.substring(0, y + 1);
      String suffix = match.substring(z);
      String oldKey = match.substring(y + 1, z);

      boolean alphaNumericKey = true;

      for (char c : oldKey.toCharArray()) {
        if (!Validator.isChar(c)
            && !Validator.isDigit(c)
            && (c != CharPool.DASH)
            && (c != CharPool.UNDERLINE)) {

          alphaNumericKey = false;
        }
      }

      if (!alphaNumericKey) {
        continue;
      }

      String newKey = TextFormatter.format(oldKey, TextFormatter.O);

      newKey = TextFormatter.format(newKey, TextFormatter.M);

      if (newKey.equals(oldKey)) {
        continue;
      }

      String oldSub = prefix.concat(oldKey).concat(suffix);
      String newSub = prefix.concat(newKey).concat(suffix);

      newContent = StringUtil.replaceFirst(newContent, oldSub, newSub);
    } while (matcher.find());

    return newContent;
  }

  protected abstract void format() throws Exception;

  protected String format(File file, String fileName, String absolutePath, String content)
      throws Exception {

    _errorMessagesMap.remove(fileName);

    String newContent = doFormat(file, fileName, absolutePath, content);

    newContent = StringUtil.replace(newContent, StringPool.RETURN, StringPool.BLANK);

    if (content.equals(newContent)) {
      return content;
    }

    return format(file, fileName, absolutePath, newContent);
  }

  protected String format(String fileName) throws Exception {
    File file = new File(BASEDIR + fileName);

    fileName = StringUtil.replace(fileName, StringPool.BACK_SLASH, StringPool.SLASH);

    String absolutePath = getAbsolutePath(file);

    String content = fileUtil.read(file);

    String newContent = format(file, fileName, absolutePath, content);

    processFormattedFile(file, fileName, content, newContent);

    return newContent;
  }

  protected String formatJavaTerms(
      String fileName,
      String absolutePath,
      String content,
      String javaClassContent,
      List<String> javaTermSortExclusions,
      List<String> testAnnotationsExclusions)
      throws Exception {

    JavaClass javaClass = new JavaClass(fileName, absolutePath, javaClassContent, StringPool.TAB);

    String newJavaClassContent =
        javaClass.formatJavaTerms(javaTermSortExclusions, testAnnotationsExclusions);

    if (!javaClassContent.equals(newJavaClassContent)) {
      return StringUtil.replaceFirst(content, javaClassContent, newJavaClassContent);
    }

    return content;
  }

  protected String getAbsolutePath(File file) {
    String absolutePath = fileUtil.getAbsolutePath(file);

    return StringUtil.replace(absolutePath, "/./", StringPool.SLASH);
  }

  protected Map<String, String> getCompatClassNamesMap() throws IOException {
    if (_compatClassNamesMap != null) {
      return _compatClassNamesMap;
    }

    Map<String, String> compatClassNamesMap = new HashMap<String, String>();

    String[] includes =
        new String[] {"**\\portal-compat-shared\\src\\com\\liferay\\compat\\**\\*.java"};

    String basedir = BASEDIR;

    List<String> fileNames = new ArrayList<String>();

    for (int i = 0; i < 3; i++) {
      fileNames = getFileNames(basedir, new String[0], includes);

      if (!fileNames.isEmpty()) {
        break;
      }

      basedir = "../" + basedir;
    }

    for (String fileName : fileNames) {
      File file = new File(basedir + fileName);

      String content = fileUtil.read(file);

      fileName = StringUtil.replace(fileName, StringPool.BACK_SLASH, StringPool.SLASH);

      fileName = StringUtil.replace(fileName, StringPool.SLASH, StringPool.PERIOD);

      int pos = fileName.indexOf("com.");

      String compatClassName = fileName.substring(pos);

      compatClassName = compatClassName.substring(0, compatClassName.length() - 5);

      String extendedClassName = StringUtil.replace(compatClassName, "compat.", StringPool.BLANK);

      if (content.contains("extends " + extendedClassName)) {
        compatClassNamesMap.put(compatClassName, extendedClassName);
      }
    }

    _compatClassNamesMap = compatClassNamesMap;

    return _compatClassNamesMap;
  }

  protected String getContent(String fileName, int level) throws IOException {
    File file = getFile(fileName, level);

    if (file != null) {
      String content = fileUtil.read(file);

      if (Validator.isNotNull(content)) {
        return content;
      }
    }

    return StringPool.BLANK;
  }

  protected String getCustomCopyright(String absolutePath) throws IOException {

    for (int x = absolutePath.length(); ; ) {
      x = absolutePath.lastIndexOf(StringPool.SLASH, x);

      if (x == -1) {
        break;
      }

      String copyright = fileUtil.read(absolutePath.substring(0, x + 1) + "copyright.txt");

      if (Validator.isNotNull(copyright)) {
        return copyright;
      }

      x = x - 1;
    }

    return null;
  }

  protected File getFile(String fileName, int level) {
    for (int i = 0; i < level; i++) {
      if (fileUtil.exists(fileName)) {
        return new File(fileName);
      }

      fileName = "../" + fileName;
    }

    return null;
  }

  protected List<String> getFileNames(String basedir, String[] excludes, String[] includes) {

    DirectoryScanner directoryScanner = new DirectoryScanner();

    directoryScanner.setBasedir(basedir);

    if (_excludes != null) {
      excludes = ArrayUtil.append(excludes, _excludes);
    }

    directoryScanner.setExcludes(excludes);

    directoryScanner.setIncludes(includes);

    return sourceFormatterHelper.scanForFiles(directoryScanner);
  }

  protected List<String> getFileNames(String[] excludes, String[] includes) {
    return getFileNames(BASEDIR, excludes, includes);
  }

  protected String[] getLanguageKeys(Matcher matcher) {
    int groupCount = matcher.groupCount();

    if (groupCount == 1) {
      String languageKey = matcher.group(1);

      if (Validator.isNotNull(languageKey)) {
        return new String[] {languageKey};
      }
    } else if (groupCount == 2) {
      String languageKey = matcher.group(2);

      languageKey = TextFormatter.format(languageKey, TextFormatter.P);

      return new String[] {languageKey};
    }

    StringBundler sb = new StringBundler();

    String match = matcher.group();

    int count = 0;

    for (int i = 0; i < match.length(); i++) {
      char c = match.charAt(i);

      switch (c) {
        case CharPool.CLOSE_PARENTHESIS:
          if (count <= 1) {
            return new String[0];
          }

          count--;

          break;

        case CharPool.OPEN_PARENTHESIS:
          count++;

          break;

        case CharPool.QUOTE:
          if (count > 1) {
            break;
          }

          while (i < match.length()) {
            i++;

            if (match.charAt(i) == CharPool.QUOTE) {
              String languageKey = sb.toString();

              if (match.startsWith("names")) {
                return StringUtil.split(languageKey);
              } else {
                return new String[] {languageKey};
              }
            }

            sb.append(match.charAt(i));
          }
      }
    }

    return new String[0];
  }

  protected Properties getLanguageProperties(String fileName) {
    StringBundler sb = new StringBundler(4);

    int pos = fileName.indexOf("/docroot/");

    sb.append(BASEDIR);

    if (pos != -1) {
      sb.append(fileName.substring(0, pos + 9));
      sb.append("WEB-INF/src/");
    } else {
      pos = fileName.indexOf("/src/");

      if (pos == -1) {
        return null;
      }

      sb.append(fileName.substring(0, pos + 5));
    }

    sb.append("content/Language.properties");

    try {
      Properties properties = new Properties();

      InputStream inputStream = new FileInputStream(sb.toString());

      properties.load(inputStream);

      return properties;
    } catch (Exception e) {
    }

    return null;
  }

  protected String getMainReleaseVersion() {
    if (_mainReleaseVersion != null) {
      return _mainReleaseVersion;
    }

    String releaseVersion = ReleaseInfo.getVersion();

    int pos = releaseVersion.lastIndexOf(StringPool.PERIOD);

    _mainReleaseVersion = releaseVersion.substring(0, pos) + ".0";

    return _mainReleaseVersion;
  }

  protected String getProperty(String key) {
    return _properties.getProperty(key);
  }

  protected List<String> getPropertyList(String key) {
    return ListUtil.fromString(GetterUtil.getString(getProperty(key)), StringPool.COMMA);
  }

  protected boolean hasMissingParentheses(String s) {
    if (Validator.isNull(s)) {
      return false;
    }

    boolean containsAndOperator = s.contains("&&");
    boolean containsOrOperator = s.contains("||");

    if (containsAndOperator && containsOrOperator) {
      return true;
    }

    boolean containsCompareOperator =
        (s.contains(" == ")
            || s.contains(" != ")
            || s.contains(" < ")
            || s.contains(" > ")
            || s.contains(" =< ")
            || s.contains(" => ")
            || s.contains(" <= ")
            || s.contains(" >= "));
    boolean containsMathOperator =
        (s.contains(" = ")
            || s.contains(" - ")
            || s.contains(" + ")
            || s.contains(" & ")
            || s.contains(" % ")
            || s.contains(" * ")
            || s.contains(" / "));

    if (containsCompareOperator
        && (containsAndOperator
            || containsOrOperator
            || (containsMathOperator && !s.contains(StringPool.OPEN_BRACKET)))) {

      return true;
    }

    return false;
  }

  protected boolean hasRedundantParentheses(String s) {
    if (!s.contains("&&") && !s.contains("||")) {
      for (int x = 0; ; ) {
        x = s.indexOf(StringPool.CLOSE_PARENTHESIS);

        if (x == -1) {
          break;
        }

        int y = s.substring(0, x).lastIndexOf(StringPool.OPEN_PARENTHESIS);

        if (y == -1) {
          break;
        }

        s = s.substring(0, y) + s.substring(x + 1);
      }
    }

    if (Validator.isNotNull(s) && !s.contains(StringPool.SPACE)) {
      return true;
    } else {
      return false;
    }
  }

  protected boolean hasRedundantParentheses(String s, String operator1, String operator2) {

    String[] parts = StringUtil.split(s, operator1);

    if (parts.length < 3) {
      return false;
    }

    for (int i = 1; i < (parts.length - 1); i++) {
      String part = parts[i];

      if (part.contains(operator2) || part.contains("!(")) {
        continue;
      }

      int closeParenthesesCount = StringUtil.count(part, StringPool.CLOSE_PARENTHESIS);
      int openParenthesesCount = StringUtil.count(part, StringPool.OPEN_PARENTHESIS);

      if (Math.abs(closeParenthesesCount - openParenthesesCount) == 1) {
        return true;
      }
    }

    return false;
  }

  protected boolean isAttributName(String attributeName) {
    if (Validator.isNull(attributeName)) {
      return false;
    }

    Matcher matcher = attributeNamePattern.matcher(attributeName);

    return matcher.matches();
  }

  protected boolean isRunsOutsidePortal(String absolutePath) {
    if (_runOutsidePortalExclusions == null) {
      _runOutsidePortalExclusions = getPropertyList("run.outside.portal.excludes");
    }

    for (String runOutsidePortalExclusions : _runOutsidePortalExclusions) {
      if (absolutePath.contains(runOutsidePortalExclusions)) {
        return true;
      }
    }

    return false;
  }

  protected void processFormattedFile(File file, String fileName, String content, String newContent)
      throws IOException {

    if (_printErrors) {
      List<String> errorMessages = _errorMessagesMap.get(fileName);

      if (errorMessages != null) {
        for (String errorMessage : errorMessages) {
          sourceFormatterHelper.printError(fileName, errorMessage);
        }
      }
    }

    if (content.equals(newContent)) {
      return;
    }

    if (_autoFix) {
      fileUtil.write(file, newContent);
    } else if (_firstSourceMismatchException == null) {
      _firstSourceMismatchException = new SourceMismatchException(fileName, content, newContent);
    }

    if (_printErrors) {
      sourceFormatterHelper.printError(fileName, file);
    }
  }

  protected String replacePrimitiveWrapperInstantiation(
      String fileName, String line, int lineCount) {

    if (true) {
      return line;
    }

    String newLine =
        StringUtil.replace(
            line,
            new String[] {
              "new Boolean(",
              "new Byte(",
              "new Character(",
              "new Integer(",
              "new Long(",
              "new Short("
            },
            new String[] {
              "Boolean.valueOf(", "Byte.valueOf(", "Character.valueOf(",
              "Integer.valueOf(", "Long.valueOf(", "Short.valueOf("
            });

    if (!line.equals(newLine)) {
      processErrorMessage(fileName, "> new Primitive(: " + fileName + " " + lineCount);
    }

    return newLine;
  }

  protected String sortAttributes(
      String fileName, String line, int lineCount, boolean allowApostropheDelimeter) {

    String s = line;

    int x = s.indexOf(StringPool.SPACE);

    if (x == -1) {
      return line;
    }

    s = s.substring(x + 1);

    String previousAttribute = null;
    String previousAttributeAndValue = null;

    boolean wrongOrder = false;

    for (x = 0; ; ) {
      x = s.indexOf(StringPool.EQUAL);

      if ((x == -1) || (s.length() <= (x + 1))) {
        return line;
      }

      String attribute = s.substring(0, x);

      if (!isAttributName(attribute)) {
        return line;
      }

      if (Validator.isNotNull(previousAttribute) && (previousAttribute.compareTo(attribute) > 0)) {

        wrongOrder = true;
      }

      s = s.substring(x + 1);

      char delimeter = s.charAt(0);

      if ((delimeter != CharPool.APOSTROPHE) && (delimeter != CharPool.QUOTE)) {

        if (delimeter != CharPool.AMPERSAND) {
          processErrorMessage(fileName, "delimeter: " + fileName + " " + lineCount);
        }

        return line;
      }

      s = s.substring(1);

      String value = null;

      int y = -1;

      while (true) {
        y = s.indexOf(delimeter, y + 1);

        if ((y == -1) || (s.length() <= (y + 1))) {
          return line;
        }

        value = s.substring(0, y);

        if (value.startsWith("<%")) {
          int endJavaCodeSignCount = StringUtil.count(value, "%>");
          int startJavaCodeSignCount = StringUtil.count(value, "<%");

          if (endJavaCodeSignCount == startJavaCodeSignCount) {
            break;
          }
        } else {
          int greaterThanCount = StringUtil.count(value, StringPool.GREATER_THAN);
          int lessThanCount = StringUtil.count(value, StringPool.LESS_THAN);

          if (greaterThanCount == lessThanCount) {
            break;
          }
        }
      }

      if (delimeter == CharPool.APOSTROPHE) {
        if (!value.contains(StringPool.QUOTE)) {
          line =
              StringUtil.replace(
                  line,
                  StringPool.APOSTROPHE + value + StringPool.APOSTROPHE,
                  StringPool.QUOTE + value + StringPool.QUOTE);

          return sortAttributes(fileName, line, lineCount, allowApostropheDelimeter);
        } else if (!allowApostropheDelimeter) {
          String newValue = StringUtil.replace(value, StringPool.QUOTE, "&quot;");

          line =
              StringUtil.replace(
                  line,
                  StringPool.APOSTROPHE + value + StringPool.APOSTROPHE,
                  StringPool.QUOTE + newValue + StringPool.QUOTE);

          return sortAttributes(fileName, line, lineCount, allowApostropheDelimeter);
        }
      }

      StringBundler sb = new StringBundler(5);

      sb.append(attribute);
      sb.append(StringPool.EQUAL);
      sb.append(delimeter);
      sb.append(value);
      sb.append(delimeter);

      String currentAttributeAndValue = sb.toString();

      if (wrongOrder) {
        if ((StringUtil.count(line, currentAttributeAndValue) == 1)
            && (StringUtil.count(line, previousAttributeAndValue) == 1)) {

          line = StringUtil.replaceFirst(line, previousAttributeAndValue, currentAttributeAndValue);

          line = StringUtil.replaceLast(line, currentAttributeAndValue, previousAttributeAndValue);

          return sortAttributes(fileName, line, lineCount, allowApostropheDelimeter);
        }

        return line;
      }

      s = s.substring(y + 1);

      if (s.startsWith(StringPool.GREATER_THAN)) {
        x = s.indexOf(StringPool.SPACE);

        if (x == -1) {
          return line;
        }

        s = s.substring(x + 1);

        previousAttribute = null;
        previousAttributeAndValue = null;
      } else {
        s = StringUtil.trimLeading(s);

        previousAttribute = attribute;
        previousAttributeAndValue = currentAttributeAndValue;
      }
    }
  }

  protected String stripLine(String s, char startDelimeter, char endDelimeter) {

    boolean insideDelimeters = false;
    int level = 0;

    StringBundler sb = new StringBundler();

    for (int i = 0; i < s.length(); i++) {
      char c = s.charAt(i);

      if (insideDelimeters) {
        if (c == endDelimeter) {
          if (level > 0) {
            level -= 1;
          } else {
            if ((c > 1)
                && (s.charAt(i - 1) == CharPool.BACK_SLASH)
                && (s.charAt(i - 2) != CharPool.BACK_SLASH)) {

              continue;
            }

            insideDelimeters = false;
          }
        } else if (c == startDelimeter) {
          level += 1;
        }
      } else if (c == startDelimeter) {
        insideDelimeters = true;
      } else {
        sb.append(c);
      }
    }

    return sb.toString();
  }

  protected String stripQuotes(String s, char delimeter) {
    return stripLine(s, delimeter, delimeter);
  }

  protected String stripRedundantParentheses(String s) {
    for (int x = 0; ; ) {
      x = s.indexOf(StringPool.OPEN_PARENTHESIS, x + 1);
      int y = s.indexOf(StringPool.CLOSE_PARENTHESIS, x);

      if ((x == -1) || (y == -1)) {
        return s;
      }

      String linePart = s.substring(x + 1, y);

      linePart = StringUtil.replace(linePart, StringPool.COMMA, StringPool.BLANK);

      if (Validator.isAlphanumericName(linePart) || Validator.isNull(linePart)) {

        s = s.substring(0, x) + s.substring(y + 1);
      }
    }
  }

  protected String trimContent(String content, boolean allowLeadingSpaces) throws IOException {

    StringBundler sb = new StringBundler();

    UnsyncBufferedReader unsyncBufferedReader =
        new UnsyncBufferedReader(new UnsyncStringReader(content));

    String line = null;

    while ((line = unsyncBufferedReader.readLine()) != null) {
      sb.append(trimLine(line, allowLeadingSpaces));
      sb.append("\n");
    }

    unsyncBufferedReader.close();

    content = sb.toString();

    if (content.endsWith("\n")) {
      content = content.substring(0, content.length() - 1);
    }

    return content;
  }

  protected String trimLine(String line, boolean allowLeadingSpaces) {
    if (line.trim().length() == 0) {
      return StringPool.BLANK;
    }

    line = StringUtil.trimTrailing(line);

    if (allowLeadingSpaces || !line.startsWith(StringPool.SPACE) || line.startsWith(" *")) {

      return line;
    }

    if (!line.startsWith(StringPool.FOUR_SPACES)) {
      while (line.startsWith(StringPool.SPACE)) {
        line = StringUtil.replaceFirst(line, StringPool.SPACE, StringPool.BLANK);
      }
    } else {
      int pos = 0;

      String temp = line;

      while (temp.startsWith(StringPool.FOUR_SPACES)) {
        line = StringUtil.replaceFirst(line, StringPool.FOUR_SPACES, StringPool.TAB);

        pos++;

        temp = line.substring(pos);
      }
    }

    return line;
  }

  protected static final String BASEDIR = "./";

  protected static Pattern attributeNamePattern = Pattern.compile("[a-z]+[-_a-zA-Z0-9]*");
  protected static Pattern emptyCollectionPattern =
      Pattern.compile("Collections\\.EMPTY_(LIST|MAP|SET)");
  protected static FileImpl fileUtil = FileImpl.getInstance();
  protected static Pattern languageKeyPattern =
      Pattern.compile("LanguageUtil.(?:get|format)\\([^;%]+|Liferay.Language.get\\('([^']+)");
  protected static boolean portalSource;
  protected static SAXReaderImpl saxReaderUtil = SAXReaderImpl.getInstance();
  protected static Pattern sessionKeyPattern =
      Pattern.compile(
          "SessionErrors.(?:add|contains|get)\\([^;%&|!]+|"
              .concat("SessionMessages.(?:add|contains|get)\\([^;%&|!]+"),
          Pattern.MULTILINE);
  protected static SourceFormatterHelper sourceFormatterHelper;
  protected static Pattern taglibSessionKeyPattern =
      Pattern.compile("<liferay-ui:error [^>]+>|<liferay-ui:success [^>]+>", Pattern.MULTILINE);

  private String[] _getExcludes() {
    List<String> excludesList =
        ListUtil.fromString(GetterUtil.getString(System.getProperty("source.formatter.excludes")));

    excludesList.addAll(getPropertyList("source.formatter.excludes"));

    String[] includes = new String[] {"**\\source_formatter.ignore"};

    List<String> ignoreFileNames = getFileNames(new String[0], includes);

    for (String ignoreFileName : ignoreFileNames) {
      excludesList.add(ignoreFileName.substring(0, ignoreFileName.length() - 23) + "**");
    }

    return excludesList.toArray(new String[excludesList.size()]);
  }

  private Properties _getProperties() throws Exception {
    String fileName = "source-formatter.properties";

    Properties properties = new Properties();

    if (portalSource) {
      ClassLoader classLoader = BaseSourceProcessor.class.getClassLoader();

      properties.load(
          classLoader.getResourceAsStream("com/liferay/portal/tools/dependencies/" + fileName));

      return properties;
    }

    List<Properties> propertiesList = new ArrayList<Properties>();

    for (int i = 0; i <= 2; i++) {
      try {
        InputStream inputStream = new FileInputStream(fileName);

        Properties props = new Properties();

        props.load(inputStream);

        propertiesList.add(props);
      } catch (FileNotFoundException fnfe) {
      }

      fileName = "../" + fileName;
    }

    if (propertiesList.isEmpty()) {
      return properties;
    }

    properties = propertiesList.get(0);

    if (propertiesList.size() == 1) {
      return properties;
    }

    for (int i = 1; i < propertiesList.size(); i++) {
      Properties props = propertiesList.get(i);

      Enumeration<String> enu = (Enumeration<String>) props.propertyNames();

      while (enu.hasMoreElements()) {
        String key = enu.nextElement();

        String value = props.getProperty(key);

        if (Validator.isNull(value)) {
          continue;
        }

        if (properties.containsKey(key)) {
          String existingValue = properties.getProperty(key);

          if (Validator.isNotNull(existingValue)) {
            value = existingValue + StringPool.COMMA + value;
          }
        }

        properties.put(key, value);
      }
    }

    return properties;
  }

  private void _init(boolean useProperties, boolean printErrors, boolean autoFix) throws Exception {

    _errorMessagesMap = new HashMap<String, List<String>>();

    sourceFormatterHelper = new SourceFormatterHelper(useProperties);

    sourceFormatterHelper.init();

    _autoFix = autoFix;

    _excludes = _getExcludes();

    _printErrors = printErrors;

    _usePortalCompatImport = GetterUtil.getBoolean(getProperty("use.portal.compat.import"));
  }

  private boolean _isPortalSource() {
    if (getFile("portal-impl", 4) != null) {
      return true;
    } else {
      return false;
    }
  }

  private static Map<String, List<String>> _errorMessagesMap = new HashMap<String, List<String>>();
  private static boolean _printErrors;

  private boolean _autoFix;
  private Map<String, String> _compatClassNamesMap;
  private String _copyright;
  private String[] _excludes;
  private SourceMismatchException _firstSourceMismatchException;
  private String _mainReleaseVersion;
  private String _oldCopyright;
  private Properties _portalLanguageProperties;
  private Properties _properties;
  private List<String> _runOutsidePortalExclusions;
  private boolean _usePortalCompatImport;
}
/**
 * @author Brian Wing Shun Chan
 * @author Connor McKay
 * @author James Hinkey
 * @author Hugo Huijser
 */
public class JavadocFormatter {

  public static void main(String[] args) {
    try {
      new JavadocFormatter(args);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public JavadocFormatter(String[] args) throws Exception {
    Map<String, String> arguments = ArgumentsUtil.parseArguments(args);

    String init = arguments.get("javadoc.init");

    if (Validator.isNotNull(init) && !init.startsWith("$")) {
      _initializeMissingJavadocs = GetterUtil.getBoolean(init);
    }

    _inputDir = GetterUtil.getString(arguments.get("javadoc.input.dir"));

    if (_inputDir.startsWith("$")) {
      _inputDir = "./";
    }

    if (!_inputDir.endsWith("/")) {
      _inputDir += "/";
    }

    System.out.println("Input directory is " + _inputDir);

    String limit = arguments.get("javadoc.limit");

    _outputFilePrefix = GetterUtil.getString(arguments.get("javadoc.output.file.prefix"));

    if (_outputFilePrefix.startsWith("$")) {
      _outputFilePrefix = "javadocs";
    }

    String update = arguments.get("javadoc.update");

    if (Validator.isNotNull(update) && !update.startsWith("$")) {
      _updateJavadocs = GetterUtil.getBoolean(update);
    }

    DirectoryScanner directoryScanner = new DirectoryScanner();

    directoryScanner.setBasedir(_inputDir);
    directoryScanner.setExcludes(new String[] {"**\\classes\\**", "**\\portal-client\\**"});

    List<String> includes = new ArrayList<String>();

    if (Validator.isNotNull(limit) && !limit.startsWith("$")) {
      System.out.println("Limit on " + limit);

      String[] limitArray = StringUtil.split(limit, '/');

      for (String curLimit : limitArray) {
        includes.add("**\\" + StringUtil.replace(curLimit, ".", "\\") + "\\**\\*.java");
        includes.add("**\\" + curLimit + ".java");
      }
    } else {
      includes.add("**\\*.java");
    }

    directoryScanner.setIncludes(includes.toArray(new String[includes.size()]));

    directoryScanner.scan();

    String[] fileNames = directoryScanner.getIncludedFiles();

    if ((fileNames.length == 0) && Validator.isNotNull(limit) && !limit.startsWith("$")) {

      StringBundler sb = new StringBundler("Limit file not found: ");

      sb.append(limit);

      if (limit.contains(".")) {
        sb.append(" Specify limit filename without package path or ");
        sb.append("file type suffix.");
      }

      System.out.println(sb.toString());
    }

    _languagePropertiesFile = new File("src/content/Language.properties");

    if (_languagePropertiesFile.exists()) {
      _languageProperties = new Properties();

      _languageProperties.load(new FileInputStream(_languagePropertiesFile.getAbsolutePath()));
    }

    for (String fileName : fileNames) {
      fileName = StringUtil.replace(fileName, "\\", "/");

      _format(fileName);
    }

    for (Map.Entry<String, Tuple> entry : _javadocxXmlTuples.entrySet()) {
      Tuple tuple = entry.getValue();

      File javadocsXmlFile = (File) tuple.getObject(1);
      String oldJavadocsXmlContent = (String) tuple.getObject(2);
      Document javadocsXmlDocument = (Document) tuple.getObject(3);

      Element javadocsXmlRootElement = javadocsXmlDocument.getRootElement();

      javadocsXmlRootElement.sortElementsByChildElement("javadoc", "type");

      String newJavadocsXmlContent = javadocsXmlDocument.formattedString();

      if (!oldJavadocsXmlContent.equals(newJavadocsXmlContent)) {
        _fileUtil.write(javadocsXmlFile, newJavadocsXmlContent);
      }

      _detachUnnecessaryTypes(javadocsXmlRootElement);

      File javadocsRuntimeXmlFile =
          new File(StringUtil.replaceLast(javadocsXmlFile.toString(), "-all.xml", "-rt.xml"));

      String oldJavadocsRuntimeXmlContent = StringPool.BLANK;

      if (javadocsRuntimeXmlFile.exists()) {
        oldJavadocsRuntimeXmlContent = _fileUtil.read(javadocsRuntimeXmlFile);
      }

      String newJavadocsRuntimeXmlContent = javadocsXmlDocument.compactString();

      if (!oldJavadocsRuntimeXmlContent.equals(newJavadocsRuntimeXmlContent)) {

        _fileUtil.write(javadocsRuntimeXmlFile, newJavadocsRuntimeXmlContent);
      }
    }
  }

  private List<Tuple> _addAncestorJavaClassTuples(
      JavaClass javaClass, List<Tuple> ancestorJavaClassTuples) {

    JavaClass superJavaClass = javaClass.getSuperJavaClass();

    if (superJavaClass != null) {
      ancestorJavaClassTuples.add(new Tuple(superJavaClass));

      ancestorJavaClassTuples =
          _addAncestorJavaClassTuples(superJavaClass, ancestorJavaClassTuples);
    }

    Type[] implementz = javaClass.getImplements();

    for (Type implement : implementz) {
      Type[] actualTypeArguments = implement.getActualTypeArguments();
      JavaClass implementedInterface = implement.getJavaClass();

      if (actualTypeArguments == null) {
        ancestorJavaClassTuples.add(new Tuple(implementedInterface));
      } else {
        ancestorJavaClassTuples.add(new Tuple(implementedInterface, actualTypeArguments));
      }

      ancestorJavaClassTuples =
          _addAncestorJavaClassTuples(implementedInterface, ancestorJavaClassTuples);
    }

    return ancestorJavaClassTuples;
  }

  private void _addClassCommentElement(Element rootElement, JavaClass javaClass) {

    String comment = _getCDATA(javaClass);

    if (comment.startsWith("Copyright (c)")) {
      comment = StringPool.BLANK;
    }

    if (Validator.isNull(comment)) {
      return;
    }

    Element commentElement = rootElement.addElement("comment");

    commentElement.addCDATA(comment);
  }

  private void _addDocletElements(
      Element parentElement, AbstractJavaEntity abstractJavaEntity, String name) throws Exception {

    DocletTag[] docletTags = abstractJavaEntity.getTagsByName(name);

    for (DocletTag docletTag : docletTags) {
      String value = docletTag.getValue();

      value = _trimMultilineText(value);

      value = StringUtil.replace(value, " </", "</");

      Element element = parentElement.addElement(name);

      element.addCDATA(value);
    }

    if ((docletTags.length == 0) && name.equals("author")) {
      Element element = parentElement.addElement(name);

      element.addCDATA(ServiceBuilder.AUTHOR);
    }
  }

  private String _addDocletTags(
      Element parentElement, String[] tagNames, String indent, boolean publicAccess) {

    List<String> allTagNames = new ArrayList<String>();
    List<String> customTagNames = new ArrayList<String>();
    List<String> requiredTagNames = new ArrayList<String>();

    for (String tagName : tagNames) {
      List<Element> elements = parentElement.elements(tagName);

      for (Element element : elements) {
        Element commentElement = element.element("comment");

        String comment = null;

        // Get comment by comment element's text or the element's text

        if (commentElement != null) {
          comment = commentElement.getText();
        } else {
          comment = element.getText();
        }

        if (tagName.equals("param") || tagName.equals("return") || tagName.equals("throws")) {

          if (Validator.isNotNull(comment)) {
            requiredTagNames.add(tagName);
          } else if (tagName.equals("param")) {
            if (GetterUtil.getBoolean(element.elementText("required"))) {

              requiredTagNames.add(tagName);
            }
          } else if (tagName.equals("throws")) {
            if (GetterUtil.getBoolean(element.elementText("required"))) {

              requiredTagNames.add(tagName);
            }
          }
        } else {
          customTagNames.add(tagName);
        }

        allTagNames.add(tagName);
      }
    }

    int maxTagNameLength = 0;

    List<String> maxTagNameLengthTags = new ArrayList<String>();

    if (_initializeMissingJavadocs) {
      maxTagNameLengthTags.addAll(allTagNames);
    } else if (_updateJavadocs) {
      if (!requiredTagNames.isEmpty()) {
        maxTagNameLengthTags.addAll(allTagNames);
      } else {
        maxTagNameLengthTags.addAll(customTagNames);
        maxTagNameLengthTags.addAll(requiredTagNames);
      }
    } else {
      maxTagNameLengthTags.addAll(customTagNames);
      maxTagNameLengthTags.addAll(requiredTagNames);
    }

    for (String name : maxTagNameLengthTags) {
      if (name.length() > maxTagNameLength) {
        maxTagNameLength = name.length();
      }
    }

    // There should be an @ sign before the tag and a space after it

    maxTagNameLength += 2;

    String tagNameIndent = _getSpacesIndent(maxTagNameLength);

    StringBundler sb = new StringBundler();

    for (String tagName : tagNames) {
      List<Element> elements = parentElement.elements(tagName);

      for (Element element : elements) {
        Element commentElement = element.element("comment");

        String comment = null;

        if (commentElement != null) {
          comment = commentElement.getText();
        } else {
          comment = element.getText();
        }

        String elementName = element.elementText("name");

        if (Validator.isNotNull(comment)) {
          comment = _assembleTagComment(tagName, elementName, comment, indent, tagNameIndent);

          sb.append(comment);
        } else {
          if (_initializeMissingJavadocs && publicAccess) {

            // Write out all tags

            comment = _assembleTagComment(tagName, elementName, comment, indent, tagNameIndent);

            sb.append(comment);
          } else if (_updateJavadocs && publicAccess) {
            if (!tagName.equals("param")
                && !tagName.equals("return")
                && !tagName.equals("throws")) {

              // Write out custom tag

              comment = _assembleTagComment(tagName, elementName, comment, indent, tagNameIndent);

              sb.append(comment);
            } else if (!requiredTagNames.isEmpty()) {

              // Write out all tags

              comment = _assembleTagComment(tagName, elementName, comment, indent, tagNameIndent);

              sb.append(comment);
            } else {

              // Skip empty common tag

            }
          } else {
            if (!tagName.equals("param")
                && !tagName.equals("return")
                && !tagName.equals("throws")) {

              // Write out custom tag

              comment = _assembleTagComment(tagName, elementName, comment, indent, tagNameIndent);

              sb.append(comment);
            } else if (tagName.equals("param")
                || tagName.equals("return")
                || tagName.equals("throws")) {

              if (GetterUtil.getBoolean(element.elementText("required"))) {

                elementName = element.elementText("name");

                comment = _assembleTagComment(tagName, elementName, comment, indent, tagNameIndent);

                sb.append(comment);
              }
            } else {

              // Skip empty common tag

            }
          }
        }
      }
    }

    return sb.toString();
  }

  private void _addFieldElement(Element rootElement, JavaField javaField) throws Exception {

    Element fieldElement = rootElement.addElement("field");

    DocUtil.add(fieldElement, "name", javaField.getName());

    String comment = _getCDATA(javaField);

    if (Validator.isNotNull(comment)) {
      Element commentElement = fieldElement.addElement("comment");

      commentElement.addCDATA(comment);
    }

    _addDocletElements(fieldElement, javaField, "version");
    _addDocletElements(fieldElement, javaField, "see");
    _addDocletElements(fieldElement, javaField, "since");
    _addDocletElements(fieldElement, javaField, "deprecated");
  }

  private void _addMethodElement(Element rootElement, JavaMethod javaMethod) throws Exception {

    Element methodElement = rootElement.addElement("method");

    DocUtil.add(methodElement, "name", javaMethod.getName());

    String comment = _getCDATA(javaMethod);

    if (Validator.isNotNull(comment)) {
      Element commentElement = methodElement.addElement("comment");

      commentElement.addCDATA(_getCDATA(javaMethod));
    }

    _addDocletElements(methodElement, javaMethod, "version");
    _addParamElements(methodElement, javaMethod);
    _addReturnElement(methodElement, javaMethod);
    _addThrowsElements(methodElement, javaMethod);
    _addDocletElements(methodElement, javaMethod, "see");
    _addDocletElements(methodElement, javaMethod, "since");
    _addDocletElements(methodElement, javaMethod, "deprecated");
  }

  private void _addParamElement(
      Element methodElement, JavaParameter javaParameter, DocletTag[] paramDocletTags) {

    String name = javaParameter.getName();

    String value = null;

    for (DocletTag paramDocletTag : paramDocletTags) {
      String curValue = paramDocletTag.getValue();

      if (!curValue.startsWith(name)) {
        continue;
      } else {
        value = curValue;

        break;
      }
    }

    Element paramElement = methodElement.addElement("param");

    DocUtil.add(paramElement, "name", name);
    DocUtil.add(paramElement, "type", _getTypeValue(javaParameter));

    if (value != null) {
      value = value.substring(name.length());

      DocUtil.add(paramElement, "required", true);
    }

    value = _trimMultilineText(value);

    Element commentElement = paramElement.addElement("comment");

    commentElement.addCDATA(value);
  }

  private void _addParamElements(Element methodElement, JavaMethod javaMethod) {

    JavaParameter[] javaParameters = javaMethod.getParameters();

    DocletTag[] paramDocletTags = javaMethod.getTagsByName("param");

    for (JavaParameter javaParameter : javaParameters) {
      _addParamElement(methodElement, javaParameter, paramDocletTags);
    }
  }

  private void _addReturnElement(Element methodElement, JavaMethod javaMethod) throws Exception {

    Type returns = javaMethod.getReturns();

    if (returns == null) {
      return;
    }

    String returnsValue = returns.getValue();

    if (returnsValue.equals("void")) {
      return;
    }

    Element returnElement = methodElement.addElement("return");

    DocletTag[] returnDocletTags = javaMethod.getTagsByName("return");

    String comment = StringPool.BLANK;

    if (returnDocletTags.length > 0) {
      DocletTag returnDocletTag = returnDocletTags[0];

      comment = GetterUtil.getString(returnDocletTag.getValue());

      DocUtil.add(returnElement, "required", true);
    }

    comment = _trimMultilineText(comment);

    Element commentElement = returnElement.addElement("comment");

    commentElement.addCDATA(comment);
  }

  private void _addThrowsElement(
      Element methodElement, Type exceptionType, DocletTag[] throwsDocletTags) {

    JavaClass javaClass = exceptionType.getJavaClass();

    String name = javaClass.getName();

    String value = null;

    for (DocletTag throwsDocletTag : throwsDocletTags) {
      String curValue = throwsDocletTag.getValue();

      if (!curValue.startsWith(name)) {
        continue;
      } else {
        value = curValue;

        break;
      }
    }

    Element throwsElement = methodElement.addElement("throws");

    DocUtil.add(throwsElement, "name", name);
    DocUtil.add(throwsElement, "type", exceptionType.getValue());

    if (value != null) {
      value = value.substring(name.length());

      DocUtil.add(throwsElement, "required", true);
    }

    value = _trimMultilineText(value);

    Element commentElement = throwsElement.addElement("comment");

    commentElement.addCDATA(_getCDATA(value));
  }

  private void _addThrowsElements(Element methodElement, JavaMethod javaMethod) {

    Type[] exceptionTypes = javaMethod.getExceptions();

    DocletTag[] throwsDocletTags = javaMethod.getTagsByName("throws");

    for (Type exceptionType : exceptionTypes) {
      _addThrowsElement(methodElement, exceptionType, throwsDocletTags);
    }
  }

  private String _assembleTagComment(
      String tagName, String elementName, String comment, String indent, String tagNameIndent) {

    String indentAndTagName = indent + StringPool.AT + tagName;

    if (Validator.isNotNull(elementName)) {
      if (Validator.isNotNull(comment)) {
        comment = elementName + StringPool.SPACE + comment;
      } else {
        comment = elementName;
      }

      // <name indent> elementName [comment]

      comment = _wrapText(comment, indent + tagNameIndent);

      // * @name <name indent> elementName [comment]

      comment = indentAndTagName + comment.substring(indentAndTagName.length());
    } else {
      if (Validator.isNotNull(comment)) {

        // <name indent> comment

        comment = _wrapText(comment, indent + tagNameIndent);

        // * @name <name indent> comment

        comment = indentAndTagName + comment.substring(indentAndTagName.length());
      } else {

        // * @name

        comment = indentAndTagName + "\n";
      }
    }

    return comment;
  }

  private void _detachUnnecessaryTypes(Element rootElement) {
    List<Element> elements = rootElement.elements();

    for (Element element : elements) {
      String type = element.elementText("type");

      if (!type.contains(".service.") || !type.endsWith("ServiceImpl")) {
        element.detach();
      }
    }
  }

  private void _format(String fileName) throws Exception {
    InputStream inputStream = new FileInputStream(_inputDir + fileName);

    byte[] bytes = new byte[inputStream.available()];

    inputStream.read(bytes);

    inputStream.close();

    String originalContent = new String(bytes, StringPool.UTF8);

    if (fileName.endsWith("JavadocFormatter.java")
        || fileName.endsWith("SourceFormatter.java")
        || _hasGeneratedTag(originalContent)) {

      return;
    }

    JavaClass javaClass = _getJavaClass(fileName, new UnsyncStringReader(originalContent));

    String javadocLessContent = _removeJavadocFromJava(javaClass, originalContent);

    Document document = _getJavadocDocument(javaClass);

    _updateJavadocsXmlFile(fileName, javaClass, document);

    _updateJavaFromDocument(fileName, originalContent, javadocLessContent, document);
  }

  private String _formatCDATA(String cdata, String exclude) {
    StringBundler sb = new StringBundler();

    String startTag = "<" + exclude + ">";
    String endTag = "</" + exclude + ">";

    String[] cdataParts = cdata.split(startTag);

    for (String cdataPart : cdataParts) {
      if (!cdataPart.contains(endTag)) {
        cdataPart = _getCDATA(cdataPart);
      }

      if (cdataPart.contains("</" + exclude + ">")) {
        sb.append(startTag);
      }

      sb.append(cdataPart);
    }

    return sb.toString();
  }

  private String _formatInlines(String text) {

    // Capitalize ID

    text = text.replaceAll("[?@param id](?i)\\bid(s)?\\b", " ID$1");

    // Wrap special constants in code tags

    text = text.replaceAll("(?i)(?<!<code>|\\w)(null|false|true)(?!\\w)", "<code>$1</code>");

    return text;
  }

  private String _getCDATA(AbstractJavaEntity abstractJavaEntity) {
    return _getCDATA(abstractJavaEntity.getComment());
  }

  private String _getCDATA(String cdata) {
    if (cdata == null) {
      return StringPool.BLANK;
    } else if (cdata.contains("<pre>")) {
      cdata = _formatCDATA(cdata, "pre");
    } else if (cdata.contains("<table>")) {
      cdata = _formatCDATA(cdata, "table");
    } else {
      cdata =
          cdata.replaceAll("(?s)\\s*<(p|[ou]l)>\\s*(.*?)\\s*</\\1>\\s*", "\n\n<$1>\n$2\n</$1>\n\n");
      cdata = cdata.replaceAll("(?s)\\s*<li>\\s*(.*?)\\s*</li>\\s*", "\n<li>\n$1\n</li>\n");
      cdata = StringUtil.replace(cdata, "</li>\n\n<li>", "</li>\n<li>");
      cdata = cdata.replaceAll("\n\\s+\n", "\n\n");
      cdata.replaceAll(" +", " ");

      // Trim whitespace inside paragraph tags or in the first paragraph

      Pattern pattern =
          Pattern.compile("(^.*?(?=\n\n|$)+|(?<=<p>\n).*?(?=\n</p>))", Pattern.DOTALL);

      Matcher matcher = pattern.matcher(cdata);

      StringBuffer sb = new StringBuffer();

      while (matcher.find()) {
        String trimmed = _trimMultilineText(matcher.group());

        // Escape dollar signs

        trimmed = trimmed.replaceAll("\\$", "\\\\\\$");

        matcher.appendReplacement(sb, trimmed);
      }

      matcher.appendTail(sb);

      cdata = sb.toString();
    }

    return cdata.trim();
  }

  private String _getClassName(String fileName) {
    int pos = fileName.indexOf("src/");

    if (pos == -1) {
      pos = fileName.indexOf("test/integration/");

      if (pos != -1) {
        pos = fileName.indexOf("integration/", pos);
      }
    }

    if (pos == -1) {
      pos = fileName.indexOf("test/unit/");

      if (pos != -1) {
        pos = fileName.indexOf("unit/", pos);
      }
    }

    if (pos == -1) {
      pos = fileName.indexOf("test/");
    }

    if (pos == -1) {
      pos = fileName.indexOf("service/");
    }

    if (pos == -1) {
      throw new RuntimeException(fileName);
    }

    pos = fileName.indexOf("/", pos);

    String srcFile = fileName.substring(pos + 1, fileName.length());

    return StringUtil.replace(srcFile.substring(0, srcFile.length() - 5), "/", ".");
  }

  private String _getFieldKey(Element fieldElement) {
    return fieldElement.elementText("name");
  }

  private String _getFieldKey(JavaField javaField) {
    return javaField.getName();
  }

  private String _getIndent(String[] lines, AbstractBaseJavaEntity abstractBaseJavaEntity) {

    String line = lines[abstractBaseJavaEntity.getLineNumber() - 1];

    String indent = StringPool.BLANK;

    for (char c : line.toCharArray()) {
      if (Character.isWhitespace(c)) {
        indent += c;
      } else {
        break;
      }
    }

    return indent;
  }

  private int _getIndentLength(String indent) {
    int indentLength = 0;

    for (char c : indent.toCharArray()) {
      if (c == '\t') {
        indentLength = indentLength + 4;
      } else {
        indentLength++;
      }
    }

    return indentLength;
  }

  private JavaClass _getJavaClass(String fileName, Reader reader) throws Exception {

    String className = _getClassName(fileName);

    JavaDocBuilder javadocBuilder = new JavaDocBuilder();

    if (reader == null) {
      File file = new File(fileName);

      if (!file.exists()) {
        return null;
      }

      javadocBuilder.addSource(file);
    } else {
      javadocBuilder.addSource(reader);
    }

    return javadocBuilder.getClassByName(className);
  }

  private String _getJavaClassComment(Element rootElement, JavaClass javaClass) {

    StringBundler sb = new StringBundler();

    String indent = StringPool.BLANK;

    sb.append("/**\n");

    String comment = rootElement.elementText("comment");

    if (Validator.isNotNull(comment)) {
      sb.append(_wrapText(comment, indent + " * "));
    }

    String docletTags =
        _addDocletTags(
            rootElement,
            new String[] {"author", "version", "see", "since", "serial", "deprecated"},
            indent + " * ",
            _hasPublicModifier(javaClass));

    if (Validator.isNotNull(docletTags)) {
      if (_initializeMissingJavadocs || Validator.isNotNull(comment)) {
        sb.append(" *\n");
      }

      sb.append(docletTags);
    }

    sb.append(" */\n");

    return sb.toString();
  }

  private int _getJavaClassLineNumber(JavaClass javaClass) {
    int lineNumber = javaClass.getLineNumber();

    Annotation[] annotations = javaClass.getAnnotations();

    if (annotations.length == 0) {
      return lineNumber;
    }

    for (Annotation annotation : annotations) {
      int annotationLineNumber = annotation.getLineNumber();

      Map<String, String> propertyMap = annotation.getPropertyMap();

      if (propertyMap.isEmpty()) {
        annotationLineNumber--;
      }

      if (annotationLineNumber < lineNumber) {
        lineNumber = annotationLineNumber;
      }
    }

    return lineNumber;
  }

  private Document _getJavadocDocument(JavaClass javaClass) throws Exception {
    Element rootElement = _saxReaderUtil.createElement("javadoc");

    Document document = _saxReaderUtil.createDocument(rootElement);

    DocUtil.add(rootElement, "name", javaClass.getName());
    DocUtil.add(rootElement, "type", javaClass.getFullyQualifiedName());

    _addClassCommentElement(rootElement, javaClass);
    _addDocletElements(rootElement, javaClass, "author");
    _addDocletElements(rootElement, javaClass, "version");
    _addDocletElements(rootElement, javaClass, "see");
    _addDocletElements(rootElement, javaClass, "since");
    _addDocletElements(rootElement, javaClass, "serial");
    _addDocletElements(rootElement, javaClass, "deprecated");

    JavaMethod[] javaMethods = javaClass.getMethods();

    for (JavaMethod javaMethod : javaMethods) {
      _addMethodElement(rootElement, javaMethod);
    }

    JavaField[] javaFields = javaClass.getFields();

    for (JavaField javaField : javaFields) {
      _addFieldElement(rootElement, javaField);
    }

    return document;
  }

  private Tuple _getJavadocsXmlTuple(String fileName) throws Exception {
    File file = new File(_inputDir + fileName);

    String absolutePath = file.getAbsolutePath();

    absolutePath = StringUtil.replace(absolutePath, "\\", "/");
    absolutePath = StringUtil.replace(absolutePath, "/./", "/");

    int pos = absolutePath.indexOf("/portal-impl/src/");

    String srcDirName = null;

    if (pos != -1) {
      srcDirName = absolutePath.substring(0, pos + 17);
    }

    if (srcDirName == null) {
      pos = absolutePath.indexOf("/portal-kernel/src/");

      if (pos == -1) {
        pos = absolutePath.indexOf("/portal-service/src/");
      }

      if (pos == -1) {
        pos = absolutePath.indexOf("/util-bridges/src/");
      }

      if (pos == -1) {
        pos = absolutePath.indexOf("/util-java/src/");
      }

      if (pos == -1) {
        pos = absolutePath.indexOf("/util-taglib/src/");
      }

      if (pos != -1) {
        srcDirName = absolutePath.substring(0, pos) + "/portal-impl/src/";
      }
    }

    if (srcDirName == null) {
      pos = absolutePath.indexOf("/WEB-INF/src/");

      if (pos != -1) {
        srcDirName = absolutePath.substring(0, pos + 13);
      }
    }

    if (srcDirName == null) {
      return null;
    }

    Tuple tuple = _javadocxXmlTuples.get(srcDirName);

    if (tuple != null) {
      return tuple;
    }

    File javadocsXmlFile = new File(srcDirName, "META-INF/" + _outputFilePrefix + "-all.xml");

    if (!javadocsXmlFile.exists()) {
      _fileUtil.write(javadocsXmlFile, "<?xml version=\"1.0\"?>\n\n<javadocs>\n</javadocs>");
    }

    String javadocsXmlContent = _fileUtil.read(javadocsXmlFile);

    Document javadocsXmlDocument = _saxReaderUtil.read(javadocsXmlContent);

    tuple = new Tuple(srcDirName, javadocsXmlFile, javadocsXmlContent, javadocsXmlDocument);

    _javadocxXmlTuples.put(srcDirName, tuple);

    return tuple;
  }

  private String _getJavaFieldComment(
      String[] lines, Map<String, Element> fieldElementsMap, JavaField javaField) {

    String fieldKey = _getFieldKey(javaField);

    Element fieldElement = fieldElementsMap.get(fieldKey);

    if (fieldElement == null) {
      return null;
    }

    String indent = _getIndent(lines, javaField);

    StringBundler sb = new StringBundler();

    sb.append(indent);
    sb.append("/**\n");

    String comment = fieldElement.elementText("comment");

    if (Validator.isNotNull(comment)) {
      sb.append(_wrapText(comment, indent + " * "));
    }

    String docletTags =
        _addDocletTags(
            fieldElement,
            new String[] {"version", "see", "since", "deprecated"},
            indent + " * ",
            _hasPublicModifier(javaField));

    if (Validator.isNotNull(docletTags)) {
      if (_initializeMissingJavadocs || Validator.isNotNull(comment)) {
        sb.append(indent);
        sb.append(" *\n");
      }

      sb.append(docletTags);
    }

    sb.append(indent);
    sb.append(" */\n");

    if (!_initializeMissingJavadocs && Validator.isNull(comment) && Validator.isNull(docletTags)) {

      return null;
    }

    if (!_hasPublicModifier(javaField)
        && Validator.isNull(comment)
        && Validator.isNull(docletTags)) {

      return null;
    }

    return sb.toString();
  }

  private String _getJavaMethodComment(
      String[] lines, Map<String, Element> methodElementsMap, JavaMethod javaMethod) {

    String methodKey = _getMethodKey(javaMethod);

    Element methodElement = methodElementsMap.get(methodKey);

    if (methodElement == null) {
      return null;
    }

    String indent = _getIndent(lines, javaMethod);

    StringBundler sb = new StringBundler();

    sb.append(indent);
    sb.append("/**\n");

    String comment = methodElement.elementText("comment");

    if (Validator.isNotNull(comment)) {
      sb.append(_wrapText(comment, indent + " * "));
    }

    String docletTags =
        _addDocletTags(
            methodElement,
            new String[] {"version", "param", "return", "throws", "see", "since", "deprecated"},
            indent + " * ",
            _hasPublicModifier(javaMethod));

    if (Validator.isNotNull(docletTags)) {
      if (_initializeMissingJavadocs || Validator.isNotNull(comment)) {
        sb.append(indent);
        sb.append(" *\n");
      }

      sb.append(docletTags);
    }

    sb.append(indent);
    sb.append(" */\n");

    if (!_initializeMissingJavadocs && Validator.isNull(comment) && Validator.isNull(docletTags)) {

      return null;
    }

    if (!_hasPublicModifier(javaMethod)
        && Validator.isNull(comment)
        && Validator.isNull(docletTags)) {

      return null;
    }

    return sb.toString();
  }

  private String _getMethodKey(Element methodElement) {
    StringBundler sb = new StringBundler();

    sb.append(methodElement.elementText("name"));
    sb.append(StringPool.OPEN_PARENTHESIS);

    List<Element> paramElements = methodElement.elements("param");

    for (Element paramElement : paramElements) {
      sb.append(paramElement.elementText("name"));
      sb.append("|");
      sb.append(paramElement.elementText("type"));
      sb.append(",");
    }

    sb.append(StringPool.CLOSE_PARENTHESIS);

    return sb.toString();
  }

  private String _getMethodKey(JavaMethod javaMethod) {
    StringBundler sb = new StringBundler();

    sb.append(javaMethod.getName());
    sb.append(StringPool.OPEN_PARENTHESIS);

    JavaParameter[] javaParameters = javaMethod.getParameters();

    for (JavaParameter javaParameter : javaParameters) {
      sb.append(javaParameter.getName());
      sb.append("|");
      sb.append(_getTypeValue(javaParameter));
      sb.append(",");
    }

    sb.append(StringPool.CLOSE_PARENTHESIS);

    return sb.toString();
  }

  private String _getSpacesIndent(int length) {
    String indent = StringPool.BLANK;

    for (int i = 0; i < length; i++) {
      indent += StringPool.SPACE;
    }

    return indent;
  }

  private String _getTypeValue(JavaParameter javaParameter) {
    Type type = javaParameter.getType();

    String typeValue = type.getValue();

    if (type.isArray()) {
      typeValue += "[]";
    }

    return typeValue;
  }

  private boolean _hasAnnotation(
      AbstractBaseJavaEntity abstractBaseJavaEntity, String annotationName) {

    Annotation[] annotations = abstractBaseJavaEntity.getAnnotations();

    if (annotations == null) {
      return false;
    }

    for (int i = 0; i < annotations.length; i++) {
      Type type = annotations[i].getType();

      JavaClass javaClass = type.getJavaClass();

      if (annotationName.equals(javaClass.getName())) {
        return true;
      }
    }

    return false;
  }

  private boolean _hasGeneratedTag(String content) {
    if (content.contains("* @generated") || content.contains("$ANTLR")) {
      return true;
    } else {
      return false;
    }
  }

  private boolean _hasPublicModifier(AbstractJavaEntity abstractJavaEntity) {
    String[] modifiers = abstractJavaEntity.getModifiers();

    if (modifiers == null) {
      return false;
    }

    for (String modifier : modifiers) {
      if (modifier.equals("public")) {
        return true;
      }
    }

    return false;
  }

  private boolean _isOverrideMethod(
      JavaClass javaClass, JavaMethod javaMethod, Collection<Tuple> ancestorJavaClassTuples) {

    if (javaMethod.isConstructor() || javaMethod.isPrivate() || javaMethod.isStatic()) {

      return false;
    }

    String methodName = javaMethod.getName();

    JavaParameter[] javaParameters = javaMethod.getParameters();

    Type[] types = new Type[javaParameters.length];

    for (int i = 0; i < javaParameters.length; i++) {
      types[i] = javaParameters[i].getType();
    }

    // Check for matching method in each ancestor

    for (Tuple ancestorJavaClassTuple : ancestorJavaClassTuples) {
      JavaClass ancestorJavaClass = (JavaClass) ancestorJavaClassTuple.getObject(0);

      JavaMethod ancestorJavaMethod = null;

      if (ancestorJavaClassTuple.getSize() > 1) {

        // LPS-35613

        Type[] ancestorActualTypeArguments = (Type[]) ancestorJavaClassTuple.getObject(1);

        Type[] genericTypes = new Type[types.length];

        for (int i = 0; i < types.length; i++) {
          Type type = types[i];

          String typeValue = type.getValue();

          boolean useGenericType = false;

          for (int j = 0; j < ancestorActualTypeArguments.length; j++) {

            if (typeValue.equals(ancestorActualTypeArguments[j].getValue())) {

              useGenericType = true;

              break;
            }
          }

          if (useGenericType) {
            genericTypes[i] = new Type("java.lang.Object");
          } else {
            genericTypes[i] = type;
          }
        }

        ancestorJavaMethod = ancestorJavaClass.getMethodBySignature(methodName, genericTypes);
      } else {
        ancestorJavaMethod = ancestorJavaClass.getMethodBySignature(methodName, types);
      }

      if (ancestorJavaMethod == null) {
        continue;
      }

      boolean samePackage = false;

      JavaPackage ancestorJavaPackage = ancestorJavaClass.getPackage();

      if (ancestorJavaPackage != null) {
        samePackage = ancestorJavaPackage.equals(javaClass.getPackage());
      }

      // Check if the method is in scope

      if (samePackage) {
        return !ancestorJavaMethod.isPrivate();
      } else {
        if (ancestorJavaMethod.isProtected() || ancestorJavaMethod.isPublic()) {

          return true;
        } else {
          return false;
        }
      }
    }

    return false;
  }

  private String _removeJavadocFromJava(JavaClass javaClass, String content) {
    Set<Integer> lineNumbers = new HashSet<Integer>();

    lineNumbers.add(_getJavaClassLineNumber(javaClass));

    JavaMethod[] javaMethods = javaClass.getMethods();

    for (JavaMethod javaMethod : javaMethods) {
      lineNumbers.add(javaMethod.getLineNumber());
    }

    JavaField[] javaFields = javaClass.getFields();

    for (JavaField javaField : javaFields) {
      lineNumbers.add(javaField.getLineNumber());
    }

    String[] lines = StringUtil.splitLines(content);

    for (int lineNumber : lineNumbers) {
      if (lineNumber == 0) {
        continue;
      }

      int pos = lineNumber - 2;

      String line = lines[pos];

      if (line == null) {
        continue;
      }

      line = line.trim();

      if (line.endsWith("*/")) {
        while (true) {
          lines[pos] = null;

          if (line.startsWith("/**") || line.startsWith("/*")) {
            break;
          }

          line = lines[--pos].trim();
        }
      }
    }

    StringBundler sb = new StringBundler(content.length());

    for (String line : lines) {
      if (line != null) {
        sb.append(line);
        sb.append("\n");
      }
    }

    return sb.toString().trim();
  }

  private String _trimMultilineText(String text) {
    String[] textArray = StringUtil.splitLines(text);

    for (int i = 0; i < textArray.length; i++) {
      textArray[i] = textArray[i].trim();
    }

    return StringUtil.merge(textArray, " ");
  }

  private void _updateJavadocsXmlFile(
      String fileName, JavaClass javaClass, Document javaClassDocument) throws Exception {

    String javaClassFullyQualifiedName = javaClass.getFullyQualifiedName();

    /*if (!javaClassFullyQualifiedName.contains(".service.") ||
    	!javaClassFullyQualifiedName.endsWith("ServiceImpl")) {

    	return;
    }*/

    Tuple javadocsXmlTuple = _getJavadocsXmlTuple(fileName);

    if (javadocsXmlTuple == null) {
      return;
    }

    Document javadocsXmlDocument = (Document) javadocsXmlTuple.getObject(3);

    Element javadocsXmlRootElement = javadocsXmlDocument.getRootElement();

    List<Element> javadocElements = javadocsXmlRootElement.elements("javadoc");

    for (Element javadocElement : javadocElements) {
      String type = javadocElement.elementText("type");

      if (type.equals(javaClassFullyQualifiedName)) {
        Element javaClassRootElement = javaClassDocument.getRootElement();

        if (Validator.equals(
            javadocElement.formattedString(), javaClassRootElement.formattedString())) {

          return;
        }

        javadocElement.detach();

        break;
      }
    }

    javadocsXmlRootElement.add(javaClassDocument.getRootElement());
  }

  private void _updateJavaFromDocument(
      String fileName, String originalContent, String javadocLessContent, Document document)
      throws Exception {

    String[] lines = StringUtil.splitLines(javadocLessContent);

    JavaClass javaClass = _getJavaClass(fileName, new UnsyncStringReader(javadocLessContent));

    _updateLanguageProperties(document, javaClass.getName());

    List<Tuple> ancestorJavaClassTuples = new ArrayList<Tuple>();

    ancestorJavaClassTuples = _addAncestorJavaClassTuples(javaClass, ancestorJavaClassTuples);

    Element rootElement = document.getRootElement();

    Map<Integer, String> commentsMap = new TreeMap<Integer, String>();

    commentsMap.put(
        _getJavaClassLineNumber(javaClass), _getJavaClassComment(rootElement, javaClass));

    Map<String, Element> methodElementsMap = new HashMap<String, Element>();

    List<Element> methodElements = rootElement.elements("method");

    for (Element methodElement : methodElements) {
      String methodKey = _getMethodKey(methodElement);

      methodElementsMap.put(methodKey, methodElement);
    }

    JavaMethod[] javaMethods = javaClass.getMethods();

    for (JavaMethod javaMethod : javaMethods) {
      if (commentsMap.containsKey(javaMethod.getLineNumber())) {
        continue;
      }

      String javaMethodComment = _getJavaMethodComment(lines, methodElementsMap, javaMethod);

      // Handle override tag insertion

      if (!_hasAnnotation(javaMethod, "Override")) {
        if (_isOverrideMethod(javaClass, javaMethod, ancestorJavaClassTuples)) {

          String overrideLine = _getIndent(lines, javaMethod) + "@Override\n";

          if (Validator.isNotNull(javaMethodComment)) {
            javaMethodComment = javaMethodComment + overrideLine;
          } else {
            javaMethodComment = overrideLine;
          }
        }
      }

      commentsMap.put(javaMethod.getLineNumber(), javaMethodComment);
    }

    Map<String, Element> fieldElementsMap = new HashMap<String, Element>();

    List<Element> fieldElements = rootElement.elements("field");

    for (Element fieldElement : fieldElements) {
      String fieldKey = _getFieldKey(fieldElement);

      fieldElementsMap.put(fieldKey, fieldElement);
    }

    JavaField[] javaFields = javaClass.getFields();

    for (JavaField javaField : javaFields) {
      if (commentsMap.containsKey(javaField.getLineNumber())) {
        continue;
      }

      commentsMap.put(
          javaField.getLineNumber(), _getJavaFieldComment(lines, fieldElementsMap, javaField));
    }

    StringBundler sb = new StringBundler(javadocLessContent.length());

    for (int lineNumber = 1; lineNumber <= lines.length; lineNumber++) {
      String line = lines[lineNumber - 1];

      String comments = commentsMap.get(lineNumber);

      if (comments != null) {
        sb.append(comments);
      }

      sb.append(line);
      sb.append("\n");
    }

    String formattedContent = sb.toString().trim();

    if (!originalContent.equals(formattedContent)) {
      File file = new File(_inputDir + fileName);

      _fileUtil.write(file, formattedContent.getBytes(StringPool.UTF8));

      System.out.println("Writing " + file);
    }
  }

  private void _updateLanguageProperties(Document document, String className) throws IOException {

    if (_languageProperties == null) {
      return;
    }

    int index = className.indexOf("ServiceImpl");

    if (index <= 0) {
      return;
    }

    StringBundler sb = new StringBundler();

    sb.append(Character.toLowerCase(className.charAt(0)));

    for (int i = 1; i < index; i++) {
      char c = className.charAt(i);

      if (Character.isUpperCase(c)) {
        if (((i + 1) < index) && Character.isLowerCase(className.charAt(i + 1))) {

          sb.append(CharPool.DASH);
        }

        sb.append(Character.toLowerCase(c));
      } else {
        sb.append(c);
      }
    }

    sb.append("-service-help");

    String key = sb.toString();

    String value = _languageProperties.getProperty(key);

    if (value == null) {
      return;
    }

    Element rootElement = document.getRootElement();

    String comment = rootElement.elementText("comment");

    if ((comment == null) || value.equals(comment)) {
      return;
    }

    index = comment.indexOf("\n\n");

    if (index != -1) {
      value = comment.substring(0, index);
    } else {
      value = comment;
    }

    _updateLanguageProperties(key, value);
  }

  private void _updateLanguageProperties(String key, String value) throws IOException {

    UnsyncBufferedReader unsyncBufferedReader =
        new UnsyncBufferedReader(new FileReader(_languagePropertiesFile));

    StringBundler sb = new StringBundler();

    boolean begin = false;
    boolean firstLine = true;
    String linePrefix = key + "=";

    String line = null;

    while ((line = unsyncBufferedReader.readLine()) != null) {
      if (line.equals(StringPool.BLANK)) {
        begin = !begin;
      }

      if (firstLine) {
        firstLine = false;
      } else {
        sb.append(StringPool.NEW_LINE);
      }

      if (line.startsWith(linePrefix)) {
        sb.append(linePrefix + value);
      } else {
        sb.append(line);
      }
    }

    unsyncBufferedReader.close();

    Writer writer =
        new OutputStreamWriter(
            new FileOutputStream(_languagePropertiesFile, false), StringPool.UTF8);

    writer.write(sb.toString());

    writer.close();

    System.out.println("Updating " + _languagePropertiesFile + " key " + key);
  }

  private String _wrapText(String text, int indentLength, String exclude) {
    StringBuffer sb = new StringBuffer();

    StringBundler regexSB = new StringBundler("(?<=^|</");

    regexSB.append(exclude);
    regexSB.append(">).+?(?=$|<");
    regexSB.append(exclude);
    regexSB.append(">)");

    Pattern pattern = Pattern.compile(regexSB.toString(), Pattern.DOTALL);

    Matcher matcher = pattern.matcher(text);

    while (matcher.find()) {
      String wrapped = _formatInlines(matcher.group());

      wrapped = StringUtil.wrap(wrapped, 80 - indentLength, "\n");

      matcher.appendReplacement(sb, wrapped);
    }

    matcher.appendTail(sb);

    return sb.toString();
  }

  private String _wrapText(String text, String indent) {
    int indentLength = _getIndentLength(indent);

    if (text.contains("<pre>")) {
      text = _wrapText(text, indentLength, "pre");
    } else if (text.contains("<table>")) {
      text = _wrapText(text, indentLength, "table");
    } else {
      text = _formatInlines(text);
      text = StringUtil.wrap(text, 80 - indentLength, "\n");
    }

    text = text.replaceAll("(?m)^", indent);
    text = text.replaceAll("(?m) +$", StringPool.BLANK);

    return text;
  }

  private static FileImpl _fileUtil = FileImpl.getInstance();
  private static SAXReaderImpl _saxReaderUtil = SAXReaderImpl.getInstance();

  private boolean _initializeMissingJavadocs;
  private String _inputDir;
  private Map<String, Tuple> _javadocxXmlTuples = new HashMap<String, Tuple>();
  private Properties _languageProperties;
  private File _languagePropertiesFile;
  private String _outputFilePrefix;
  private boolean _updateJavadocs;
}
/** @author Brian Wing Shun Chan */
public class JavadocBuilder {

  public static void main(String[] args) {
    try {
      new JavadocBuilder(args);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public JavadocBuilder(String[] args) throws Exception {
    CmdLineParser cmdLineParser = new CmdLineParser();

    CmdLineParser.Option commandOption = cmdLineParser.addStringOption("command");
    CmdLineParser.Option limitOption = cmdLineParser.addStringOption("limit");
    CmdLineParser.Option ignoreAutogeneratedOption =
        cmdLineParser.addBooleanOption("ignoreAutogenerated");

    cmdLineParser.parse(args);

    String command = (String) cmdLineParser.getOptionValue(commandOption);
    String limit = (String) cmdLineParser.getOptionValue(limitOption);
    Boolean ignoreAutogenerated = (Boolean) cmdLineParser.getOptionValue(ignoreAutogeneratedOption);

    _process(command, limit, ignoreAutogenerated);
  }

  private void _addClassCommentElement(Element rootElement, JavaClass javaClass) {

    Element commentElement = rootElement.addElement("comment");

    String comment = _getCDATA(javaClass);

    if (comment.startsWith("Copyright (c) 2000-2010 Liferay, Inc.")) {
      comment = StringPool.BLANK;
    }

    if (comment.startsWith("<a href=\"" + javaClass.getName() + ".java.html\">")) {

      int pos = comment.indexOf("</a>");

      comment = comment.substring(pos + 4).trim();
    }

    commentElement.addCDATA(comment);
  }

  private void _addDocletElements(
      Element parentElement, AbstractJavaEntity abstractJavaEntity, String name) {

    DocletTag[] docletTags = abstractJavaEntity.getTagsByName(name);

    for (DocletTag docletTag : docletTags) {
      String value = docletTag.getValue();

      if (name.equals("author")
          || name.equals("see")
          || name.equals("since")
          || name.equals("version")) {

        /*if (value.startsWith("Raymond Aug")) {
        	value = "Raymond Aug\u00c3\u00a9";
        }*/

        DocUtil.add(parentElement, name, value);
      } else {
        Element element = parentElement.addElement(name);

        element.addCDATA(value);
      }
    }
  }

  private void _addDocletTags(Element parentElement, String name, String indent, StringBuilder sb) {

    List<Element> elements = parentElement.elements(name);

    for (Element element : elements) {
      sb.append(indent);
      sb.append(" * @");
      sb.append(name);
      sb.append(" ");

      Element commentElement = element.element("comment");

      if (commentElement != null) {
        sb.append(element.elementText("name"));
        sb.append(" ");
        sb.append(_getCDATA(element.elementText("comment")));
      } else {
        sb.append(_getCDATA(element.getText()));
      }

      sb.append("\n");
    }
  }

  private void _addFieldElement(Element rootElement, JavaField javaField) {
    Element fieldElement = rootElement.addElement("field");

    DocUtil.add(fieldElement, "name", javaField.getName());

    Element commentElement = fieldElement.addElement("comment");

    commentElement.addCDATA(_getCDATA(javaField));

    _addDocletElements(fieldElement, javaField, "deprecated");
    _addDocletElements(fieldElement, javaField, "see");
    _addDocletElements(fieldElement, javaField, "since");
    _addDocletElements(fieldElement, javaField, "version");
  }

  private void _addMethodElement(Element rootElement, JavaMethod javaMethod) {
    Element methodElement = rootElement.addElement("method");

    DocUtil.add(methodElement, "name", javaMethod.getName());

    Element commentElement = methodElement.addElement("comment");

    commentElement.addCDATA(_getCDATA(javaMethod));

    _addDocletElements(methodElement, javaMethod, "deprecated");
    _addParamElements(methodElement, javaMethod);
    _addReturnElement(methodElement, javaMethod);
    _addDocletElements(methodElement, javaMethod, "see");
    _addDocletElements(methodElement, javaMethod, "since");
    _addThrowsElements(methodElement, javaMethod);
    _addDocletElements(methodElement, javaMethod, "version");
  }

  private void _addParamElement(
      Element methodElement, JavaParameter javaParameter, DocletTag[] paramDocletTags) {

    String name = javaParameter.getName();
    String type = javaParameter.getType().getValue();
    String value = null;

    for (DocletTag paramDocletTag : paramDocletTags) {
      String curValue = paramDocletTag.getValue();

      if (!curValue.startsWith(name)) {
        continue;
      } else {
        curValue = value;

        break;
      }
    }

    Element paramElement = methodElement.addElement("param");

    DocUtil.add(paramElement, "name", name);
    DocUtil.add(paramElement, "type", type);

    if (value != null) {
      value = value.substring(name.length());
    }

    Element commentElement = paramElement.addElement("comment");

    commentElement.addCDATA(_getCDATA(value));
  }

  private void _addParamElements(Element methodElement, JavaMethod javaMethod) {

    JavaParameter[] javaParameters = javaMethod.getParameters();

    DocletTag[] paramDocletTags = javaMethod.getTagsByName("param");

    for (JavaParameter javaParameter : javaParameters) {
      _addParamElement(methodElement, javaParameter, paramDocletTags);
    }
  }

  private void _addReturnElement(Element methodElement, JavaMethod javaMethod) {

    Type returns = javaMethod.getReturns();

    if ((returns == null) || returns.getValue().equals("void")) {
      return;
    }

    _addDocletElements(methodElement, javaMethod, "return");
  }

  private void _addThrowsElement(
      Element methodElement, Type exception, DocletTag[] throwsDocletTags) {

    String name = exception.getJavaClass().getName();
    String value = null;

    for (DocletTag throwsDocletTag : throwsDocletTags) {
      String curValue = throwsDocletTag.getValue();

      if (!curValue.startsWith(name)) {
        continue;
      } else {
        curValue = value;

        break;
      }
    }

    Element throwsElement = methodElement.addElement("throws");

    DocUtil.add(throwsElement, "name", name);
    DocUtil.add(throwsElement, "type", exception.getValue());

    if (value != null) {
      value = value.substring(name.length());
    }

    Element commentElement = throwsElement.addElement("comment");

    commentElement.addCDATA(_getCDATA(value));
  }

  private void _addThrowsElements(Element methodElement, JavaMethod javaMethod) {

    Type[] exceptions = javaMethod.getExceptions();

    DocletTag[] throwsDocletTags = javaMethod.getTagsByName("throws");

    for (Type exception : exceptions) {
      _addThrowsElement(methodElement, exception, throwsDocletTags);
    }
  }

  private String _getCDATA(AbstractJavaEntity abstractJavaEntity) {
    return _getCDATA(abstractJavaEntity.getComment());
  }

  private String _getCDATA(String cdata) {
    if (cdata == null) {
      return StringPool.BLANK;
    }

    cdata =
        StringUtil.replace(
            cdata, new String[] {"\n", "<p>", "</p>"}, new String[] {" ", " <p> ", " </p> "});

    while (cdata.contains("  ")) {
      cdata = StringUtil.replace(cdata, "  ", " ");
    }

    return cdata.trim();
  }

  private String _getFieldKey(Element fieldElement) {
    return fieldElement.elementText("name");
  }

  private String _getFieldKey(JavaField javaField) {
    return javaField.getName();
  }

  private JavaClass _getJavaClass(String fileName) throws Exception {
    return _getJavaClass(fileName, null);
  }

  private JavaClass _getJavaClass(String fileName, Reader reader) throws Exception {

    int pos = fileName.indexOf("src/");

    if (pos == -1) {
      pos = fileName.indexOf("test/");
    }

    if (pos == -1) {
      throw new RuntimeException(fileName);
    }

    pos = fileName.indexOf("/", pos);

    String srcFile = fileName.substring(pos + 1, fileName.length());
    String className = StringUtil.replace(srcFile.substring(0, srcFile.length() - 5), "/", ".");

    JavaDocBuilder builder = new JavaDocBuilder();

    if (reader == null) {
      File file = new File(fileName);

      if (!file.exists()) {
        return null;
      }

      builder.addSource(file);
    } else {
      builder.addSource(reader);
    }

    return builder.getClassByName(className);
  }

  private String _getJavaClassComment(Element rootElement, JavaClass javaClass) {

    StringBuilder sb = new StringBuilder();

    sb.append("/**\n");
    sb.append(" * ");
    sb.append(_getCDATA(rootElement.elementText("comment")));
    sb.append("\n");
    sb.append(" *\n");

    String indent = StringPool.BLANK;

    _addDocletTags(rootElement, "author", indent, sb);
    _addDocletTags(rootElement, "deprecated", indent, sb);
    _addDocletTags(rootElement, "see", indent, sb);
    _addDocletTags(rootElement, "serial", indent, sb);
    _addDocletTags(rootElement, "since", indent, sb);
    _addDocletTags(rootElement, "version", indent, sb);

    sb.append(" */\n");

    return sb.toString();
  }

  private String _getJavadocXml(JavaClass javaClass) throws Exception {
    Element rootElement = _saxReaderUtil.createElement("javadoc");

    Document document = _saxReaderUtil.createDocument(rootElement);

    DocUtil.add(rootElement, "name", javaClass.getName());
    DocUtil.add(rootElement, "type", javaClass.getFullyQualifiedName());

    _addClassCommentElement(rootElement, javaClass);
    _addDocletElements(rootElement, javaClass, "author");
    _addDocletElements(rootElement, javaClass, "deprecated");
    _addDocletElements(rootElement, javaClass, "see");
    _addDocletElements(rootElement, javaClass, "serial");
    _addDocletElements(rootElement, javaClass, "since");
    _addDocletElements(rootElement, javaClass, "version");

    JavaMethod[] javaMethods = javaClass.getMethods();

    for (JavaMethod javaMethod : javaMethods) {
      _addMethodElement(rootElement, javaMethod);
    }

    JavaField[] javaFields = javaClass.getFields();

    for (JavaField javaField : javaFields) {
      _addFieldElement(rootElement, javaField);
    }

    return document.formattedString();
  }

  private String _getJavaFieldComment(
      String[] lines, Map<String, Element> fieldElementsMap, JavaField javaField) {

    String fieldKey = _getFieldKey(javaField);

    Element fieldElement = fieldElementsMap.get(fieldKey);

    if (fieldElement == null) {
      return null;
    }

    String line = lines[javaField.getLineNumber() - 1];

    String indent = StringPool.BLANK;

    for (char c : line.toCharArray()) {
      if (Character.isWhitespace(c)) {
        indent += c;
      } else {
        break;
      }
    }

    StringBuilder sb = new StringBuilder();

    sb.append(indent);
    sb.append("/**\n");
    sb.append(indent);
    sb.append(" * ");
    sb.append(fieldElement.elementText("comment"));
    sb.append("\n");
    sb.append(indent);
    sb.append(" *\n");

    _addDocletTags(fieldElement, "deprecated", indent, sb);
    _addDocletTags(fieldElement, "see", indent, sb);
    _addDocletTags(fieldElement, "since", indent, sb);
    _addDocletTags(fieldElement, "version", indent, sb);

    sb.append(indent);
    sb.append(" */\n");

    return sb.toString();
  }

  private String _getJavaMethodComment(
      String[] lines, Map<String, Element> methodElementsMap, JavaMethod javaMethod) {

    String methodKey = _getMethodKey(javaMethod);

    Element methodElement = methodElementsMap.get(methodKey);

    if (methodElement == null) {
      return null;
    }

    String line = lines[javaMethod.getLineNumber() - 1];

    String indent = StringPool.BLANK;

    for (char c : line.toCharArray()) {
      if (Character.isWhitespace(c)) {
        indent += c;
      } else {
        break;
      }
    }

    StringBuilder sb = new StringBuilder();

    sb.append(indent);
    sb.append("/**\n");
    sb.append(indent);
    sb.append(" * ");
    sb.append(methodElement.elementText("comment"));
    sb.append("\n");
    sb.append(indent);
    sb.append(" *\n");

    _addDocletTags(methodElement, "deprecated", indent, sb);
    _addDocletTags(methodElement, "param", indent, sb);
    _addDocletTags(methodElement, "return", indent, sb);
    _addDocletTags(methodElement, "see", indent, sb);
    _addDocletTags(methodElement, "since", indent, sb);
    _addDocletTags(methodElement, "throws", indent, sb);
    _addDocletTags(methodElement, "version", indent, sb);

    sb.append(indent);
    sb.append(" */\n");

    return sb.toString();
  }

  private String _getMethodKey(Element methodElement) {
    StringBuilder sb = new StringBuilder();

    sb.append(methodElement.elementText("name"));
    sb.append("(");

    List<Element> paramElements = methodElement.elements("param");

    for (Element paramElement : paramElements) {
      sb.append(paramElement.elementText("name"));
      sb.append("|");
      sb.append(paramElement.elementText("type"));
      sb.append(",");
    }

    sb.append(")");

    return sb.toString();
  }

  private String _getMethodKey(JavaMethod javaMethod) {
    StringBuilder sb = new StringBuilder();

    sb.append(javaMethod.getName());
    sb.append("(");

    JavaParameter[] javaParameters = javaMethod.getParameters();

    for (JavaParameter javaParameter : javaParameters) {
      sb.append(javaParameter.getName());
      sb.append("|");
      sb.append(javaParameter.getType().getValue());
      sb.append(",");
    }

    sb.append(")");

    return sb.toString();
  }

  private boolean _isGenerated(String content) {
    if (content.contains("<javadoc autogenerated=\"true\">")) {
      return true;
    } else {
      return false;
    }
  }

  private void _process(String command, String limit, Boolean ignoreAutogenerated)
      throws Exception {

    DirectoryScanner ds = new DirectoryScanner();

    ds.setBasedir(_basedir);
    ds.setExcludes(new String[] {"**\\classes\\**", "**\\portal-client\\**", "**\\portal-web\\**"});

    List<String> includes = new ArrayList<String>();

    if (Validator.isNotNull(limit) && !limit.startsWith("$")) {
      String[] limitArray = StringUtil.split(limit, '/');

      for (String curLimit : limitArray) {
        includes.add("**\\" + StringUtil.replace(curLimit, ".", "\\") + "\\**\\*.java");
        includes.add("**\\" + curLimit + ".java");
      }
    } else {
      includes.add("**\\*.java");
    }

    ds.setIncludes(includes.toArray(new String[includes.size()]));

    ds.scan();

    String[] fileNames = ds.getIncludedFiles();

    for (String fileName : fileNames) {
      fileName = StringUtil.replace(fileName, "\\", "/");

      /*if (!fileName.endsWith("Isolation.java")) {
      	continue;
      }*/

      if ((ignoreAutogenerated != null) && (ignoreAutogenerated.booleanValue())) {

        File file = new File(_basedir + fileName);

        if (file.exists()) {
          String oldContent = _fileUtil.read(_basedir + fileName + "doc");

          if (_isGenerated(oldContent)) {
            continue;
          }
        }
      }

      if (command.equals("cleanup")) {
        _processGet(fileName);
        _processSave(fileName);
        _processDelete(fileName);
      } else if (command.equals("commit")) {
        _processSave(fileName);
        _processDelete(fileName);
      } else if (command.equals("delete")) {
        _processDelete(fileName);
      } else if (command.equals("get")) {
        _processGet(fileName);
      } else if (command.equals("save")) {
        _processSave(fileName);
      }
    }
  }

  private void _processDelete(String fileName) throws Exception {
    _removeJavadocFromJava(fileName, true);
  }

  private void _processGet(String fileName) throws Exception {
    File javadocFile = new File(_basedir + fileName + "doc");

    if (!javadocFile.exists()) {
      _updateJavadocFromJava(fileName);
    }

    String javaWithoutJavadoc = _removeJavadocFromJava(fileName, false);

    _updateJavaFromJavadoc(fileName, javaWithoutJavadoc);
  }

  private void _processSave(String fileName) throws Exception {
    _updateJavadocFromJava(fileName);
  }

  private String _removeJavadocFromJava(String fileName, boolean log) throws Exception {

    File file = new File(_basedir + fileName);

    String oldContent = _fileUtil.read(file);

    String[] lines = StringUtil.splitLines(oldContent);

    JavaClass javaClass = _getJavaClass(fileName, new UnsyncStringReader(oldContent));

    Set<Integer> lineNumbers = new HashSet<Integer>();

    lineNumbers.add(javaClass.getLineNumber());

    JavaMethod[] javaMethods = javaClass.getMethods();

    for (JavaMethod javaMethod : javaMethods) {
      lineNumbers.add(javaMethod.getLineNumber());
    }

    JavaField[] javaFields = javaClass.getFields();

    for (JavaField javaField : javaFields) {
      lineNumbers.add(javaField.getLineNumber());
    }

    for (int lineNumber : lineNumbers) {
      int pos = lineNumber - 2;

      String line = lines[pos].trim();

      if (line.endsWith("*/")) {
        while (true) {
          lines[pos] = null;

          if (line.startsWith("/**")) {
            break;
          }

          line = lines[--pos].trim();
        }
      }
    }

    StringBuilder sb = new StringBuilder(oldContent.length());

    for (String line : lines) {
      if (line != null) {
        sb.append(line);
        sb.append("\n");
      }
    }

    String newContent = sb.toString().trim();

    if ((oldContent == null) || !oldContent.equals(newContent)) {
      _fileUtil.write(file, newContent);

      if (log) {
        System.out.println("Writing " + file);
      }
    }

    return newContent;
  }

  private void _updateJavadocFromJava(String fileName) throws Exception {
    File file = new File(_basedir + fileName + "doc");

    String oldContent = null;

    if (file.exists()) {
      oldContent = _fileUtil.read(file);

      if (_isGenerated(oldContent)) {
        return;
      }
    }

    JavaClass javaClass = _getJavaClass(fileName);

    String newContent = _getJavadocXml(javaClass);

    if ((oldContent == null) || !oldContent.equals(newContent)) {
      _fileUtil.write(file, newContent.getBytes());

      System.out.println("Writing " + file);
    }
  }

  private void _updateJavaFromJavadoc(String fileName, String oldContent) throws Exception {

    File javadocFile = new File(_basedir + fileName + "doc");

    if (!javadocFile.exists()) {
      return;
    }

    File file = new File(_basedir + fileName);

    if (oldContent == null) {
      oldContent = _fileUtil.read(file);
    }

    String[] lines = StringUtil.splitLines(oldContent);

    JavaClass javaClass = _getJavaClass(fileName, new UnsyncStringReader(oldContent));

    Document document = _saxReaderUtil.read(javadocFile);

    Element rootElement = document.getRootElement();

    Map<Integer, String> commentsMap = new TreeMap<Integer, String>();

    commentsMap.put(javaClass.getLineNumber(), _getJavaClassComment(rootElement, javaClass));

    Map<String, Element> methodElementsMap = new HashMap<String, Element>();

    List<Element> methodElements = rootElement.elements("method");

    for (Element methodElement : methodElements) {
      String methodKey = _getMethodKey(methodElement);

      methodElementsMap.put(methodKey, methodElement);
    }

    JavaMethod[] javaMethods = javaClass.getMethods();

    for (JavaMethod javaMethod : javaMethods) {
      if (commentsMap.containsKey(javaMethod.getLineNumber())) {
        continue;
      }

      commentsMap.put(
          javaMethod.getLineNumber(), _getJavaMethodComment(lines, methodElementsMap, javaMethod));
    }

    Map<String, Element> fieldElementsMap = new HashMap<String, Element>();

    List<Element> fieldElements = rootElement.elements("field");

    for (Element fieldElement : fieldElements) {
      String fieldKey = _getFieldKey(fieldElement);

      fieldElementsMap.put(fieldKey, fieldElement);
    }

    JavaField[] javaFields = javaClass.getFields();

    for (JavaField javaField : javaFields) {
      if (commentsMap.containsKey(javaField.getLineNumber())) {
        continue;
      }

      commentsMap.put(
          javaField.getLineNumber(), _getJavaFieldComment(lines, fieldElementsMap, javaField));
    }

    StringBuilder sb = new StringBuilder(oldContent.length());

    for (int lineNumber = 1; lineNumber <= lines.length; lineNumber++) {
      String line = lines[lineNumber - 1];

      String comments = commentsMap.get(lineNumber);

      if (comments != null) {
        sb.append(comments);
      }

      sb.append(line);
      sb.append("\n");
    }

    String newContent = sb.toString().trim();

    if ((oldContent == null) || !oldContent.equals(newContent)) {
      _fileUtil.write(file, newContent);

      System.out.println("Writing " + file);
    }
  }

  private static FileImpl _fileUtil = FileImpl.getInstance();
  private static SAXReaderImpl _saxReaderUtil = SAXReaderImpl.getInstance();

  private String _basedir = "./";
}
/**
 * @author Brian Wing Shun Chan
 * @author Alexander Chow
 */
public class ImageToolImpl implements ImageTool {

  public static ImageTool getInstance() {
    return _instance;
  }

  public RenderedImage convertCMYKtoRGB(byte[] bytes, String type, boolean fork) {

    ImageMagick imageMagick = getImageMagick();

    if (!imageMagick.isEnabled()) {
      return null;
    }

    File inputFile = _fileUtil.createTempFile(type);
    File outputFile = _fileUtil.createTempFile(type);

    try {
      _fileUtil.write(inputFile, bytes);

      IMOperation imOperation = new IMOperation();

      imOperation.addRawArgs("-format", "%[colorspace]");
      imOperation.addImage(inputFile.getPath());

      String[] output = imageMagick.identify(imOperation.getCmdArgs(), fork);

      if ((output.length == 1) && output[0].equalsIgnoreCase("CMYK")) {
        if (_log.isInfoEnabled()) {
          _log.info("The image is in the CMYK colorspace");
        }

        imOperation = new IMOperation();

        imOperation.addRawArgs("-colorspace", "RGB");
        imOperation.addImage(inputFile.getPath());
        imOperation.addImage(outputFile.getPath());

        imageMagick.convert(imOperation.getCmdArgs(), fork);

        bytes = _fileUtil.getBytes(outputFile);

        return read(bytes, type);
      }
    } catch (Exception e) {
      if (_log.isErrorEnabled()) {
        _log.error(e, e);
      }
    } finally {
      _fileUtil.delete(inputFile);
      _fileUtil.delete(outputFile);
    }

    return null;
  }

  public BufferedImage convertImageType(BufferedImage sourceImage, int type) {
    BufferedImage targetImage =
        new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), type);

    Graphics2D graphics = targetImage.createGraphics();

    graphics.drawRenderedImage(sourceImage, null);

    graphics.dispose();

    return targetImage;
  }

  public void encodeGIF(RenderedImage renderedImage, OutputStream os) throws IOException {

    if (JavaProps.isJDK6()) {
      ImageIO.write(renderedImage, TYPE_GIF, os);
    } else {
      BufferedImage bufferedImage = getBufferedImage(renderedImage);

      if (!(bufferedImage.getColorModel() instanceof IndexColorModel)) {
        bufferedImage = convertImageType(bufferedImage, BufferedImage.TYPE_BYTE_INDEXED);
      }

      Gif89Encoder encoder = new Gif89Encoder(bufferedImage);

      encoder.encode(os);
    }
  }

  public void encodeWBMP(RenderedImage renderedImage, OutputStream os) throws IOException {

    BufferedImage bufferedImage = getBufferedImage(renderedImage);

    SampleModel sampleModel = bufferedImage.getSampleModel();

    int type = sampleModel.getDataType();

    if ((bufferedImage.getType() != BufferedImage.TYPE_BYTE_BINARY)
        || (type < DataBuffer.TYPE_BYTE)
        || (type > DataBuffer.TYPE_INT)
        || (sampleModel.getNumBands() != 1)
        || (sampleModel.getSampleSize(0) != 1)) {

      BufferedImage binaryImage =
          new BufferedImage(
              bufferedImage.getWidth(), bufferedImage.getHeight(), BufferedImage.TYPE_BYTE_BINARY);

      Graphics graphics = binaryImage.getGraphics();

      graphics.drawImage(bufferedImage, 0, 0, null);

      renderedImage = binaryImage;
    }

    if (!ImageIO.write(renderedImage, "wbmp", os)) {

      // See http://www.jguru.com/faq/view.jsp?EID=127723

      os.write(0);
      os.write(0);
      os.write(toMultiByte(bufferedImage.getWidth()));
      os.write(toMultiByte(bufferedImage.getHeight()));

      DataBuffer dataBuffer = bufferedImage.getData().getDataBuffer();

      int size = dataBuffer.getSize();

      for (int i = 0; i < size; i++) {
        os.write((byte) dataBuffer.getElem(i));
      }
    }
  }

  public BufferedImage getBufferedImage(RenderedImage renderedImage) {
    if (renderedImage instanceof BufferedImage) {
      return (BufferedImage) renderedImage;
    } else {
      RenderedImageAdapter adapter = new RenderedImageAdapter(renderedImage);

      return adapter.getAsBufferedImage();
    }
  }

  public byte[] getBytes(RenderedImage renderedImage, String contentType) throws IOException {

    UnsyncByteArrayOutputStream baos = new UnsyncByteArrayOutputStream();

    write(renderedImage, contentType, baos);

    return baos.toByteArray();
  }

  public ImageBag read(byte[] bytes) {
    RenderedImage renderedImage = null;
    String type = TYPE_NOT_AVAILABLE;

    Enumeration<ImageCodec> enu = ImageCodec.getCodecs();

    while (enu.hasMoreElements()) {
      ImageCodec codec = enu.nextElement();

      if (codec.isFormatRecognized(bytes)) {
        type = codec.getFormatName();

        renderedImage = read(bytes, type);

        break;
      }
    }

    if (type.equals("jpeg")) {
      type = TYPE_JPEG;
    }

    return new ImageBag(renderedImage, type);
  }

  public ImageBag read(File file) throws IOException {
    return read(_fileUtil.getBytes(file));
  }

  public RenderedImage scale(RenderedImage renderedImage, int width) {
    if (width <= 0) {
      return renderedImage;
    }

    int imageHeight = renderedImage.getHeight();
    int imageWidth = renderedImage.getWidth();

    double factor = (double) width / imageWidth;

    int scaledHeight = (int) (factor * imageHeight);
    int scaledWidth = width;

    BufferedImage bufferedImage = getBufferedImage(renderedImage);

    int type = bufferedImage.getType();

    if (type == 0) {
      type = BufferedImage.TYPE_INT_ARGB;
    }

    BufferedImage scaledBufferedImage = new BufferedImage(scaledWidth, scaledHeight, type);

    Graphics graphics = scaledBufferedImage.getGraphics();

    Image scaledImage =
        bufferedImage.getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH);

    graphics.drawImage(scaledImage, 0, 0, null);

    return scaledBufferedImage;
  }

  public RenderedImage scale(RenderedImage renderedImage, int maxHeight, int maxWidth) {

    int imageHeight = renderedImage.getHeight();
    int imageWidth = renderedImage.getWidth();

    if (maxHeight == 0) {
      maxHeight = imageHeight;
    }

    if (maxWidth == 0) {
      maxWidth = imageWidth;
    }

    if ((imageHeight <= maxHeight) && (imageWidth <= maxWidth)) {
      return renderedImage;
    }

    double factor = Math.min((double) maxHeight / imageHeight, (double) maxWidth / imageWidth);

    int scaledHeight = Math.max(1, (int) (factor * imageHeight));
    int scaledWidth = Math.max(1, (int) (factor * imageWidth));

    BufferedImage bufferedImage = getBufferedImage(renderedImage);

    int type = bufferedImage.getType();

    if (type == 0) {
      type = BufferedImage.TYPE_INT_ARGB;
    }

    BufferedImage scaledBufferedImage = null;

    if ((type == BufferedImage.TYPE_BYTE_BINARY) || (type == BufferedImage.TYPE_BYTE_INDEXED)) {

      IndexColorModel indexColorModel = (IndexColorModel) bufferedImage.getColorModel();

      BufferedImage tempBufferedImage = new BufferedImage(1, 1, type, indexColorModel);

      int bits = indexColorModel.getPixelSize();
      int size = indexColorModel.getMapSize();

      byte[] reds = new byte[size];

      indexColorModel.getReds(reds);

      byte[] greens = new byte[size];

      indexColorModel.getGreens(greens);

      byte[] blues = new byte[size];

      indexColorModel.getBlues(blues);

      WritableRaster writableRaster = tempBufferedImage.getRaster();

      int pixel = writableRaster.getSample(0, 0, 0);

      IndexColorModel scaledIndexColorModel =
          new IndexColorModel(bits, size, reds, greens, blues, pixel);

      scaledBufferedImage =
          new BufferedImage(scaledWidth, scaledHeight, type, scaledIndexColorModel);
    } else {
      scaledBufferedImage = new BufferedImage(scaledWidth, scaledHeight, type);
    }

    Graphics graphics = scaledBufferedImage.getGraphics();

    Image scaledImage =
        bufferedImage.getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH);

    graphics.drawImage(scaledImage, 0, 0, null);

    return scaledBufferedImage;
  }

  public void write(RenderedImage renderedImage, String contentType, OutputStream os)
      throws IOException {

    if (contentType.contains(TYPE_BMP)) {
      ImageEncoder imageEncoder = ImageCodec.createImageEncoder(TYPE_BMP, os, null);

      imageEncoder.encode(renderedImage);
    } else if (contentType.contains(TYPE_GIF)) {
      encodeGIF(renderedImage, os);
    } else if (contentType.contains(TYPE_JPEG) || contentType.contains("jpeg")) {

      ImageIO.write(renderedImage, "jpeg", os);
    } else if (contentType.contains(TYPE_PNG)) {
      ImageIO.write(renderedImage, TYPE_PNG, os);
    } else if (contentType.contains(TYPE_TIFF) || contentType.contains("tif")) {

      ImageEncoder imageEncoder = ImageCodec.createImageEncoder(TYPE_TIFF, os, null);

      imageEncoder.encode(renderedImage);
    }
  }

  protected ImageMagick getImageMagick() {
    if (_imageMagick == null) {
      _imageMagick = ImageMagickImpl.getInstance();

      _imageMagick.reset();
    }

    return _imageMagick;
  }

  protected RenderedImage read(byte[] bytes, String type) {
    RenderedImage renderedImage = null;

    try {
      if (type.equals(TYPE_JPEG)) {
        type = "jpeg";
      }

      ImageDecoder decoder =
          ImageCodec.createImageDecoder(type, new UnsyncByteArrayInputStream(bytes), null);

      renderedImage = decoder.decodeAsRenderedImage();
    } catch (IOException ioe) {
      if (_log.isDebugEnabled()) {
        _log.debug(type + ": " + ioe.getMessage());
      }
    }

    return renderedImage;
  }

  protected byte[] toMultiByte(int intValue) {
    int numBits = 32;
    int mask = 0x80000000;

    while ((mask != 0) && ((intValue & mask) == 0)) {
      numBits--;
      mask >>>= 1;
    }

    int numBitsLeft = numBits;
    byte[] multiBytes = new byte[(numBitsLeft + 6) / 7];

    int maxIndex = multiBytes.length - 1;

    for (int b = 0; b <= maxIndex; b++) {
      multiBytes[b] = (byte) ((intValue >>> ((maxIndex - b) * 7)) & 0x7f);

      if (b != maxIndex) {
        multiBytes[b] |= (byte) 0x80;
      }
    }

    return multiBytes;
  }

  private static Log _log = LogFactoryUtil.getLog(ImageToolImpl.class);

  private static ImageTool _instance = new ImageToolImpl();

  private static FileImpl _fileUtil = FileImpl.getInstance();
  private static ImageMagick _imageMagick;
}
/**
 * @author Alexander Chow
 * @author Brian Wing Shun Chan
 */
public class PluginsEnvironmentBuilder {

  public static void main(String[] args) throws Exception {
    try {
      File dir = new File(System.getProperty("plugins.env.dir"));

      new PluginsEnvironmentBuilder(dir);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public PluginsEnvironmentBuilder(File dir) throws Exception {
    DirectoryScanner directoryScanner = new DirectoryScanner();

    directoryScanner.setBasedir(dir);
    directoryScanner.setIncludes(new String[] {"**\\liferay-plugin-package.properties"});

    directoryScanner.scan();

    String dirName = dir.getCanonicalPath();

    for (String fileName : directoryScanner.getIncludedFiles()) {
      setupWarProject(dirName, fileName);
    }

    directoryScanner = new DirectoryScanner();

    directoryScanner.setBasedir(dir);
    directoryScanner.setIncludes(new String[] {"**\\build.xml"});

    directoryScanner.scan();

    for (String fileName : directoryScanner.getIncludedFiles()) {
      String content = _fileUtil.read(dirName + "/" + fileName);

      boolean osgiProject = false;

      if (content.contains("<import file=\"../../build-common-osgi-plugin.xml\" />")
          || content.contains("../tools/sdk/build-common-osgi-plugin.xml\" />")) {

        osgiProject = true;
      }

      boolean sharedProject = false;

      if (content.contains("<import file=\"../build-common-shared.xml\" />")
          || content.contains("../tools/sdk/build-common-shared.xml\" />")) {

        sharedProject = true;
      }

      List<String> dependencyJars = Collections.emptyList();

      if (osgiProject) {
        int x = content.indexOf("osgi.ide.dependencies");

        if (x != -1) {
          x = content.indexOf("value=\"", x);
          x = content.indexOf("\"", x);

          int y = content.indexOf("\"", x + 1);

          dependencyJars = Arrays.asList(StringUtil.split(content.substring(x + 1, y)));
        }
      }

      if (osgiProject || sharedProject) {
        setupJarProject(dirName, fileName, dependencyJars, sharedProject);
      }
    }
  }

  protected void addClasspathEntry(StringBundler sb, String jar) {
    addClasspathEntry(sb, jar, null);
  }

  protected void addClasspathEntry(StringBundler sb, String jar, Map<String, String> attributes) {

    sb.append("\t<classpathentry kind=\"lib\" path=\"");
    sb.append(jar);

    if ((attributes == null) || attributes.isEmpty()) {
      sb.append("\" />\n");

      return;
    }

    sb.append("\">\n\t\t<attributes>\n");

    for (Map.Entry<String, String> entry : attributes.entrySet()) {
      sb.append("\t\t\t<attribute name=\"");
      sb.append(entry.getKey());
      sb.append("\" value=\"");
      sb.append(entry.getValue());
      sb.append("\" />\n");
    }

    sb.append("\t\t</attributes>\n\t</classpathentry>\n");
  }

  protected void addIvyCacheJar(StringBundler sb, String ivyDirName, String dependencyName) {

    String dirName = ivyDirName + "/cache/" + dependencyName + "/bundles";

    if (!_fileUtil.exists(dirName)) {
      dirName = ivyDirName + "/cache/" + dependencyName + "/jars";

      if (!_fileUtil.exists(dirName)) {
        throw new RuntimeException("Unable to find jars in " + dirName);
      }
    }

    File dir = new File(dirName);

    File[] files = dir.listFiles();

    for (File file : files) {
      if (!file.isFile()) {
        continue;
      }

      addClasspathEntry(sb, dirName + "/" + file.getName());

      return;
    }

    throw new RuntimeException("Unable to find jars in " + dirName);
  }

  protected List<String> getCommonJars() {
    List<String> jars = new ArrayList<String>();

    jars.add("commons-logging.jar");
    jars.add("log4j.jar");
    jars.add("util-bridges.jar");
    jars.add("util-java.jar");
    jars.add("util-taglib.jar");

    return jars;
  }

  protected List<String> getImportSharedJars(File projectDir) throws Exception {

    File buildXmlFile = new File(projectDir, "build.xml");

    String content = _fileUtil.read(buildXmlFile);

    int x = content.indexOf("import.shared");

    if (x == -1) {
      return new ArrayList<String>();
    }

    x = content.indexOf("value=\"", x);
    x = content.indexOf("\"", x);

    int y = content.indexOf("\" />", x);

    if ((x == -1) || (y == -1)) {
      return new ArrayList<String>();
    }

    String[] importShared = StringUtil.split(content.substring(x + 1, y));

    if (importShared.length == 0) {
      return new ArrayList<String>();
    }

    List<String> jars = new ArrayList<String>();

    for (String currentImportShared : importShared) {
      jars.add(currentImportShared + ".jar");

      File currentImportSharedLibDir =
          new File(projectDir, "../../shared/" + currentImportShared + "/lib");

      if (!currentImportSharedLibDir.exists()) {
        continue;
      }

      for (File file : currentImportSharedLibDir.listFiles()) {
        jars.add(file.getName());
      }
    }

    return jars;
  }

  protected List<String> getPortalDependencyJars(Properties properties) {
    String[] dependencyJars =
        StringUtil.split(
            properties.getProperty(
                "portal-dependency-jars", properties.getProperty("portal.dependency.jars")));

    return ListUtil.toList(dependencyJars);
  }

  protected List<String> getRequiredDeploymentContextsJars(File libDir, Properties properties)
      throws Exception {

    List<String> jars = new ArrayList<String>();

    String[] requiredDeploymentContexts =
        StringUtil.split(properties.getProperty("required-deployment-contexts"));

    for (String requiredDeploymentContext : requiredDeploymentContexts) {
      if (_fileUtil.exists(
          libDir.getCanonicalPath() + "/" + requiredDeploymentContext + "-service.jar")) {

        jars.add(requiredDeploymentContext + "-service.jar");
      }
    }

    return jars;
  }

  protected void setupJarProject(
      String dirName, String fileName, List<String> dependencyJars, boolean sharedProject)
      throws Exception {

    File buildFile = new File(dirName + "/" + fileName);

    File projectDir = new File(buildFile.getParent());

    File libDir = new File(projectDir, "lib");

    writeEclipseFiles(libDir, projectDir, dependencyJars);

    List<String> importSharedJars = getImportSharedJars(projectDir);

    if (sharedProject) {
      if (!importSharedJars.contains("portal-compat-shared.jar")) {
        importSharedJars.add("portal-compat-shared.jar");
      }
    }

    File gitignoreFile = new File(projectDir.getCanonicalPath() + "/.gitignore");

    String[] gitIgnores = importSharedJars.toArray(new String[importSharedJars.size()]);

    for (int i = 0; i < gitIgnores.length; i++) {
      String gitIgnore = gitIgnores[i];

      gitIgnore = "/lib/" + gitIgnore;

      gitIgnores[i] = gitIgnore;
    }

    if (gitIgnores.length > 0) {
      System.out.println("Updating " + gitignoreFile);

      _fileUtil.write(gitignoreFile, StringUtil.merge(gitIgnores, "\n"));
    }
  }

  protected void setupWarProject(String dirName, String fileName) throws Exception {

    File propertiesFile = new File(dirName + "/" + fileName);

    Properties properties = new Properties();

    properties.load(new FileInputStream(propertiesFile));

    Set<String> jars = new TreeSet<String>();

    jars.addAll(getCommonJars());

    List<String> dependencyJars = getPortalDependencyJars(properties);

    jars.addAll(dependencyJars);

    File projectDir = new File(propertiesFile.getParent() + "/../..");

    jars.addAll(getImportSharedJars(projectDir));

    File libDir = new File(propertiesFile.getParent() + "/lib");

    jars.addAll(getRequiredDeploymentContextsJars(libDir, properties));

    writeEclipseFiles(libDir, projectDir, dependencyJars);

    String libDirPath =
        StringUtil.replace(libDir.getPath(), StringPool.BACK_SLASH, StringPool.SLASH);

    List<String> ignores = ListUtil.fromFile(libDir.getCanonicalPath() + "/../.gitignore");

    if (libDirPath.contains("/ext/") || ignores.contains("/lib")) {
      return;
    }

    File gitignoreFile = new File(libDir.getCanonicalPath() + "/.gitignore");

    System.out.println("Updating " + gitignoreFile);

    String[] gitIgnores = jars.toArray(new String[jars.size()]);

    for (int i = 0; i < gitIgnores.length; i++) {
      String gitIgnore = gitIgnores[i];

      if (Validator.isNotNull(gitIgnore) && !gitIgnore.startsWith("/")) {
        gitIgnores[i] = "/" + gitIgnore;
      }
    }

    _fileUtil.write(gitignoreFile, StringUtil.merge(gitIgnores, "\n"));
  }

  protected void writeClasspathFile(
      File libDir,
      List<String> dependencyJars,
      String projectDirName,
      String projectName,
      boolean javaProject)
      throws Exception {

    File classpathFile = new File(projectDirName + "/.classpath");

    if (!javaProject) {
      classpathFile.delete();

      return;
    }

    Set<String> globalJars = new LinkedHashSet<String>();
    List<String> portalJars = new ArrayList<String>();

    Set<String> extGlobalJars = new LinkedHashSet<String>();
    Set<String> extPortalJars = new LinkedHashSet<String>();

    String libDirPath =
        StringUtil.replace(libDir.getPath(), StringPool.BACK_SLASH, StringPool.SLASH);

    if (libDirPath.contains("/ext/")) {
      FilenameFilter filenameFilter = new GlobFilenameFilter("*.jar");

      for (String dirName : new String[] {"global", "portal"}) {
        File file = new File(libDirPath + "/../ext-lib/" + dirName);

        List<String> jars = ListUtil.toList(file.list(filenameFilter));

        if (dirName.equals("global")) {
          extGlobalJars.addAll(ListUtil.sort(jars));

          File dir = new File(PropsValues.LIFERAY_LIB_GLOBAL_DIR);

          String[] fileNames = dir.list(filenameFilter);

          globalJars.addAll(ListUtil.sort(ListUtil.toList(fileNames)));
          globalJars.removeAll(extGlobalJars);
        } else if (dirName.equals("portal")) {
          extPortalJars.addAll(ListUtil.sort(jars));

          File dir = new File(PropsValues.LIFERAY_LIB_PORTAL_DIR);

          String[] fileNames = dir.list(filenameFilter);

          portalJars.addAll(ListUtil.sort(ListUtil.toList(fileNames)));
          portalJars.removeAll(extPortalJars);
        }
      }
    } else {
      globalJars.add("portlet.jar");

      portalJars.addAll(dependencyJars);
      portalJars.add("commons-logging.jar");
      portalJars.add("log4j.jar");

      portalJars = ListUtil.unique(portalJars);

      Collections.sort(portalJars);
    }

    String[] customJarsArray = libDir.list(new GlobFilenameFilter("*.jar"));

    List<String> customJars = null;

    if (customJarsArray != null) {
      customJars = ListUtil.toList(customJarsArray);

      for (String jar : portalJars) {
        customJars.remove(jar);
      }

      customJars.remove(projectName + "-service.jar");
      customJars.remove("util-bridges.jar");
      customJars.remove("util-java.jar");
      customJars.remove("util-taglib.jar");

      Collections.sort(customJars);
    } else {
      customJars = new ArrayList<String>();
    }

    StringBundler sb = new StringBundler();

    sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n");
    sb.append("<classpath>\n");

    for (String sourceDirName : _SOURCE_DIR_NAMES) {
      if (_fileUtil.exists(projectDirName + "/" + sourceDirName)) {
        sb.append("\t<classpathentry excluding=\"**/.svn/**|.svn/\" ");
        sb.append("kind=\"src\" path=\"");
        sb.append(sourceDirName);
        sb.append("\" />\n");
      }
    }

    sb.append("\t<classpathentry kind=\"src\" path=\"/portal\" />\n");
    sb.append("\t<classpathentry kind=\"con\" ");
    sb.append("path=\"org.eclipse.jdt.launching.JRE_CONTAINER\" />\n");

    boolean addJunitJars = false;

    for (String testType : _TEST_TYPES) {
      String testFolder = "test/" + testType;

      if (_fileUtil.exists(projectDirName + "/" + testFolder)) {
        addJunitJars = true;

        sb.append("\t<classpathentry excluding=\"**/.svn/**|.svn/\" ");
        sb.append("kind=\"src\" path=\"");
        sb.append(testFolder);
        sb.append("\" />\n");
      }
    }

    if (addJunitJars) {
      addClasspathEntry(sb, "/portal/lib/development/junit.jar");
      addClasspathEntry(sb, "/portal/lib/development/mockito.jar");
      addClasspathEntry(sb, "/portal/lib/development/powermock-mockito.jar");
      addClasspathEntry(sb, "/portal/lib/development/spring-test.jar");

      portalJars.add("commons-io.jar");
      portalJars.add("commons-lang.jar");
    }

    addClasspathEntry(sb, "/portal/lib/development/activation.jar");
    addClasspathEntry(sb, "/portal/lib/development/annotations.jar");
    addClasspathEntry(sb, "/portal/lib/development/jsp-api.jar");
    addClasspathEntry(sb, "/portal/lib/development/mail.jar");
    addClasspathEntry(sb, "/portal/lib/development/servlet-api.jar");

    Map<String, String> attributes = new HashMap<String, String>();

    if (libDirPath.contains("/ext/")) {
      attributes.put("optional", "true");
    }

    for (String jar : globalJars) {
      addClasspathEntry(sb, "/portal/lib/global/" + jar, attributes);
    }

    portalJars = ListUtil.unique(portalJars);

    Collections.sort(portalJars);

    for (String jar : portalJars) {
      if (!jar.equals("util-slf4j.jar")) {
        addClasspathEntry(sb, "/portal/lib/portal/" + jar, attributes);
      }
    }

    addClasspathEntry(sb, "/portal/portal-service/portal-service.jar");
    addClasspathEntry(sb, "/portal/util-bridges/util-bridges.jar");
    addClasspathEntry(sb, "/portal/util-java/util-java.jar");

    if (portalJars.contains("util-slf4j.jar")) {
      addClasspathEntry(sb, "/portal/util-slf4j/util-slf4j.jar");
    }

    addClasspathEntry(sb, "/portal/util-taglib/util-taglib.jar");

    for (String jar : extGlobalJars) {
      addClasspathEntry(sb, "docroot/WEB-INF/ext-lib/global/" + jar);
    }

    for (String jar : extPortalJars) {
      addClasspathEntry(sb, "docroot/WEB-INF/ext-lib/portal/" + jar);
    }

    for (String jar : customJars) {
      if (libDirPath.contains("/tmp/WEB-INF/lib")) {
        addClasspathEntry(sb, "tmp/WEB-INF/lib/" + jar);
      } else if (libDirPath.contains("/docroot/WEB-INF/lib")) {
        addClasspathEntry(sb, "docroot/WEB-INF/lib/" + jar);
      } else {
        addClasspathEntry(sb, "lib/" + jar);
      }
    }

    File ivyXmlFile = new File(projectDirName, "ivy.xml");

    if (ivyXmlFile.exists()) {
      String content = _fileUtil.read(ivyXmlFile);

      if (content.contains("arquillian-junit-container")) {
        String ivyDirName = ".ivy";

        for (int i = 0; i < 10; i++) {
          if (_fileUtil.exists(ivyDirName)) {
            break;
          }

          ivyDirName = "../" + ivyDirName;
        }

        addIvyCacheJar(sb, ivyDirName, "org.apache.felix/org.apache.felix.framework");
        addIvyCacheJar(sb, ivyDirName, "org.jboss.arquillian.junit/arquillian-junit-core");
        addIvyCacheJar(sb, ivyDirName, "org.jboss.arquillian.test/arquillian-test-api");
      }
    }

    sb.append("\t<classpathentry kind=\"output\" path=\"bin\" />\n");
    sb.append("</classpath>");

    System.out.println("Updating " + classpathFile);

    String content = StringUtil.replace(sb.toString(), "\"/portal", "\"/portal-" + _BRANCH);

    _fileUtil.write(classpathFile, content);
  }

  protected void writeEclipseFiles(File libDir, File projectDir, List<String> dependencyJars)
      throws Exception {

    String projectDirName = projectDir.getCanonicalPath();

    String projectName = StringUtil.extractLast(projectDirName, File.separatorChar);

    boolean javaProject = false;

    for (String sourceDirName : _SOURCE_DIR_NAMES) {
      if (_fileUtil.exists(projectDirName + "/" + sourceDirName)) {
        javaProject = true;

        break;
      }
    }

    if (!javaProject) {
      System.out.println(
          "Eclipse Java project will not be used because a source " + "folder does not exist");
    }

    writeProjectFile(projectDirName, projectName, javaProject);

    writeClasspathFile(libDir, dependencyJars, projectDirName, projectName, javaProject);

    for (String sourceDirName : _SOURCE_DIR_NAMES) {
      if (_fileUtil.exists(projectDirName + "/" + sourceDirName)) {
        List<String> gitIgnores = new ArrayList<String>();

        if (sourceDirName.endsWith("ext-impl/src")) {
          gitIgnores.add("/classes");
          gitIgnores.add("/ext-impl.jar");
        } else if (sourceDirName.endsWith("ext-service/src")) {
          gitIgnores.add("/classes");
          gitIgnores.add("/ext-service.jar");
        } else if (sourceDirName.endsWith("ext-util-bridges/src")) {
          gitIgnores.add("/classes");
          gitIgnores.add("/ext-util-bridges.jar");
        } else if (sourceDirName.endsWith("ext-util-java/src")) {
          gitIgnores.add("/classes");
          gitIgnores.add("/ext-util-java.jar");
        } else if (sourceDirName.endsWith("ext-util-taglib/src")) {
          gitIgnores.add("/classes");
          gitIgnores.add("/ext-util-taglib.jar");
        } else {
          continue;
        }

        String dirName = projectDirName + "/" + sourceDirName + "/../";

        if (gitIgnores.isEmpty()) {
          _fileUtil.delete(dirName + ".gitignore");
        } else {
          String gitIgnoresString = StringUtil.merge(gitIgnores, "\n");

          _fileUtil.write(dirName + ".gitignore", gitIgnoresString);
        }
      }
    }

    if (_fileUtil.exists(projectDirName + "/test")) {
      _fileUtil.write(projectDirName + "/.gitignore", "/test-classes\n/test-results");
    } else {
      _fileUtil.delete(projectDirName + "/.gitignore");
    }
  }

  protected void writeProjectFile(String projectDirName, String projectName, boolean javaProject)
      throws Exception {

    StringBundler sb = new StringBundler(17);

    sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n");
    sb.append("<projectDescription>\n");
    sb.append("\t<name>");
    sb.append(projectName);
    sb.append("-");
    sb.append(_BRANCH);
    sb.append("</name>\n");
    sb.append("\t<comment></comment>\n");
    sb.append("\t<projects></projects>\n");
    sb.append("\t<buildSpec>\n");

    if (javaProject) {
      sb.append("\t\t<buildCommand>\n");
      sb.append("\t\t\t<name>org.eclipse.jdt.core.javabuilder</name>\n");
      sb.append("\t\t\t<arguments></arguments>\n");
      sb.append("\t\t</buildCommand>\n");
    }

    sb.append("\t</buildSpec>\n");
    sb.append("\t<natures>\n");

    if (javaProject) {
      sb.append("\t\t<nature>org.eclipse.jdt.core.javanature</nature>\n");
    }

    sb.append("\t</natures>\n");
    sb.append("</projectDescription>");

    File projectFile = new File(projectDirName + "/.project");

    System.out.println("Updating " + projectFile);

    _fileUtil.write(projectFile, sb.toString());
  }

  private static final String _BRANCH = "master";

  private static final String[] _SOURCE_DIR_NAMES =
      new String[] {
        "docroot/WEB-INF/ext-impl/src",
        "docroot/WEB-INF/ext-service/src",
        "docroot/WEB-INF/ext-util-bridges/src",
        "docroot/WEB-INF/ext-util-java/src",
        "docroot/WEB-INF/ext-util-taglib/src",
        "docroot/WEB-INF/service",
        "docroot/WEB-INF/src",
        "src"
      };

  private static final String[] _TEST_TYPES = {"integration", "unit"};

  private static FileImpl _fileUtil = FileImpl.getInstance();
}