Package org.hybridlabs.source.beautifier

Source Code of org.hybridlabs.source.beautifier.JavaImportBeautifierImpl

package org.hybridlabs.source.beautifier;

/**
* Copyright 2008 hybrid labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.hybridlabs.file.FileHelper;
import org.hybridlabs.source.beautifier.replacement.DefaultTypeReplacementStrategy;
import org.hybridlabs.source.beautifier.replacement.TypeReplacementStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.hunsicker.jalopy.language.antlr.JavaNode;

/**
* Simple oAW beautifier implementation based on Jalopy and the antlr JavaNode. The
* implementation substitutes fully qualified class names (as long as there is
* no conflict) with the short name and the according import statement.
*
* @author Karsten Klein, hybrid labs
* @author Jan Labrie
*/
public class JavaImportBeautifierImpl extends JavaBeautifier implements BeautifierWithConvention {

    private static final Logger LOG = LoggerFactory.getLogger(JavaImportBeautifierImpl.class);

    private static final String LINESEP = System.getProperty("line.separator");

    private static final CharacterSequence DEFAULT_PACKAGE = new CharacterSequence(new StringBuffer());

    private static Set<Integer> startSequenceTypeSet = new HashSet<Integer>();
    private static Set<Integer> terminateSequenceTypeSet = new HashSet<Integer>();

    static {
        startSequenceTypeSet.add(new Integer(PACKAGE));
        startSequenceTypeSet.add(new Integer(IMPORT_STATEMENT));
        startSequenceTypeSet.add(new Integer(GENERIC_UPPER_BOUNDS));
        startSequenceTypeSet.add(new Integer(EXTENDS_CLAUSE));
        startSequenceTypeSet.add(new Integer(IMPLEMENTS_CLAUSE));
        startSequenceTypeSet.add(new Integer(TYPE));
        startSequenceTypeSet.add(new Integer(EXPRESSION));
        startSequenceTypeSet.add(new Integer(NEW));
        startSequenceTypeSet.add(new Integer(PACKAGE_ANNOTATION));
        startSequenceTypeSet.add(new Integer(ANNOTATION));
        startSequenceTypeSet.add(new Integer(ANNOTATION_VALUE));
        startSequenceTypeSet.add(new Integer(CAST));
        startSequenceTypeSet.add(new Integer(CLASS));
        startSequenceTypeSet.add(new Integer(AT));
        startSequenceTypeSet.add(new Integer(SEMI));
        startSequenceTypeSet.add(new Integer(STATIC_IMPORT_STATEMENT));
        startSequenceTypeSet.add(new Integer(DOT));
        startSequenceTypeSet.add(new Integer(COMMA));
        startSequenceTypeSet.add(new Integer(THROWS));
    }

    static {
        // these terminate a sequence AND inherit the scope of the current (separator types)
        terminateSequenceTypeSet.add(new Integer(10));
        terminateSequenceTypeSet.add(new Integer(11));
        terminateSequenceTypeSet.add(new Integer(39));
        terminateSequenceTypeSet.add(new Integer(45));
        terminateSequenceTypeSet.add(new Integer(DOT));
        terminateSequenceTypeSet.add(new Integer(100));
        terminateSequenceTypeSet.add(new Integer(109));
        terminateSequenceTypeSet.add(new Integer(COMMA));
    }

    private boolean isOrganizeImports = true;
    private boolean isStrict = true;
    private boolean isFormat = true;

    private Map<Integer, TypeReplacementStrategy> strategyMap = new HashMap<Integer, TypeReplacementStrategy>();

    public JavaImportBeautifierImpl() {
        // populate strategy map
      strategyMap.put(new Integer(NEW), new DefaultTypeReplacementStrategy("[\\,\\<\\)\\[\\<\\(\\s]"));
      strategyMap.put(new Integer(GENERIC_UPPER_BOUNDS), new DefaultTypeReplacementStrategy( "[\\(\\,\\<\\[\\,\\>\\s]"));
      strategyMap.put(new Integer(EXTENDS_CLAUSE), new DefaultTypeReplacementStrategy("[\\(\\>\\<\\[\\,\\;\\{\\s]"));
      strategyMap.put(new Integer(IMPLEMENTS_CLAUSE), new DefaultTypeReplacementStrategy("[\\(\\>\\<\\[\\,\\;\\{\\s]"));
      strategyMap.put(new Integer(THROWS), new DefaultTypeReplacementStrategy("[\\(\\,\\>\\<\\)\\.\\s*]"));
      strategyMap.put(new Integer(TYPE), new DefaultTypeReplacementStrategy("[\\(\\,\\>\\)\\[\\<\\;\\s\\.]"));
      strategyMap.put(new Integer(EXPRESSION), new DefaultTypeReplacementStrategy("[\\(\\,\\>\\<\\)\\.\\s*]"));
      strategyMap.put(new Integer(ANNOTATION), new DefaultTypeReplacementStrategy("[\\(\\>\\<\\[\\,\\;\\{\\s]"));
      strategyMap.put(new Integer(AT), new DefaultTypeReplacementStrategy("[\\(\\>\\<\\[\\,\\;\\{\\s]"));
      strategyMap.put(new Integer(CAST), new DefaultTypeReplacementStrategy("[\\(\\)\\>\\<\\[\\,\\;\\{\\s\\.]"));
      strategyMap.put(new Integer(ANNOTATION_VALUE), new DefaultTypeReplacementStrategy("[\\(\\>\\<\\[\\,\\;\\{\\s\\.]"));
    }

    public boolean isFormat() {
        return isFormat;
    }

    public void setFormat(boolean isFormat) {
        this.isFormat = isFormat;
    }

    public boolean isOrganizeImports() {
        return isOrganizeImports;
    }

    public void setOrganizeImports(boolean isOrganizeImports) {
        this.isOrganizeImports = isOrganizeImports;
    }

    public boolean isStrict() {
        return isStrict;
    }

    public void setStrict(boolean isOrganizeOnWildcards) {
        this.isStrict = isOrganizeOnWildcards;
    }

    public void beautify(CharacterSequence sb) {
        File file = null;
        try {
            file = File.createTempFile("hybridlabs-beautifier", ".java");
            if (isOrganizeImports()) {
                organizeImports(sb, file);
            }
            if (isFormat()) {
                format(sb, file);
            }
        } catch (Exception e) {
          handleError(e, sb);
        } catch (Error e) {
          handleError(e, sb);
        } finally {
            if (file != null) {
                file.delete();
            }
        }
    }

  /**
   * Beautifies the source file and writes the result to the target file.
   * Source and target may be identical.
   *
   * @param sourceFile
   * @param targetFile
   * @throws FileNotFoundException
   * @throws IOException
   */
  public void beautify(File sourceFile, File targetFile)
      throws FileNotFoundException, IOException {
    StringBuffer buffer = FileHelper.loadStringBuffer(sourceFile);

    CharacterSequence sequence = new CharacterSequence(buffer);
    beautify(sequence);

    String targetPath = sourceFile.getAbsolutePath();
    if (targetFile != null) {
      targetPath = targetFile.getAbsolutePath();
    }

    FileWriter writer = new FileWriter(new File(targetPath));
    writer.write(sequence.toString());
    writer.close();
  }

  private void handleError (Throwable e, CharacterSequence sb) {
        LOG.error(e.getClass().getSimpleName()+" occured during beautification. Content:" + LINESEP + sb.subSequence(0, Math.min(160, sb.length())) + "...");
        if (LOG.isDebugEnabled()) {
            LOG.debug("Error during beautification.", e);
        } else if (e.getMessage()!=null) {
          LOG.error ("Error message: "+e.getMessage());
        }
    }

    private void organizeImports(CharacterSequence sequence, File file) {

        // create formatter context for this file
        BeautifierContext formatterContext = new BeautifierContext();

        // use formatter context to traverse the abstract syntax tree
        JavaNode node = createJavaNode(sequence, file);
        traverseAst(node, formatterContext, 0);

        Set<TypeContext> sequences = formatterContext.getSequences();

        // dump all detected sequences to system out in DEBUG mode
        if (LOG.isDebugEnabled()) {
            printSequences(sequences);
        }

        // extract package from ast
        CharacterSequence currentPackage = DEFAULT_PACKAGE;
        for (TypeContext typeContext : sequences) {
            if (typeContext.getType() == PACKAGE_ANNOTATION) {
                currentPackage = typeContext.getQualifiedName();
                break;
            }
        }

        // determine absolute position of package declaration
        int packageEndPos = findPositionInCharacterSequence(sequence,
                formatterContext.getPackageLine(), formatterContext.getPackageEnd());

        // determine absolute position of import declaration
        int importEndPos = findPositionInCharacterSequence(sequence,
                formatterContext.getImportEndLine(), formatterContext.getImportEndColumn());
        // split the file in header (including the package declaration) ...
        CharacterSequence result;
        if (DEFAULT_PACKAGE.equals(currentPackage)) {
            result = new CharacterSequence(new StringBuffer());
            packageEndPos = 0;
        } else {
            result = sequence.subCharacterSequence(0, packageEndPos);
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("Package: " + currentPackage + "[" + packageEndPos + "]");
            LOG.debug("Imports end at: " + importEndPos);
        }

        if (importEndPos == -1) {
            importEndPos = packageEndPos;
        }

        CharSequence imports = sequence.subSequence(packageEndPos, importEndPos);
        // do not organize anything if there is a wildcard import - this could lead to ambiguity
        if (isStrict && imports.toString().contains(".*")) {
          LOG.info("Did not organize import "+file.getAbsolutePath()+" - file contains wildcard imports.");
        } else {
          // and the body part (imports, comments, classes/interfaces)
          CharSequence body = sequence.subSequence(importEndPos, sequence.length());

          // extract the detected imports sequences
          Set<CharacterSequence> importTypes = extractImports(sequences);

          // set for monitoring the already replaced types
          Set<CharacterSequence> replacedSet = new TreeSet<CharacterSequence>();

          for (TypeContext typeContext : sequences) {
              if (typeContext.getType() == CLASS) {
                  if (currentPackage == null) {
                      importTypes.add(typeContext.getQualifiedName());
                  } else {
                      CharacterSequence sb = new CharacterSequence(new StringBuffer(100));
                      sb.append(currentPackage);
                      sb.append('.');
                      sb.append(typeContext.getQualifiedName());
                      importTypes.add(sb);
                  }
              }
          }

          body = replaceFullQualifiedClassNames(body, sequences, importTypes, replacedSet);

          if (imports.length() > 0) {
              result.append(imports);
          }

          // populate header with imports
          int numNewImports = 0;
          for (CharacterSequence type : replacedSet) {
              if (!isRedundant(type, importTypes)) {

                  int index = type.lastIndexOf('.');
                  CharSequence typePackage = index == -1 ? DEFAULT_PACKAGE : type.subSequence(0, index);

                  // make sure the import is not redundant, because it is part of the current package
                  // and also the java.lang package does not need to be imported
                  if (!currentPackage.equals(typePackage) && !typePackage.equals("java.lang")) {
                      result.append("import ");
                      result.append(type);
                      result.append(";");
                      result.append(LINESEP);
                      numNewImports += 1;
                  }
              }
          }

          // separate existing imports from added imports
          if (numNewImports > 0) {
              result.append(LINESEP);
          }

          // supplement the rest of the source
          result.append(body);

          sequence.set(result);
        }
    }

    private CharSequence replaceFullQualifiedClassNames(CharSequence body, Set<TypeContext> sequences, Set<CharacterSequence> importTypes, Set<CharacterSequence> replacedSet) {

        // body consists of import, comment, classes etc
        // the existing imports should not be touched by the replacement

        Set<CharacterSequence> conflictCache = new HashSet<CharacterSequence>();

        // At first replace all java.lang imports
        for (TypeContext typeContext : sequences) {
          if (typeContext.getQualifiedName().startsWith("java.lang")) {
            body = replaceFullQualifiedClassNames(typeContext, body, importTypes, replacedSet, conflictCache);
          }
        }

        // And then the 'ordinary' ones
        for (TypeContext typeContext : sequences) {
            body = replaceFullQualifiedClassNames(typeContext, body, importTypes, replacedSet, conflictCache);
        }

        return body;
    }

  private CharSequence replaceFullQualifiedClassNames(
      TypeContext typeContext,
      CharSequence body,
      Set<CharacterSequence> importTypes,
      Set<CharacterSequence> replacedSet,
      Set<CharacterSequence> conflictCache) {

    CharacterSequence type = typeContext.getQualifiedName();

    if (!replacedSet.contains(type)) {
        if (typeContext.isValidType()) {
            // ensure there are no conflicts
            if (!isConflict(type, importTypes, replacedSet, conflictCache)) {
                // access the replacement strategy
                TypeReplacementStrategy strategy = (TypeReplacementStrategy)
                    strategyMap.get(new Integer(typeContext.getType()));

                if (strategy != null) {
                    String replacement = strategy.composeReplace(type);
                    Pattern pattern = Pattern.compile(strategy
                            .composeMatch(type));
                    Matcher matcher = pattern.matcher(body);

                    // check whether the pattern was matched
                    if (matcher.find()) {
                        // match and replace the type by the short form (in the sub string)
                        body = matcher.replaceAll(replacement);

                        // mark type as replaced
                        replacedSet.add(type);
                    }
                }
            }
        }
    }
    return body;
  }

    private Set<CharacterSequence> extractImports(Set<TypeContext> sequences) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("These are the existing imports:");
        }
        Set<CharacterSequence> importTypes = new TreeSet<CharacterSequence>();

        for (TypeContext typeContext : sequences) {
            if (typeContext.getType() == IMPORT_STATEMENT) {
                CharacterSequence sb = typeContext.getQualifiedName();
                importTypes.add(sb);
                LOG.debug("{}", sb.toString());
            } else if (typeContext.getType() == STATIC_IMPORT_STATEMENT) {
                CharacterSequence sb = new CharacterSequence(new StringBuffer(200));
                sb.append("static ");
                sb.append(typeContext.getQualifiedName());
                importTypes.add(sb);
                LOG.debug("{}", sb);
            }
        }
        return importTypes;
    }

    private void printSequences(Set<TypeContext> sequences) {
        LOG.debug("These are the symbols that are candidate to be replaced by imports:");
        for (TypeContext typeContext : sequences) {
            LOG.debug("{}", typeContext);
        }
    }

    private boolean isConflict(CharacterSequence type, Set<CharacterSequence> importList, Set<CharacterSequence> replacedSet, Set<CharacterSequence> conflictCache) {
        if (isConflict(type, replacedSet, conflictCache)) {
            return true;
        }

        if (isConflict(type, importList, conflictCache)) {
            return true;
        }
        return false;
    }

    private boolean isConflict(CharacterSequence type, Set<CharacterSequence> testSet, Set<CharacterSequence> conflictCache) {
        if (testSet.contains(type)) {
            return false;
        }

        if (conflictCache.contains(type)) {
            return true;
        }

        for (CharacterSequence importType : testSet) {
            if (!importType.endsWith('*')) {
                if (!type.equals(importType)) {
                    CharSequence t = importType
                            .substring(importType.lastIndexOf('.') + 1);
                    if (type.endsWith("." + t) && !importType.startsWith("static ")) {
                        conflictCache.add(type);
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * Method to calculate whether a symbol is already covered by a set of import sequences
     * @param type The type which is checked
     * @param importList The list of import statements in which regard the checking is done
     * @return true when the symbol is covered by the import list
     */
    private final boolean isRedundant(CharacterSequence type, Set<CharacterSequence> importList) {
        for (CharacterSequence importType : importList) {
            if (importType.equals(type) ||
                    importType.endsWith('*') &&
                    notLastPart(type).equals(notLastPart(importType))) {
                return true;
            }
        }
        return false;
    }

    private String notLastPart(CharacterSequence charSeq) {
        String result= "";
        int i = charSeq.lastIndexOf('.');
        if (i >= 0) {
            result =  charSeq.subCharacterSequence(0, i).toString();
        }
        return result;
    }

    private int traverseAst(JavaNode ast, BeautifierContext context, int depth) {

        // print this node
        if (LOG.isDebugEnabled()) {
            preparePrintln(depth);
            LOG.debug("{}", ast);
        }

        if (context.isProcessed(ast)) {
            return -1;
        }

        context.preProcess(ast);

        // check whether this subtree is to be ignored completely
        Integer astType = new Integer(ast.getType());

        if (terminateSequenceTypeSet.contains(astType)) {
            context.terminateCurrentSequence(ast, context.getCurrentTypeContext().getType());
        } else if (startSequenceTypeSet.contains(astType)) {
            context.terminateCurrentSequence(ast, -1);
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("TRAVERSING: " + ast.getType());
        }

        // traverse all children
        JavaNode child = (JavaNode) ast.getFirstChild();
        if (child != null) {
            traverseAst(child, context, depth + 1);
        }

        context.postProcess(ast);


        // print if a type was detected
        if (ast.getType() == IDENT) {
            context.addToCurrentTypeContext(ast);
            if (LOG.isDebugEnabled()) {
                LOG.debug(preparePrintln(depth)+context.getCurrentTypeContext());
            }
        }

        // continue visiting the tree (traverse siblings)
        JavaNode next = (JavaNode) ast.getNextSibling();
        while (next != null) {
            traverseAst(next, context, depth);
            next = (JavaNode) next.getNextSibling();
        }

        return -1;
    }

    private String preparePrintln(int depth) {
      StringBuilder result = new StringBuilder();
        for (int i = 0; i < depth; i++) {
            result.append("    ");
        }
        return result.toString();
    }

}
TOP

Related Classes of org.hybridlabs.source.beautifier.JavaImportBeautifierImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.