package com.redhat.ceylon.eclipse.code.refactor; import static com.redhat.ceylon.eclipse.code.refactor.MoveUtil.createEditorChange; import static com.redhat.ceylon.eclipse.code.refactor.MoveUtil.getImportText; import static com.redhat.ceylon.eclipse.code.refactor.MoveUtil.getImports; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.utilJ2C; import static com.redhat.ceylon.eclipse.util.Nodes.text; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import org.antlr.runtime.CommonToken; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.CompositeChange; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.TextChange; import org.eclipse.text.edits.InsertEdit; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.ui.IEditorPart; import com.redhat.ceylon.compiler.typechecker.tree.Node; import com.redhat.ceylon.compiler.typechecker.tree.Tree; import com.redhat.ceylon.compiler.typechecker.tree.Visitor; import com.redhat.ceylon.eclipse.code.correct.correctJ2C; import com.redhat.ceylon.ide.common.platform.CommonDocument; import com.redhat.ceylon.ide.common.platform.TextEdit; import com.redhat.ceylon.model.typechecker.model.ClassOrInterface; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.Package; import com.redhat.ceylon.model.typechecker.model.Parameter; import com.redhat.ceylon.model.typechecker.model.Scope; import com.redhat.ceylon.model.typechecker.model.Type; import com.redhat.ceylon.model.typechecker.model.TypeDeclaration; import com.redhat.ceylon.model.typechecker.model.TypeParameter; import com.redhat.ceylon.model.typechecker.model.TypedDeclaration; public class ExtractInterfaceRefactoring extends AbstractRefactoring { String newInterfaceName; Tree.TypedDeclaration[] extractedMembers; Tree.TypedDeclaration[] extractableMembers; private Tree.Declaration container; private Tree.ClassOrInterface containerAsClassOrInter; private Tree.ObjectDefinition containerAsObjectDef; private String packageName; private LinkedHashSet<TypeParameter> extractedTypeParameters = new LinkedHashSet<TypeParameter>(); private HashMap<Declaration, String> extractedImports = new HashMap<Declaration, String>(); private HashSet<String> extractedImportsPackages = new HashSet<String>(); public ExtractInterfaceRefactoring(IEditorPart editor) { super(editor); ClassOrInterface clazz = null; if (node != null) { InternalFindContainerVisitor findContainerVisitor = new InternalFindContainerVisitor(node); findContainerVisitor.visit(rootNode); container = findContainerVisitor.container; if (container instanceof Tree.ClassOrInterface) { containerAsClassOrInter = (Tree.ClassOrInterface) container; clazz = containerAsClassOrInter.getDeclarationModel(); } if (container instanceof Tree.ObjectDefinition) { containerAsObjectDef = (Tree.ObjectDefinition) container; clazz = containerAsObjectDef.getAnonymousClass(); } } if (clazz != null) { extractableMembers = findExtractableMembers(clazz); packageName = findPackageName(clazz); } } @Override public boolean getEnabled() { return extractableMembers != null && extractableMembers.length > 0; } @Override public String getName() { return "Extract Interface"; } @Override protected boolean isAffectingOtherFiles() { return true; //TODO!!!!! } @Override public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException { return new RefactoringStatus(); } @Override public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException { return new RefactoringStatus(); } @Override public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { collectExtractedTypeParametersFromMembers(); collectExtractedTypeParametersFromTheirConstrains(); collectExtractedImports(); StringBuilder newUnitBuilder = new StringBuilder(); addImports(newUnitBuilder); addInterfaceHeader(newUnitBuilder); addInterfaceTypeParameters(newUnitBuilder); addInterfaceTypeConstraints(newUnitBuilder); addInterfaceBody(newUnitBuilder); Change newUnitChange = createNewUnitChange(newUnitBuilder); Change originalUnitChange = createOriginalUnitChange(); CompositeChange cc = new CompositeChange(getName()); cc.add(newUnitChange); cc.add(originalUnitChange); return cc; } @Override void refactorInFile(TextChange textChange, CompositeChange compositChange, Tree.CompilationUnit rootNode, List<CommonToken> tokens) { throw new UnsupportedOperationException(); } private Change createNewUnitChange(StringBuilder newUnitBuilder) { IPath path = sourceFile.getParent() .getProjectRelativePath() .append(newInterfaceName + ".ceylon"); IFile file = project.getFile(path); String desc = "Create source file '" + file.getProjectRelativePath() + "'"; CreateUnitChange newUnitChange = new CreateUnitChange(file, true, newUnitBuilder.toString(), project, desc); return newUnitChange; } private Change createOriginalUnitChange() { TextChange originalUnitChange = createEditorChange(editor, document); originalUnitChange.setEdit(new MultiTextEdit()); addSatisfies(originalUnitChange); addSharedAndActualAnnotations(originalUnitChange); return originalUnitChange; } private void collectExtractedTypeParametersFromMembers() { for (Tree.TypedDeclaration extractedMember : extractedMembers) { TypedDeclaration d = extractedMember.getDeclarationModel(); if (d != null) { collectExtractedTypeParameters(d.getType()); } if (extractedMember instanceof Tree.AnyMethod) { Tree.AnyMethod method = (Tree.AnyMethod) extractedMember; if (method.getParameterLists() != null) { for (Tree.ParameterList parameterList : method.getParameterLists()) { for (Tree.Parameter parameter : parameterList.getParameters()) { Parameter parameterModel = parameter.getParameterModel(); if (parameterModel != null) { collectExtractedTypeParameters( parameterModel.getType()); } } } } if (method.getTypeConstraintList() != null) { for (Tree.TypeConstraint typeConstraint : method.getTypeConstraintList() .getTypeConstraints()) { TypeParameter typeConstraintModel = typeConstraint.getDeclarationModel(); if (typeConstraintModel != null) { collectExtractedTypeParameters( typeConstraintModel.getType()); } } } } } } private void collectExtractedTypeParametersFromTheirConstrains() { if (containerAsClassOrInter != null && !extractedTypeParameters.isEmpty()) { Tree.TypeConstraintList typeConstraintList = typeConstraintList(); if (typeConstraintList != null) { boolean repeat = true; while (repeat) { int size = extractedTypeParameters.size(); for (Tree.TypeConstraint tc : typeConstraintList.getTypeConstraints()) { if (extractedTypeParameters.contains( tc.getDeclarationModel())) { if (tc.getSatisfiedTypes() != null && tc.getSatisfiedTypes().getTypes() != null) { for (Tree.StaticType t : tc.getSatisfiedTypes().getTypes()) { collectExtractedTypeParameters( t.getTypeModel()); } } } } if (size == extractedTypeParameters.size()) { repeat = false; } } } } } private void collectExtractedTypeParameters(Type pt) { if (pt.isTypeParameter()) { TypeDeclaration d = pt.getDeclaration(); extractedTypeParameters.add((TypeParameter) d); for (Type st : pt.getSatisfiedTypes()) { collectExtractedTypeParameters(st); } } else if (pt.isUnion()) { for (Type ct : pt.getCaseTypes()) { collectExtractedTypeParameters(ct); } } else if (pt.isIntersection()) { for (Type st : pt.getSatisfiedTypes()) { collectExtractedTypeParameters(st); } } if (pt.getTypeArgumentList() != null) { for (Type ta : pt.getTypeArgumentList()) { collectExtractedTypeParameters(ta); } } } private void collectExtractedImports() { if (containerAsClassOrInter != null && !extractedTypeParameters.isEmpty()) { Tree.TypeParameterList typeParameterList = containerAsClassOrInter.getTypeParameterList(); if (typeParameterList != null) { for (Tree.TypeParameterDeclaration tp : typeParameterList.getTypeParameterDeclarations()) { if (extractedTypeParameters.contains( tp.getDeclarationModel())) { collectExtractedImports(tp); } } } Tree.TypeConstraintList typeConstraintList = typeConstraintList(); if (typeConstraintList != null) { for (Tree.TypeConstraint tc : typeConstraintList.getTypeConstraints()) { if (extractedTypeParameters.contains( tc.getDeclarationModel())) { collectExtractedImports(tc); } } } } for (Tree.TypedDeclaration member : extractedMembers) { collectExtractedImports(member.getType()); if (member instanceof Tree.AnyMethod) { Tree.AnyMethod method = (Tree.AnyMethod) member; for (Tree.ParameterList parameterList : method.getParameterLists()) { collectExtractedImports(parameterList); } collectExtractedImports(method.getTypeParameterList()); collectExtractedImports(method.getTypeConstraintList()); } } } private void collectExtractedImports(Node node) { if (node != null) { Map<Declaration, String> imports = getImports(node, packageName, null, extractedImportsPackages); extractedImports.putAll(imports); } } private void addImports(StringBuilder content) { String delim = utilJ2C().indents() .getDefaultLineDelimiter(document); String importText = getImportText(extractedImportsPackages, extractedImports, delim); if (!importText.isEmpty()) { content.append(importText); content.append(delim); } } private void addInterfaceHeader(StringBuilder content) { content.append("shared interface "); content.append(newInterfaceName); } private void addInterfaceTypeParameters(StringBuilder content) { if (containerAsClassOrInter != null && !extractedTypeParameters.isEmpty()) { Tree.TypeParameterList typeParameterList = containerAsClassOrInter.getTypeParameterList(); if (typeParameterList != null) { boolean first = true; for (Tree.TypeParameterDeclaration tp : typeParameterList.getTypeParameterDeclarations()) { if (extractedTypeParameters.contains( tp.getDeclarationModel())) { if (first) { first = false; content.append("<"); } else { content.append(", "); } content.append(text(tp, tokens)); } } if (!first) { content.append(">"); } } } } private void addInterfaceTypeConstraints(StringBuilder content) { if (containerAsClassOrInter != null && !extractedTypeParameters.isEmpty()) { Tree.TypeConstraintList typeConstraintList = typeConstraintList(); if (typeConstraintList != null) { for (Tree.TypeConstraint tc : typeConstraintList.getTypeConstraints()) { if (extractedTypeParameters.contains( tc.getDeclarationModel())) { content.append(" "); content.append(text(tc, tokens)); } } } } } private Tree.TypeConstraintList typeConstraintList() { Tree.TypeConstraintList typeConstraintList = null; if (containerAsClassOrInter instanceof Tree.AnyClass) { Tree.AnyClass cl = (Tree.AnyClass) containerAsClassOrInter; typeConstraintList = cl.getTypeConstraintList(); } if (containerAsClassOrInter instanceof Tree.AnyInterface) { Tree.AnyInterface in = (Tree.AnyInterface) containerAsClassOrInter; typeConstraintList = in.getTypeConstraintList(); } return typeConstraintList; } private void addInterfaceBody(StringBuilder content) { String delim = utilJ2C().indents() .getDefaultLineDelimiter(document); String indent = utilJ2C().indents() .getDefaultIndent(); content.append(" {"); content.append(delim); for (Tree.TypedDeclaration member : extractedMembers) { content.append(delim); content.append(indent); Tree.AnnotationList annotationList = member.getAnnotationList(); Tree.AnonymousAnnotation anonymousAnnotation = annotationList.getAnonymousAnnotation(); if (anonymousAnnotation != null) { content.append(text(anonymousAnnotation, tokens)) .append(delim).append(indent); } content.append("shared formal "); if(member.getDeclarationModel().isVariable()){ content.append("variable "); } for (Tree.Annotation annotation : annotationList.getAnnotations()) { String annotationText = text(annotation, tokens); if (annotationText.equals("shared") || annotationText.equals("variable") || annotationText.equals("late") || annotationText.equals("default") || annotationText.equals("actual") || annotationText.equals("formal")) { continue; } content.append(annotationText).append(" "); } content.append(text(member.getType(), tokens)); content.append(" "); content.append(text(member.getIdentifier(), tokens)); if (member instanceof Tree.AnyMethod) { Tree.AnyMethod method = (Tree.AnyMethod) member; if (method.getTypeConstraintList() != null) { content.append(text(method.getTypeParameterList(), tokens)); } for (Tree.ParameterList parameterList : method.getParameterLists()) { content.append(text(parameterList, tokens)); } if (method.getTypeConstraintList() != null) { content.append(" "); content.append(text(method.getTypeConstraintList(), tokens)); } } content.append(";"); content.append(delim); } content.append(delim); content.append("}"); } private void addSatisfies(TextChange originalUnitChange) { int offset = -1; boolean containsSatisfies = false; if (containerAsClassOrInter != null) { if (containerAsClassOrInter instanceof Tree.AnyClass) { Tree.AnyClass cl = (Tree.AnyClass) containerAsClassOrInter; if (cl.getSatisfiedTypes() != null) { offset = cl.getSatisfiedTypes().getEndIndex(); containsSatisfies = true; } else if (cl.getExtendedType() != null) { offset = cl.getExtendedType().getEndIndex(); } else if (cl.getCaseTypes() != null) { offset = cl.getCaseTypes().getEndIndex(); } else if (cl.getParameterList() != null) { offset = cl.getParameterList().getEndIndex(); } else if (cl.getTypeParameterList() != null) { offset = cl.getTypeParameterList().getEndIndex(); } else { offset = cl.getIdentifier().getEndIndex(); } } if (containerAsClassOrInter instanceof Tree.AnyInterface) { Tree.AnyInterface in = (Tree.AnyInterface) containerAsClassOrInter; if (in.getSatisfiedTypes() != null) { offset = in.getSatisfiedTypes().getEndIndex(); containsSatisfies = true; } else if (in.getCaseTypes() != null) { offset = in.getCaseTypes().getEndIndex(); } else if (in.getTypeParameterList() != null) { offset = in.getTypeParameterList().getEndIndex(); } else { offset = in.getIdentifier().getEndIndex(); } } } if (containerAsObjectDef != null) { if (containerAsObjectDef.getSatisfiedTypes() != null) { offset = containerAsObjectDef.getSatisfiedTypes().getEndIndex(); containsSatisfies = true; } else if (containerAsObjectDef.getExtendedType() != null) { offset = containerAsObjectDef.getExtendedType().getEndIndex(); } else { offset = containerAsObjectDef.getIdentifier().getEndIndex(); } } StringBuilder sb = new StringBuilder(); if (containsSatisfies) { sb.append(" & "); } else { sb.append(" satisfies "); } sb.append(newInterfaceName); if (!extractedTypeParameters.isEmpty() && container instanceof Tree.ClassOrInterface) { Tree.ClassOrInterface clazz = (Tree.ClassOrInterface) container; if (clazz.getTypeParameterList() != null) { boolean first = true; for (Tree.TypeParameterDeclaration tp : clazz.getTypeParameterList().getTypeParameterDeclarations()) { if (extractedTypeParameters.contains(tp.getDeclarationModel())) { if (first) { first = false; sb.append("<"); } else { sb.append(", "); } sb.append(text(tp.getIdentifier(), tokens)); } } if (!first) { sb.append(">"); } } } sb.append(" "); originalUnitChange.addEdit(new InsertEdit(offset, sb.toString())); } private void addSharedAndActualAnnotations(TextChange originalUnitChange) { CommonDocument platformDoc = new correctJ2C().newDocument(document); for (Tree.TypedDeclaration member : extractedMembers) { if (!member.getDeclarationModel().isShared()) { TextEdit createInsertAnnotationEdit = new correctJ2C() .addAnnotationsQuickFix() .createInsertAnnotationEdit("shared", member, platformDoc); originalUnitChange.addEdit(new InsertEdit( (int) createInsertAnnotationEdit.getStart(), createInsertAnnotationEdit.getText() )); } if (!member.getDeclarationModel().isActual()) { TextEdit createInsertAnnotationEdit = new correctJ2C() .addAnnotationsQuickFix() .createInsertAnnotationEdit("actual", member, platformDoc); originalUnitChange.addEdit(new InsertEdit( (int) createInsertAnnotationEdit.getStart(), createInsertAnnotationEdit.getText() )); } } } private Tree.TypedDeclaration[] findExtractableMembers(ClassOrInterface clazz) { InternalFindExtractableVisitor findExtractableVisitor = new InternalFindExtractableVisitor(clazz); findExtractableVisitor.visit(container); return findExtractableVisitor.extractableMembers .toArray(new Tree.TypedDeclaration[] {}); } private String findPackageName(Scope scope) { String packageName = null; while (packageName == null) { if (scope == null) { break; } if (scope instanceof Package) { packageName = ((Package) scope).getNameAsString(); } scope = scope.getContainer(); } return packageName; } private static class InternalFindContainerVisitor extends Visitor { private Node node; private Tree.Declaration current; private Tree.Declaration container; private InternalFindContainerVisitor(Node node) { this.node = node; } @Override public void visit(Tree.ObjectDefinition that) { Tree.Declaration d = current; current = that; super.visit(that); current = d; } @Override public void visit(Tree.ClassDefinition that) { Tree.Declaration d = current; current = that; super.visit(that); current = d; } @Override public void visit(Tree.InterfaceDefinition that) { Tree.Declaration d = current; current = that; super.visit(that); current = d; } @Override public void visitAny(Node node) { if (this.node == node) { container = current; } if (container == null) { super.visitAny(node); } } } private static class InternalFindExtractableVisitor extends Visitor { private Declaration container; private List<Tree.TypedDeclaration> extractableMembers = new ArrayList<Tree.TypedDeclaration>(); private InternalFindExtractableVisitor(Declaration container) { this.container = container; } @Override public void visit(Tree.AnyAttribute that) { if (that.getDeclarationModel().getContainer() == container) { extractableMembers.add(that); } } @Override public void visit(Tree.AnyMethod that) { if (that.getDeclarationModel().getContainer() == container) { extractableMembers.add(that); } } } }