package com.redhat.ceylon.eclipse.code.refactor; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.utilJ2C; import static com.redhat.ceylon.eclipse.util.EditorUtil.getDocument; import static com.redhat.ceylon.eclipse.util.Nodes.findToplevelStatement; import static org.eclipse.ltk.core.refactoring.RefactoringStatus.createErrorStatus; import java.util.ArrayList; import java.util.List; import org.antlr.runtime.CommonToken; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.jface.text.IDocument; 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.text.edits.ReplaceEdit; import org.eclipse.ui.IEditorPart; import com.redhat.ceylon.compiler.typechecker.context.PhasedUnit; 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.util.Nodes; import com.redhat.ceylon.ide.common.util.escaping_; import com.redhat.ceylon.model.typechecker.model.Class; import com.redhat.ceylon.model.typechecker.model.Interface; import com.redhat.ceylon.model.typechecker.model.Parameter; import com.redhat.ceylon.model.typechecker.model.ParameterList; import com.redhat.ceylon.model.typechecker.model.Type; import com.redhat.ceylon.model.typechecker.model.TypeDeclaration; import com.redhat.ceylon.model.typechecker.model.Unit; public class AliasRefactoring extends AbstractRefactoring { private static class FindAliasedTypeVisitor extends Visitor { private Type type; private List<Node> nodes = new ArrayList<Node>(); private FindAliasedTypeVisitor(Type type) { this.type = type; } public List<Node> getNodes() { return nodes; } @Override public void visit(Tree.Type that) { super.visit(that); if (!(that instanceof Tree.SyntheticVariable)) { Type t = that.getTypeModel(); if (t!=null && type.isExactly(t)) { nodes.add(that); } } } @Override public void visit(Tree.BaseTypeExpression that) { super.visit(that); TypeDeclaration td = type.getDeclaration(); if (isClassWithParameters(td)) { Type t = that.getTarget().getType(); if (t!=null && type.isExactly(t)) { nodes.add(that); } } } } private static boolean isClassWithParameters( TypeDeclaration td) { return td instanceof Class && !td.isTuple() && ((Class) td).getParameterList()!=null; } private String newName; private String typeString; private final Type type; // private boolean renameValuesAndFunctions; public Node getNode() { return node; } public AliasRefactoring(IEditorPart editor) { super(editor); if (rootNode!=null) { if (node instanceof Tree.Type) { Tree.Type t = (Tree.Type) node; type = t.getTypeModel(); newName = null; typeString = Nodes.text(t, tokens); } else { type = null; } } else { type = null; } } @Override public boolean getEnabled() { return type!=null && project != null; } public int getCount() { return type==null ? 0 : countDeclarationOccurrences(); } @Override protected boolean searchInFile(PhasedUnit pu) { return inSamePackage(pu) && super.searchInFile(pu); } @Override int countReferences(Tree.CompilationUnit cu) { FindAliasedTypeVisitor frv = new FindAliasedTypeVisitor(type); cu.visit(frv); return frv.getNodes().size(); } public String getName() { return "Introduce Type Alias"; } public RefactoringStatus checkInitialConditions( IProgressMonitor pm) throws CoreException, OperationCanceledException { // Check parameters retrieved from editor context return new RefactoringStatus(); } public RefactoringStatus checkFinalConditions( IProgressMonitor pm) throws CoreException, OperationCanceledException { if (newName==null || !newName.matches("^[a-zA-Z_]\\w*$")) { return createErrorStatus( "Not a legal Ceylon identifier"); } else if (escaping_.get_().isKeyword(newName)) { return createErrorStatus( "'" + newName + "' is a Ceylon keyword"); } else { int ch = newName.codePointAt(0); if (!Character.isUpperCase(ch)) { return createErrorStatus( "Not an initial uppercase identifier"); } } /*Declaration existing = declaration.getContainer() .getMemberOrParameter(declaration.getUnit(), newName, null, false); if (null!=existing && !existing.equals(declaration)) { return createWarningStatus("An existing declaration named '" + newName + "' already exists in the same scope"); }*/ return new RefactoringStatus(); } private int aliasOffset; private int insertedLength; private int insertedLocation; int getAliasOffset() { return aliasOffset; } int getAliasLength() { return typeString.length(); } public int getInsertedLength() { return insertedLength; } public int getInsertedLocation() { return insertedLocation; } @Override protected void refactorInFile(TextChange tfc, CompositeChange cc, Tree.CompilationUnit root, List<CommonToken> tokens) { tfc.setEdit(new MultiTextEdit()); if (type!=null) { Unit editorUnit = editor.getParseController() .getLastCompilationUnit() .getUnit(); Unit unit = root.getUnit(); if (editorUnit.getPackage() .equals(unit.getPackage())) { IDocument doc = getDocument(tfc); String delim = utilJ2C().indents() .getDefaultLineDelimiter(document); if (newName!=null) { for (Node node: getNodesToRename(root)) { renameNode(tfc, node, root); } } if (unit.getFilename() .equals(editorUnit.getFilename())) { Type t = getType(); TypeDeclaration td = t.getDeclaration(); StringBuffer header = new StringBuffer(); Tree.Statement statement = findToplevelStatement( this.rootNode, this.node); int insertLoc = statement==null ? doc.getLength() : statement.getStartIndex(); aliasOffset = insertLoc; // doc.getLength() + // delim.length()*2; if (td.isShared()) { header.append("shared "); aliasOffset += 7; } StringBuffer args = new StringBuffer(); String initialName = getInitialName(); if (isClassWithParameters(td)) { Class c = (Class) td; aliasOffset += 6; header.append("class ") .append(initialName) .append("("); args.append("("); boolean first = true; ParameterList pl = c.getParameterList(); for (Parameter p: pl.getParameters()) { if (first) { first = false; } else { header.append(", "); args.append(", "); } String ptype = t.getTypedParameter(p) .getFullType() .asString(unit); String pname = p.getName(); header.append(ptype) .append(" ") .append(pname); args.append(pname); } header.append(")"); args.append(")"); } else if (td instanceof Interface) { aliasOffset += 10; header.append("interface ") .append(initialName); } else { aliasOffset += 6; header.append("alias ") .append(initialName); } String indent = utilJ2C().indents() .getDefaultIndent(); String text = header + delim + indent + indent + "=> " + t.asString(unit) + args + ";" + delim + delim; insertedLength = text.length(); insertedLocation = insertLoc; tfc.addEdit(new InsertEdit( insertLoc, text)); } } // if (renameValuesAndFunctions) { // for (Tree.Identifier id: getIdentifiersToRename(root)) { // renameIdentifier(tfc, id, root); // } // } } if (cc!=null && tfc.getEdit().hasChildren()) { cc.add(tfc); } } String getInitialName() { return newName==null ? typeString : newName; } public List<Node> getNodesToRename( Tree.CompilationUnit root) { FindAliasedTypeVisitor frv = new FindAliasedTypeVisitor(type); root.visit(frv); return frv.getNodes(); } protected void renameNode(TextChange tfc, Node node, Tree.CompilationUnit root) { tfc.addEdit(new ReplaceEdit( node.getStartIndex(), node.getDistance(), newName)); } /*public boolean isRenameValuesAndFunctions() { return renameValuesAndFunctions; } public void setRenameValuesAndFunctions(boolean renameLocals) { this.renameValuesAndFunctions = renameLocals; }*/ public void setNewName(String text) { newName = text; } public Type getType() { return type; } public String getNewName() { return newName; } @Override protected boolean isAffectingOtherFiles() { return true; } }