Package edu.ohio_state.khatchad.refactoring

Source Code of edu.ohio_state.khatchad.refactoring.ConvertConstantsToEnumRefactoring

package edu.ohio_state.khatchad.refactoring;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTRequestor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NumberLiteral;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.search.FieldDeclarationMatch;
import org.eclipse.jdt.core.search.FieldReferenceMatch;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.LocalVariableDeclarationMatch;
import org.eclipse.jdt.core.search.LocalVariableReferenceMatch;
import org.eclipse.jdt.core.search.MethodDeclarationMatch;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.corext.dom.NodeFinder;
import org.eclipse.jdt.ui.wizards.NewEnumWizardPage;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.ChangeDescriptor;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.swt.widgets.Display;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;

import edu.ohio_state.khatchad.refactoring.core.EnumConstantComparator;
import edu.ohio_state.khatchad.refactoring.core.EnumerizationComputer;
import edu.ohio_state.khatchad.refactoring.core.InternalStateStatus;
import edu.ohio_state.khatchad.refactoring.core.Util;

public class ConvertConstantsToEnumRefactoring extends Refactoring {

  private static class SearchMatchPurpose {
    public static final SearchMatchPurpose ALTER_INFIX_EXPRESSION = new SearchMatchPurpose();
    public static final SearchMatchPurpose ALTER_NAMESPACE_PREFIX = new SearchMatchPurpose();
    public static final SearchMatchPurpose ALTER_TYPE_DECLARATION = new SearchMatchPurpose();

    private SearchMatchPurpose() {
    }
  }

  private void commenceSearch(SearchEngine engine,
      SearchPattern pattern, IJavaSearchScope scope,
      final SearchMatchPurpose purpose,
      IProgressMonitor monitor) throws CoreException {
    engine.search(pattern, new SearchParticipant[] { SearchEngine
        .getDefaultSearchParticipant() }, scope, new SearchRequestor() {

      public void acceptSearchMatch(SearchMatch match)
          throws CoreException {
        if (match.getAccuracy() == SearchMatch.A_ACCURATE
            && !match.isInsideDocComment())
          matchToPurposeMap.put(match, purpose);
      }
    }, new SubProgressMonitor(monitor, 1,
            SubProgressMonitor.SUPPRESS_SUBTASK_LABEL));
  }

  private static EnumConstantDeclaration createNewEnumConstantDeclarataion(
      AST ast, SimpleName constantName, Javadoc docComment,
      Collection annotationCollection) {
    final EnumConstantDeclaration enumConstDecl = ast
        .newEnumConstantDeclaration();
    enumConstDecl.setJavadoc(docComment);
    enumConstDecl.setName(constantName);
    enumConstDecl.modifiers().addAll(annotationCollection);
    return enumConstDecl;
  }

  private static EnumDeclaration createNewEnumDeclaration(AST ast,
      SimpleName name, Collection enumConstantDeclarationCollection,
      Object[] enumTypeModifierCollection) {

    final EnumDeclaration enumDecl = ast.newEnumDeclaration();
    enumDecl.setName(name);
    for (int i = 0; i < enumTypeModifierCollection.length; i++)
      enumDecl.modifiers().add(enumTypeModifierCollection[i]);
    enumDecl.enumConstants().addAll(enumConstantDeclarationCollection);
    return enumDecl;
  }

  private static Type getNewType(AST ast, String typeName) {
    final SimpleName name = ast.newSimpleName(typeName);
    return ast.newSimpleType(name);
  }

  private final Map changes = new LinkedHashMap();

  private EnumerizationComputer computer;

  /**
   * The input fields to attempt to refactor.
   */
  private List fieldsToRefactor = new LinkedList();

  /**
   * A map from search matches to the reason they were searched for.
   * The key set is the declarations that need to be transformed.
   */
  private final Map matchToPurposeMap = new LinkedHashMap();

  private final Map packageNames = new LinkedHashMap();

  private final Map removedFieldNodes = new LinkedHashMap();

  private final Map simpleTypeNames = new LinkedHashMap();

  private String simpleTypeName; //$NON-NLS-1$
 
  private String packageName;

  /**
   * Default ctor.
   */
  public ConvertConstantsToEnumRefactoring() {
  }

  public ConvertConstantsToEnumRefactoring(List fieldsToRefactor) {
    this.fieldsToRefactor = new LinkedList(fieldsToRefactor);
  }

  public RefactoringStatus checkFinalConditions(final IProgressMonitor monitor)
      throws CoreException, OperationCanceledException {
    final RefactoringStatus status = new RefactoringStatus();
    try {
      monitor.beginTask(Messages.ConvertConstantsToEnumRefactoring_CheckingPreconditions, 2);

      final IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
      this.computer = new EnumerizationComputer(this.fieldsToRefactor,
          scope, monitor);

      // build the enumerization forest.
      /*
       * TODO: Will treat this as a 'blackbox' for now. For more details
       * on the internals of this method, check the paper available at:
       * http://www.cse.ohio-state.edu/~khatchad/papers/khatchad-TR26.pdf
       * or a shorter version at
       * http://presto.cse.ohio-state.edu/pubs/icsm07.pdf.
       */
      this.computer.compute();

      /*
       * TODO: The enumerization forest build by the enum computer
       * consists of only the *minimal* sets which can be transformed into
       * an enum type. That is, elements are grouped together in each set
       * based *only* upon their type dependencies. Therefore, it is
       * possible to, if desired, to further union these sets to build
       * large (in terms of members) types. In the initial test case, the
       * field DECREASE_SPEED is an example of this problem. Since
       * DECREASE_SPEED does not currently share type dependencies with
       * any of the other automobile actions, the computer places it in a
       * singleton set. However, it is clear that DECREASE_SPEED should
       * belong to the set consisting of the other automobile actions. As
       * such, we may want a sophisticated UI that presents the input
       * constants in sets produced by the enumerization computer, then
       * allow the user to further union the sets as desired. After the
       * user has manipulated the sets, we would only need to run the new
       * "forest" through the member constraint filter which is a public
       * static method of the EnumerizationComputer.
       */

      // check to see if any of the input constants weren't enumerizable.
      final RefactoringStatus nonEnumStatus = this
          .reportNonEnumerizableInputConstants();
      status.merge(nonEnumStatus);

      // Get names for the new types.
      this.retrieveTypeNames();
     
      for (final Iterator fit = this.computer.getEnumerizationForest()
          .iterator(); fit.hasNext();) {
        final Collection col = (Collection) fit.next();
        for (final Iterator cit = col.iterator(); cit.hasNext();) {
          final IJavaElement elem = (IJavaElement) cit.next();

          // The search engine.
          final SearchEngine engine = new SearchEngine();

          // The search pattern corresponding to the entities whose
          // type must be altered.
          SearchPattern pattern = SearchPattern.createPattern(elem,
              IJavaSearchConstants.DECLARATIONS,
              SearchPattern.R_EXACT_MATCH);

          // Search for declarations (must always do this since each
          // element's type must be altered).
          commenceSearch(engine, pattern, scope,
              SearchMatchPurpose.ALTER_TYPE_DECLARATION, monitor);

          // if the current element is a that of an original input
          // constant ...
          if (this.fieldsToRefactor.contains(elem)) {
            // The search pattern corresponding to the references to
            // the constant whose parent expression(s) must be
            // altered (more like tweaked).
            pattern = SearchPattern.createPattern(elem,
                IJavaSearchConstants.REFERENCES,
                SearchPattern.R_EXACT_MATCH);

            commenceSearch(engine, pattern, scope,
                SearchMatchPurpose.ALTER_NAMESPACE_PREFIX,
                monitor);
          }

          // if the current element needs infix expression
          // manipulation ...
          if (this.computer
              .getElemToLegalInfixExpressionSourceRangeMap()
              .containsKey(elem)) {
            pattern = SearchPattern.createPattern(elem,
                IJavaSearchConstants.REFERENCES,
                SearchPattern.R_EXACT_MATCH);

            commenceSearch(engine, pattern, scope,
                SearchMatchPurpose.ALTER_INFIX_EXPRESSION,
                monitor);
          }
        }
      }

      // The compilation units needing to be altered mapped to the
      // appropriate search matches.
      final Map units = new HashMap();
      for (final Iterator it = this.matchToPurposeMap.keySet().iterator(); it
          .hasNext();) {
        final SearchMatch match = (SearchMatch) it.next();
        final IJavaElement element = (IJavaElement) match.getElement();
        final ICompilationUnit unit = Util.getIMember(element)
            .getCompilationUnit();
        if (unit != null) {
          Collection searchMatchCollection = (Collection) units
              .get(unit);
          if (searchMatchCollection == null) {
            searchMatchCollection = new ArrayList();
            units.put(unit, searchMatchCollection);
          }
          searchMatchCollection.add(match);
        }
      }

      final Map projects = new HashMap();
      for (final Iterator it = units.keySet().iterator(); it.hasNext();) {
        final ICompilationUnit unit = (ICompilationUnit) it.next();
        final IJavaProject project = unit.getJavaProject();
        if (project != null) {
          Collection unitsCollection = (Collection) projects
              .get(project);
          if (unitsCollection == null) {
            unitsCollection = new ArrayList();
            projects.put(project, unitsCollection);
          }
          unitsCollection.add(unit);
        }
      }

      final ASTRequestor requestor = new ASTRequestor() {

        public void acceptAST(ICompilationUnit source,
            CompilationUnit ast) {
          try {
            ConvertConstantsToEnumRefactoring.this
                .rewriteCompilationUnit(source,
                    (Collection) units.get(source), ast,
                    status, monitor);
          } catch (CoreException exception) {
            RefactoringPlugin.log(exception);
          }
        }
      };

      final IProgressMonitor subMonitor = new SubProgressMonitor(monitor,
          1);
      try {
        final Set set = projects.keySet();
        subMonitor.beginTask(Messages.ConvertConstantsToEnumRefactoring_CompilingSource, set.size());

        for (final Iterator it = set.iterator(); it.hasNext();) {
          final IJavaProject project = (IJavaProject) it.next();
          final ASTParser parser = ASTParser.newParser(AST.JLS3);
          parser.setProject(project);
          parser.setResolveBindings(true);
          final Collection collection = (Collection) projects
              .get(project);
          parser.createASTs((ICompilationUnit[]) collection
              .toArray(new ICompilationUnit[collection.size()]),
              new String[0], requestor, new SubProgressMonitor(
                  subMonitor, 1));
        }

      } finally {
        subMonitor.done();
      }
    } finally {
      monitor.done();
    }

    status.merge(this.insertNewEnumType(monitor));
    return status;
  }

  public RefactoringStatus checkInitialConditions(IProgressMonitor monitor)
      throws CoreException, OperationCanceledException {
    final RefactoringStatus status = new RefactoringStatus();
    try {
      monitor.beginTask(Messages.ConvertConstantsToEnumRefactoring_CheckingPreconditions, 1);
      if (this.fieldsToRefactor.isEmpty())
        status
            .merge(RefactoringStatus
                .createFatalErrorStatus(Messages.ConvertConstantsToEnumRefactoring_FieldsHaveNotBeenSpecified));

      else {
        for (final Iterator it = this.fieldsToRefactor.listIterator(); it
            .hasNext();) {
          final IField field = (IField) it.next();
          if (!field.exists()) {
            String message = Messages.ConvertConstantsToEnumRefactoring_FileDoesNotExist;
            status.addWarning(MessageFormat.format(message, new Object[] {field.getElementName()}));
            it.remove();
          }

          else if (!field.isBinary()
              && !field.getCompilationUnit().isStructureKnown()) {
            String message = Messages.ConvertConstantsToEnumRefactoring_CUContainsCompileErrors;
            status.addWarning(MessageFormat.format(message, new Object[] {field.getCompilationUnit().getElementName()}));
            it.remove();
          }

          else if (field.getElementName().equals("serialVersionUID")) { //$NON-NLS-1$
            String message = Messages.ConvertConstantsToEnumRefactoring_FieldNotEligibleForEnum;
            status.addWarning(MessageFormat.format(message, new Object[] {field.getElementName()}));
            it.remove();
          }

          else if (Signature.getTypeSignatureKind(field
              .getTypeSignature()) != Signature.BASE_TYPE_SIGNATURE) {
            String message = Messages.ConvertConstantsToEnumRefactoring_FieldMustBePrimitive;
            status.addWarning(MessageFormat.format(message, new Object[] {field.getElementName()}));
            it.remove();
          }

          else if (!Util.isConstantField(field)) {
            String message = Messages.ConvertConstantsToEnumRefactoring_FieldIsNotAConstant;
            status.addWarning(MessageFormat.format(message, new Object[] {field.getElementName()}));
            it.remove();
          }

          else if (Flags.isVolatile(field.getFlags())
              || Flags.isTransient(field.getFlags())) {
            String message = Messages.ConvertConstantsToEnumRefactoring_FieldCannotBeExpressedAsEnum;
            status.addWarning(MessageFormat.format(message, new Object[] {field.getElementName()}));
            it.remove();
          }

          if (Signature.getElementType(field.getTypeSignature()) == Signature.SIG_BOOLEAN) {
            String message = Messages.ConvertConstantsToEnumRefactoring_FieldIsBoolean;
            status
                .addWarning(message);
            status.addWarning(MessageFormat.format(message, new Object[] {field.getElementName()}));
            it.remove();
          }
        }
        if (this.fieldsToRefactor.isEmpty())
          status
              .addFatalError(Messages.ConvertConstantsToEnumRefactoring_PreconditionFailed);
      }

    } finally {
      monitor.done();
    }
    return status;
  }

  public Change createChange(IProgressMonitor monitor) throws CoreException,
      OperationCanceledException {
    try {
      monitor.beginTask(Messages.ConvertConstantsToEnumRefactoring_CreatingChange, 1);
      final Collection changes = this.changes.values();
      final CompositeChange change = new CompositeChange(this.getName(),
          (Change[]) changes.toArray(new Change[changes.size()])) {
        public ChangeDescriptor getDescriptor() {
          String project = ConvertConstantsToEnumRefactoring.this
              .getJavaProject().getElementName();
          String description = Messages.ConvertConstantsToEnum_Name;
          Map arguments = new HashMap();
          return new RefactoringChangeDescriptor(
              new ConvertConstantsToEnumDescriptor(project,
                  description, new String(), arguments));
        }
      };
      return change;
    } finally {
      monitor.done();
    }
  }

  public String getName() {
    return Messages.ConvertConstantsToEnum_Name;
  }

  public RefactoringStatus initialize(Map arguments) {
    return new RefactoringStatus();
  }

  /**
   * @param fieldsToRefactor
   *            the fieldsToRefactor to set
   */
  public void setFieldsToRefactor(List fieldsToRefactor) {
    this.fieldsToRefactor = fieldsToRefactor;
  }

  public Collection getFieldsToRefactor() {
    return this.fieldsToRefactor;
  }

  private Collection extractConstants(Collection col) {
    final Collection ret = new LinkedHashSet();

    for (final Iterator it = col.iterator(); it.hasNext();) {
      final IJavaElement elem = (IJavaElement) it.next();
      if (elem.getElementType() == IJavaElement.FIELD)
        ret.add(elem);
    }

    ret.retainAll(this.fieldsToRefactor);
    return ret;
  }

  private String getFullyQualifiedName(IJavaElement elem) {
    // Get the associated set.
    Collection set = null;
    for (final Iterator it = this.computer.getEnumerizationForest()
        .iterator(); it.hasNext();) {
      final Collection col = (Collection) it.next();
      if (col.contains(elem))
        set = col;
    }

    // Get the fully qualified type name.
    final StringBuffer fqn = new StringBuffer(this.simpleTypeNames.get(set)
        .toString());
    fqn.insert(0, '.');
    fqn.insert(0, this.packageNames.get(set));
    return fqn.toString();
  }

  /**
   * @return
   */
  private IJavaProject getJavaProject() {
    /*
     * TODO: Just a simulation
     */
    return ((IField) this.fieldsToRefactor.iterator().next())
        .getJavaProject();
  }

  /**
   * @param monitor
   * @return
   * @throws JavaModelException
   */
  private IPackageFragment getPackageFragment(String packageName,
      IProgressMonitor monitor) throws JavaModelException {
    final IPackageFragmentRoot root = this.getPackageFragmentRoot();
    return root.createPackageFragment(packageName, false, monitor);
  }

  /**
   * @return
   */
  private IPackageFragmentRoot getPackageFragmentRoot()
      throws JavaModelException {
    /*
     * TODO: Just a simulation
     */
    final IJavaProject project = this.getJavaProject();
    return project.getPackageFragmentRoots()[0];
  }

  /*
   * TODO: This function creates a new enum type in its own file. However, the
   * user may want to insert the new enum type as an inner type in some
   * existing class. Such a method should create a new enum type as an ASTNode
   * attached to the AST of the existing class, as opposed to using the
   * "Create New Enum Type" plug-in programmatically (which is what this
   * method does).
   */
  private RefactoringStatus insertNewEnumType(IProgressMonitor monitor)
      throws CoreException {
    final RefactoringStatus status = new RefactoringStatus();
    final AST ast = AST.newAST(AST.JLS3);
    for (final Iterator it = this.computer.getEnumerizationForest()
        .iterator(); it.hasNext();) {
      final Collection col = (Collection) it.next();

      if (col.isEmpty())
        continue;

      final Collection constants = this.extractConstants(col);
      final Map newEnumConstantToOldConstantFieldMap = new HashMap();
      final EnumConstantComparator comparator = new EnumConstantComparator(
          newEnumConstantToOldConstantFieldMap);
      final SortedSet enumConstantDeclarationCollection = new TreeSet(
          comparator);

      final Map annotationToQualifiedNameMap = new HashMap();

      for (final Iterator cit = constants.iterator(); cit.hasNext();) {
        final IField constantField = (IField) cit.next();
        final FieldDeclaration originalFieldDeclaration = (FieldDeclaration) this.removedFieldNodes
            .get(constantField);

        // Get annotations.
        final Collection annotationCollection = new LinkedHashSet();
        for (final Iterator mit = originalFieldDeclaration.modifiers()
            .iterator(); mit.hasNext();) {
          final Object o = mit.next();
          if (o instanceof Annotation) {
            final Annotation oldAnnotation = (Annotation) o;
            final Annotation newAnnotation = (Annotation) ASTNode
                .copySubtree(ast, oldAnnotation);
            annotationToQualifiedNameMap.put(newAnnotation,
                oldAnnotation.resolveTypeBinding()
                    .getQualifiedName());
            annotationCollection.add(newAnnotation);
          }
        }

        // Get the javadoc.
        final Javadoc originalJavadoc = originalFieldDeclaration
            .getJavadoc();
        final Javadoc newJavadoc = (Javadoc) ASTNode.copySubtree(ast,
            originalJavadoc);

        final EnumConstantDeclaration constDecl = createNewEnumConstantDeclarataion(
            ast, ast.newSimpleName(constantField.getElementName()),
            newJavadoc, annotationCollection);

        newEnumConstantToOldConstantFieldMap.put(constDecl, constantField);
        enumConstantDeclarationCollection.add(constDecl);
      }

      // Get the consistent access modifier of the enum constants.
      final int flag = Util.getConsistentVisibility(constants);
      /*******************************************************************
       * * TODO: Need condition check here: 1. If enum is in its own file
       * then only public and package default are allowed. 2. Else, if
       * enum is embedded in another type, then all visibilities are
       * allowed, but may need more checking here for private!.
       ******************************************************************/
      if (!(Flags.isPublic(flag) || Flags.isPackageDefault(flag)))
        status
            .addFatalError(Messages.ConvertConstantsToEnumRefactoring_EnumTypeMustHaveCorrectVisibility);

      EnumDeclaration newEnumDeclaration = null;
      // only add modifier if it is not package default.
      if (!Flags.isPackageDefault(flag)) {
        final Modifier newModifier = ast
            .newModifier(Modifier.ModifierKeyword
                .fromFlagValue(flag));

        newEnumDeclaration = createNewEnumDeclaration(ast, ast
            .newSimpleName((String) this.simpleTypeNames.get(col)),
            enumConstantDeclarationCollection,
            new Object[] { newModifier });
      } else
        newEnumDeclaration = createNewEnumDeclaration(ast, ast
            .newSimpleName((String) this.simpleTypeNames.get(col)),
            enumConstantDeclarationCollection, new Object[] {});

      // TODO [bm] pretty dirty hack to workaround 16: Refactoring should not use UI components for code changes
      //       http://code.google.com/p/constants-to-enum-eclipse-plugin/issues/detail?id=16
      final NewEnumWizardPage[] result = new NewEnumWizardPage[1];
      Display.getDefault().syncExec(new Runnable() {
        public void run() {
          result[0]= new NewEnumWizardPage();
        }
      });
      NewEnumWizardPage page = result[0];
      page.setTypeName((String) this.simpleTypeNames.get(col), false);

      final IPackageFragmentRoot root = this.getPackageFragmentRoot();
      page.setPackageFragmentRoot(root, false);

      final IPackageFragment pack = this.getPackageFragment(
          (String) this.packageNames.get(col), monitor);
      page.setPackageFragment(pack, false);

      /*******************************************************************
       * * TODO: This is somewhat of a dirty workaround. Basically, I am
       * creating a new Enum type only to replace the root AST node. If
       * you have any better ideas please let me know!
       ******************************************************************/
      /*******************************************************************
       * TODO: Need a way of inserting this new type such that it appears
       * in the text changes for rollback purposes, etc.
       ******************************************************************/
      try {
        page.createType(monitor);
      } catch (final InterruptedException E) {
        status.addFatalError(E.getMessage());
      }

      // Modify the newly created enum type.
      final IType newEnumType = page.getCreatedType();
      final CompilationUnit node = (CompilationUnit) Util.getASTNode(
          newEnumType, monitor);

      final ASTRewrite astRewrite = ASTRewrite.create(node.getAST());
      final ImportRewrite importRewrite = ImportRewrite
          .create(node, true);

      final EnumDeclaration oldEnumDeclaration = (EnumDeclaration) node
          .types().get(0);

      // Add imports for annotations to the enum constants.
      for (final Iterator eit = newEnumDeclaration.enumConstants()
          .iterator(); eit.hasNext();) {
        final Object obj = eit.next();
        final EnumConstantDeclaration ecd = (EnumConstantDeclaration) obj;
        for (final Iterator emit = ecd.modifiers().iterator(); emit
            .hasNext();) {
          final Object o = emit.next();
          if (o instanceof Annotation) {
            final Annotation anno = (Annotation) o;
            final String newName = importRewrite
                .addImport((String) annotationToQualifiedNameMap
                    .get(anno));
            anno.setTypeName(ast.newName(newName));
          }
        }
      }
      /*
       * TODO: Need to remove resulting unused imports, but I am unsure of
       * how to do that.
       */

      astRewrite.replace(oldEnumDeclaration, newEnumDeclaration, null);
      this.rewriteAST(newEnumType.getCompilationUnit(), astRewrite,
          importRewrite);
    }

    return status;
  }

  private boolean isEmptyEdit(TextEdit edit) {
    return edit.getClass() == MultiTextEdit.class && !edit.hasChildren();
  }

  private RefactoringStatus removeConstField(ASTRewrite astRewrite,
      ImportRewrite importRewrite,
      FieldDeclaration constFieldDeclaration, IField constField) {
    final RefactoringStatus status = new RefactoringStatus();
    astRewrite.remove(constFieldDeclaration, null);
    this.removedFieldNodes.put(constField, constFieldDeclaration);
    return status;
  }

  /**
   * @return
   */
  private RefactoringStatus reportNonEnumerizableInputConstants() {
    final RefactoringStatus ret = new RefactoringStatus();
    final Collection enumerizableElements = Util
        .flattenForest(this.computer.getEnumerizationForest());
    for (final Iterator it = this.fieldsToRefactor.iterator(); it.hasNext();) {
      final IField field = (IField) it.next();
      if (!enumerizableElements.contains(field)) {
        String message = Messages.ConvertConstantsToEnumRefactoring_RefactoringNotPossible;
        ret.addWarning(MessageFormat.format(message, new Object[] {field.getElementName()}));
      }
    }
    return ret;
  }

  private void retrievePackageNames() {
    /** * TODO: Get real package names. ** */
//    int counter = 0;
    for (final Iterator it = this.computer.getEnumerizationForest()
        .iterator(); it.hasNext();) {
      final Collection col = (Collection) it.next();
//      this.packageNames.put(col, this.packageName + counter++); //$NON-NLS-1$
      this.packageNames.put(col, this.packageName); //$NON-NLS-1$
    }
  }

  private void retrieveSimpleTypeNames() {
    /** * TODO: Get real type names. ** */
//    int counter = 0;
    for (final Iterator it = this.computer.getEnumerizationForest()
        .iterator(); it.hasNext();) {
      final Collection col = (Collection) it.next();
//      this.simpleTypeNames.put(col, simpleTypeName + counter++);
      this.simpleTypeNames.put(col, simpleTypeName);
    }
  }

  public String getSimpleTypeName() {
    return simpleTypeName;
  }

  public void setSimpleTypeName(String simpleTypeName) {
    this.simpleTypeName = simpleTypeName;
  }

  private void retrieveTypeNames() {
    this.retrieveSimpleTypeNames();
    this.retrievePackageNames();
  }

  private void rewriteAST(ICompilationUnit unit, ASTRewrite astRewrite,
      ImportRewrite importRewrite) {
    try {
      final MultiTextEdit edit = new MultiTextEdit();
      final TextEdit astEdit = astRewrite.rewriteAST();

      if (!this.isEmptyEdit(astEdit))
        edit.addChild(astEdit);
      final TextEdit importEdit = importRewrite
          .rewriteImports(new NullProgressMonitor());
      if (!this.isEmptyEdit(importEdit))
        edit.addChild(importEdit);
      if (this.isEmptyEdit(edit))
        return;

      TextFileChange change = (TextFileChange) this.changes.get(unit);
      if (change == null) {
        change = new TextFileChange(unit.getElementName(), (IFile) unit
            .getResource());
        change.setTextType("java"); //$NON-NLS-1$
        change.setEdit(edit);
      } else
        change.getEdit().addChild(edit);

      this.changes.put(unit, change);
    } catch (final MalformedTreeException exception) {
      RefactoringPlugin.log(exception);
    } catch (final IllegalArgumentException exception) {
      RefactoringPlugin.log(exception);
    } catch (final CoreException exception) {
      RefactoringPlugin.log(exception);
    }
  }

  private void rewriteDeclarationsAndNamespaces(CompilationUnit node,
      SearchMatch match, RefactoringStatus status, ASTRewrite astRewrite,
      ImportRewrite importRewrite) throws CoreException {
    // final ASTNode result = NodeFinder.perform(node, match
    // .getOffset(), match.getLength());
    final ASTNode result = Util.getExactASTNode(node, match);

    // Must be simple name node.
    if (result.getNodeType() != ASTNode.SIMPLE_NAME) {
      final String errorMessage = Messages.ConvertConstantsToEnumRefactoring_WrongType;
      status
          .merge(RefactoringStatus
              .createFatalErrorStatus(MessageFormat.format(errorMessage,new Object[] {node, node.getClass()})));
      final IStatus stateStatus = new InternalStateStatus(IStatus.ERROR,
          errorMessage);
      throw new CoreException(stateStatus);
    }

    // Get the fully qualified type name.
    final String fqn = this.getFullyQualifiedName(((Name) result)
        .resolveBinding().getJavaElement());

    if (match instanceof FieldDeclarationMatch
        && this.fieldsToRefactor.contains(match.getElement()))
      status.merge(this.removeConstField(astRewrite, importRewrite, Util
          .getFieldDeclaration(result), (IField) match.getElement()));

    else if (match instanceof FieldDeclarationMatch)
      status.merge(this.rewriteFieldDeclaration(astRewrite,
          importRewrite, Util.getFieldDeclaration(result), fqn));

    // Workaround for Bug 207257.
    else if (match instanceof LocalVariableDeclarationMatch
        || match instanceof LocalVariableReferenceMatch)
      if (((IVariableBinding) ((Name) result).resolveBinding())
          .isParameter())
        status.merge(this.rewriteFormalParameterDeclaration(astRewrite,
            importRewrite, Util
                .getSingleVariableDeclaration(result), fqn));

      else
        status.merge(this.rewriteLocalVariableDeclaration(astRewrite,
            importRewrite, Util
                .getVariableDeclarationStatement(result), fqn));

    else if (match instanceof MethodDeclarationMatch)
      status.merge(this.rewriteMethodDeclaration(astRewrite,
          importRewrite, Util.getMethodDeclaration(result), fqn));

    else if (match instanceof FieldReferenceMatch)
      // Rewrite the reference.
      status.merge(this.rewriteReference(astRewrite, importRewrite,
          (SimpleName) result, fqn));

  }

  private void rewriteExpressions(CompilationUnit node, SearchMatch match,
      RefactoringStatus status, ASTRewrite astRewrite,
      ImportRewrite importRewrite) {
    final ASTNode result = NodeFinder.perform(node, match.getOffset(),
        match.getLength());
    final InfixExpression ie = Util.getInfixExpression(result);
    if (ie == null) // there is none.
      return;
    else if (Util.inNeedOfTransformation(ie.getOperator())) {
      // Get the fully qualified type name.
      final String fqn = this.getFullyQualifiedName(((Name) result)
          .resolveBinding().getJavaElement());
      this.rewriteInfixExpression(astRewrite, importRewrite, ie, fqn);
    }
  }

  private RefactoringStatus rewriteFieldDeclaration(ASTRewrite astRewrite,
      ImportRewrite importRewrite, FieldDeclaration oldFieldDeclaration,
      String fullyQualifiedTypeName) {
    final RefactoringStatus status = new RefactoringStatus();
    final AST ast = oldFieldDeclaration.getAST();
    final String typeName = importRewrite.addImport(fullyQualifiedTypeName);
    final Type newType = getNewType(ast, typeName);
    final Type oldType = oldFieldDeclaration.getType();
    astRewrite.replace(oldType, newType, null);
    return status;
  }

  private RefactoringStatus rewriteFormalParameterDeclaration(
      ASTRewrite astRewrite, ImportRewrite importRewrite,
      SingleVariableDeclaration oldFormalParameterDeclaration,
      String fullyQualifiedTypeName) {

    final RefactoringStatus status = new RefactoringStatus();
    final String typeName = importRewrite.addImport(fullyQualifiedTypeName);
    final AST ast = oldFormalParameterDeclaration.getAST();
    final Type newType = getNewType(ast, typeName);
    final Type oldType = oldFormalParameterDeclaration.getType();
    astRewrite.replace(oldType, newType, null);
    return status;
  }

  private RefactoringStatus rewriteInfixExpression(ASTRewrite astRewrite,
      ImportRewrite importRewrite, InfixExpression ie,
      String fullyQualifiedTypeName) {
    final RefactoringStatus status = new RefactoringStatus();
    final AST ast = ie.getAST();

    final Expression leftExpCopy = (Expression) ASTNode.copySubtree(ast, ie
        .getLeftOperand());
    final Expression rightExpCopy = (Expression) ASTNode.copySubtree(ast,
        ie.getRightOperand());

    final NumberLiteral zero = ast.newNumberLiteral();
    astRewrite.replace(ie.getRightOperand(), zero, null);

    final MethodInvocation newInvocation = ast.newMethodInvocation();
    newInvocation.setExpression(leftExpCopy);
    newInvocation.setName(ast.newSimpleName("compareTo")); //$NON-NLS-1$
    newInvocation.arguments().add(rightExpCopy);

    astRewrite.replace(ie.getLeftOperand(), newInvocation, null);

    if (((ASTNode) newInvocation.arguments().get(0)).getNodeType() == ASTNode.SIMPLE_NAME
        && this.fieldsToRefactor.contains(((SimpleName) ie
            .getRightOperand()).resolveBinding().getJavaElement()))
      this.rewriteReference(astRewrite, importRewrite,
          (SimpleName) newInvocation.arguments().get(0),
          fullyQualifiedTypeName);

    if (((ASTNode) newInvocation.getExpression()).getNodeType() == ASTNode.SIMPLE_NAME
        && this.fieldsToRefactor.contains(((SimpleName) ie
            .getLeftOperand()).resolveBinding().getJavaElement()))
      this.rewriteReference(astRewrite, importRewrite,
          (SimpleName) newInvocation.getExpression(),
          fullyQualifiedTypeName);

    return status;
  }

  private RefactoringStatus rewriteLocalVariableDeclaration(
      ASTRewrite astRewrite, ImportRewrite importRewrite,
      VariableDeclarationStatement oldVariableDeclaration,
      String fullyQualifiedTypeName) {

    final RefactoringStatus status = new RefactoringStatus();
    final AST ast = oldVariableDeclaration.getAST();
    final String typeName = importRewrite.addImport(fullyQualifiedTypeName);
    final Type newType = getNewType(ast, typeName);
    final Type oldType = oldVariableDeclaration.getType();
    astRewrite.replace(oldType, newType, null);
    return status;
  }

  private RefactoringStatus rewriteMethodDeclaration(ASTRewrite astRewrite,
      ImportRewrite importRewrite,
      MethodDeclaration oldMethodDeclaration,
      String fullyQualifiedTypeName) {

    final RefactoringStatus status = new RefactoringStatus();
    final AST ast = oldMethodDeclaration.getAST();
    final String typeName = importRewrite.addImport(fullyQualifiedTypeName);
    final Type newType = getNewType(ast, typeName);
    final Type oldType = oldMethodDeclaration.getReturnType2();
    astRewrite.replace(oldType, newType, null);
    return status;
  }

  private RefactoringStatus rewriteReference(ASTRewrite astRewrite,
      ImportRewrite importRewrite, SimpleName name,
      String fullyQualifiedTypeName) {
    final RefactoringStatus status = new RefactoringStatus();

    // get the node to replace.
    final Name nodeToReplace = Util.getTopmostName(name);

    // If its in a case statement.
    if (Util.isContainedInCaseLabel(name)) {
      if (!nodeToReplace.isSimpleName()) // Need to remove prefix.
        astRewrite.replace(nodeToReplace, name, null);
      return status;
    }

    // Make a copy of the simple name.
    final AST ast = name.getAST();
    final SimpleName nameCopy = (SimpleName) ASTNode.copySubtree(ast, name);

    final String typeName = importRewrite.addImport(fullyQualifiedTypeName);
    final QualifiedName newNameNode = ast.newQualifiedName(ast
        .newName(typeName), nameCopy);

    astRewrite.replace(nodeToReplace, newNameNode, null);
    return status;
  }

  protected void rewriteCompilationUnit(ICompilationUnit unit,
      Collection matches, CompilationUnit node, RefactoringStatus status,
      IProgressMonitor monitor) throws CoreException {
    final ASTRewrite astRewrite = ASTRewrite.create(node.getAST());
    final ImportRewrite importRewrite = ImportRewrite.create(node, true);

    for (final Iterator it = matches.iterator(); it.hasNext();) {
      final SearchMatch match = (SearchMatch) it.next();
      if (match.getAccuracy() == SearchMatch.A_ACCURATE
          && !match.isInsideDocComment())
        if (this.matchToPurposeMap.get(match) == SearchMatchPurpose.ALTER_TYPE_DECLARATION
            || this.matchToPurposeMap.get(match) == SearchMatchPurpose.ALTER_NAMESPACE_PREFIX)
          this.rewriteDeclarationsAndNamespaces(node, match, status,
              astRewrite, importRewrite);
        else if (this.matchToPurposeMap.get(match) == SearchMatchPurpose.ALTER_INFIX_EXPRESSION)
          this.rewriteExpressions(node, match, status, astRewrite,
              importRewrite);
    }

    this.rewriteAST(unit, astRewrite, importRewrite);
  }

  public void setPackageName(String packageName) {
    this.packageName = packageName;
  }

  public String getPackageName() {
    return packageName;
  }
}
TOP

Related Classes of edu.ohio_state.khatchad.refactoring.ConvertConstantsToEnumRefactoring

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.