package com.redhat.ceylon.eclipse.code.correct; import static com.redhat.ceylon.eclipse.code.imports.ModuleImportUtil.appendNative; 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.Types.getRefinedDeclaration; import static com.redhat.ceylon.model.typechecker.model.ModelUtil.isConstructor; import static java.util.Arrays.asList; import java.util.Collection; import java.util.List; import org.antlr.runtime.CommonToken; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.ltk.core.refactoring.TextChange; import org.eclipse.ltk.core.refactoring.TextFileChange; import org.eclipse.text.edits.InsertEdit; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; import com.redhat.ceylon.common.Backends; 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.ide.common.model.ModifiableSourceFile; import com.redhat.ceylon.ide.common.typechecker.ModifiablePhasedUnit; import com.redhat.ceylon.ide.common.util.FindDeclarationNodeVisitor; import com.redhat.ceylon.model.typechecker.model.Class; import com.redhat.ceylon.model.typechecker.model.ClassOrInterface; import com.redhat.ceylon.model.typechecker.model.Constructor; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.Module; import com.redhat.ceylon.model.typechecker.model.Package; import com.redhat.ceylon.model.typechecker.model.Parameter; import com.redhat.ceylon.model.typechecker.model.Referenceable; import com.redhat.ceylon.model.typechecker.model.Scope; import com.redhat.ceylon.model.typechecker.model.Type; import com.redhat.ceylon.model.typechecker.model.TypeAlias; import com.redhat.ceylon.model.typechecker.model.TypeDeclaration; import com.redhat.ceylon.model.typechecker.model.TypedDeclaration; import com.redhat.ceylon.model.typechecker.model.Unit; import com.redhat.ceylon.model.typechecker.model.UnknownType; import com.redhat.ceylon.model.typechecker.model.Value; public class AddAnnotionProposal extends CorrectionProposal { private static final List<String> ANNOTATIONS_ORDER = asList("doc", "throws", "see", "tagged", "shared", "abstract", "actual", "formal", "default", "variable"); private static final List<String> ANNOTATIONS_ON_SEPARATE_LINE = asList("doc", "throws", "see", "tagged"); private final Referenceable dec; private final String annotation; AddAnnotionProposal(Referenceable dec, String annotation, String desc, int offset, TextChange change, Region selection) { super(desc, change, selection); this.dec = dec; this.annotation = annotation; } @Override public boolean equals(Object obj) { if (obj instanceof AddAnnotionProposal) { AddAnnotionProposal that = (AddAnnotionProposal) obj; return that.dec.equals(dec) && that.annotation.equals(annotation); } else { return super.equals(obj); } } @Override public int hashCode() { return dec.hashCode(); } private static void addAddAnnotationProposal(Node node, String annotation, String desc, Referenceable dec, Collection<ICompletionProposal> proposals, IProject project) { if (dec!=null && !(node instanceof Tree.MissingDeclaration)) { Unit u = dec.getUnit(); if (u instanceof ModifiableSourceFile) { ModifiableSourceFile msf = (ModifiableSourceFile) u; ModifiablePhasedUnit phasedUnit = msf.getPhasedUnit(); FindDeclarationNodeVisitor fdv = new FindDeclarationNodeVisitor(dec); phasedUnit.getCompilationUnit().visit(fdv); Tree.StatementOrArgument decNode = (Tree.StatementOrArgument) fdv.getDeclarationNode(); if (decNode!=null) { addAddAnnotationProposal(annotation, desc, dec, proposals, phasedUnit, node, decNode); } } } } private static void addAddAnnotationProposal( String annotation, String desc, Referenceable dec, Collection<ICompletionProposal> proposals, ModifiablePhasedUnit<IProject,IResource,IFolder,IFile> unit, Node node, Tree.StatementOrArgument decNode) { IFile file = unit.getResourceFile(); if (file == null) { return; } TextFileChange change = new TextFileChange(desc, file); change.setEdit(new MultiTextEdit()); TextEdit edit = createReplaceAnnotationEdit(annotation, node, change); if (edit==null) { edit = createInsertAnnotationEdit(annotation, decNode, getDocument(change)); } change.addEdit(edit); createExplicitTypeEdit(decNode, change); Region selection; if (node!=null && node.getUnit().equals(decNode.getUnit())) { selection = new Region(edit.getOffset(), annotation.length()); } else { selection = null; } AddAnnotionProposal p = new AddAnnotionProposal(dec, annotation, description(annotation, dec), edit.getOffset(), change, selection); if (!proposals.contains(p)) { proposals.add(p); } } private static void createExplicitTypeEdit( Tree.StatementOrArgument decNode, TextFileChange change) { if (decNode instanceof Tree.TypedDeclaration && !(decNode instanceof Tree.ObjectDefinition)) { Tree.TypedDeclaration tdNode = (Tree.TypedDeclaration) decNode; Tree.Type type = tdNode.getType(); if (type.getToken()!=null && (type instanceof Tree.FunctionModifier || type instanceof Tree.ValueModifier)) { Type it = type.getTypeModel(); if (it!=null && !(it.getDeclaration() instanceof UnknownType)) { String explicitType = it.asString(); change.addEdit(new ReplaceEdit( type.getStartIndex(), type.getText().length(), explicitType)); } } } } private static String description(String annotation, Referenceable dec) { String description; if (dec instanceof Declaration) { Declaration d = (Declaration) dec; Scope container = d.getContainer(); String containerDesc = ""; if (container instanceof TypeDeclaration) { TypeDeclaration td = (TypeDeclaration) container; String name = td.getName(); if (name == null && container instanceof Constructor) { Scope cont = container.getContainer(); if (cont instanceof Declaration) { Declaration cd = (Declaration) cont; name = cd.getName(); } } containerDesc = " in '" + name + "'"; } String name = d.getName(); if (name == null && isConstructor(d)) { description = "Make default constructor " + annotation + containerDesc; } else { description = "Make '" + name + "' " + annotation + containerDesc; } } else { description = "Make package '" + dec.getNameAsString() + "' " + annotation; } return description; } private static ReplaceEdit createReplaceAnnotationEdit( String annotation, Node node, TextFileChange change) { String toRemove; if ("formal".equals(annotation)) { toRemove = "default"; } else if ("abstract".equals(annotation)) { toRemove = "final"; } else { return null; } Tree.AnnotationList annotationList = getAnnotationList(node); if (annotationList != null) { for (Tree.Annotation ann: annotationList.getAnnotations()) { if (toRemove.equals(getAnnotationIdentifier(ann))) { int start = ann.getStartIndex(); int length = ann.getDistance(); return new ReplaceEdit(start, length, annotation); } } } return null; } public static InsertEdit createInsertAnnotationEdit( String newAnnotation, Node node, IDocument doc) { String newAnnotationName = getAnnotationWithoutParam(newAnnotation); Tree.Annotation prevAnnotation = null; Tree.Annotation nextAnnotation = null; Tree.AnnotationList annotationList = getAnnotationList(node); if (annotationList != null) { for (Tree.Annotation annotation: annotationList.getAnnotations()) { if (isAnnotationAfter(newAnnotationName, getAnnotationIdentifier(annotation))) { prevAnnotation = annotation; } else if (nextAnnotation == null) { nextAnnotation = annotation; break; } } } int nextNodeStartIndex; if (nextAnnotation != null) { nextNodeStartIndex = nextAnnotation.getStartIndex(); } else { CommonToken tok = (CommonToken) node.getMainToken(); if (node instanceof Tree.AnyAttribute || node instanceof Tree.AnyMethod ) { Tree.TypedDeclaration tdn = (Tree.TypedDeclaration) node; nextNodeStartIndex = tdn.getType().getStartIndex(); } else if (node instanceof Tree.ObjectDefinition ) { nextNodeStartIndex = tok.getStartIndex(); } else if (node instanceof Tree.ClassOrInterface) { nextNodeStartIndex = tok.getStartIndex(); } else { nextNodeStartIndex = node.getStartIndex(); } } int newAnnotationOffset; StringBuilder newAnnotationText = new StringBuilder(); if (isAnnotationOnSeparateLine(newAnnotationName) && !(node instanceof Tree.Parameter)) { if (prevAnnotation != null && isAnnotationOnSeparateLine( getAnnotationIdentifier(prevAnnotation))) { newAnnotationOffset = prevAnnotation.getEndIndex(); newAnnotationText.append(System.lineSeparator()); newAnnotationText.append(utilJ2C().indents().getIndent(node, doc)); newAnnotationText.append(newAnnotation); } else { newAnnotationOffset = nextNodeStartIndex; newAnnotationText.append(newAnnotation); newAnnotationText.append(System.lineSeparator()); newAnnotationText.append(utilJ2C().indents().getIndent(node, doc)); } } else { newAnnotationOffset = nextNodeStartIndex; newAnnotationText.append(newAnnotation); newAnnotationText.append(" "); } return new InsertEdit(newAnnotationOffset, newAnnotationText.toString()); } public static Tree.AnnotationList getAnnotationList(Node node) { Tree.AnnotationList annotationList = null; if (node instanceof Tree.Declaration) { Tree.Declaration tdn = (Tree.Declaration) node; annotationList = tdn.getAnnotationList(); } else if (node instanceof Tree.ModuleDescriptor) { Tree.ModuleDescriptor mdn = (Tree.ModuleDescriptor) node; annotationList = mdn.getAnnotationList(); } else if (node instanceof Tree.PackageDescriptor) { Tree.PackageDescriptor pdn = (Tree.PackageDescriptor) node; annotationList = pdn.getAnnotationList(); } else if (node instanceof Tree.Assertion) { Tree.Assertion an = (Tree.Assertion) node; annotationList = an.getAnnotationList(); } return annotationList; } public static String getAnnotationIdentifier( Tree.Annotation annotation) { String annotationName = null; if (annotation != null) { Tree.Primary primary = annotation.getPrimary(); if (primary instanceof Tree.BaseMemberExpression) { Tree.BaseMemberExpression bme = (Tree.BaseMemberExpression) primary; annotationName = bme.getIdentifier().getText(); } } return annotationName; } private static String getAnnotationWithoutParam(String annotation) { int index = annotation.indexOf("("); if (index != -1) { return annotation.substring(0, index).trim(); } index = annotation.indexOf("\""); if (index != -1) { return annotation.substring(0, index).trim(); } index = annotation.indexOf(" "); if (index != -1) { return annotation.substring(0, index).trim(); } return annotation.trim(); } private static boolean isAnnotationAfter( String annotation1, String annotation2) { int index1 = ANNOTATIONS_ORDER.indexOf(annotation1); int index2 = ANNOTATIONS_ORDER.indexOf(annotation2); return index1 >= index2; } private static boolean isAnnotationOnSeparateLine(String annotation) { return ANNOTATIONS_ON_SEPARATE_LINE.contains(annotation); } static void addMakeActualDecProposal( Collection<ICompletionProposal> proposals, IProject project, Node node) { Declaration dec = annotatedNode(node); boolean shared = dec.isShared(); addAddAnnotationProposal(node, shared ? "actual" : "shared actual", shared ? "Make Actual" : "Make Shared Actual", dec, proposals, project); } private static Declaration annotatedNode(Node node) { Declaration dec; if (node instanceof Tree.Declaration) { Tree.Declaration dn = (Tree.Declaration) node; dec = dn.getDeclarationModel(); } else { dec = (Declaration) node.getScope(); } return dec; } static void addMakeDefaultProposal( Collection<ICompletionProposal> proposals, IProject project, Node node) { Declaration d; if (node instanceof Tree.Declaration) { Tree.Declaration decNode = (Tree.Declaration) node; //get the supertype declaration we're refining d = getRefinedDeclaration( decNode.getDeclarationModel()); } else if (node instanceof Tree.SpecifierStatement) { Tree.SpecifierStatement specNode = (Tree.SpecifierStatement) node; //get the supertype declaration we're referencing d = specNode.getRefined(); } /*else if (node instanceof Tree.BaseMemberExpression) { Tree.BaseMemberExpression bme = (Tree.BaseMemberExpression) node; d = bme.getDeclaration(); }*/ else { return; } addAddAnnotationProposal(node, "default", "Make Default", d, proposals, project); /*if (d.isClassOrInterfaceMember()) { ClassOrInterface container = (ClassOrInterface) d.getContainer(); String name = d.getName(); List<Declaration> rds = container.getInheritedMembers(name); Declaration rd=null; if (rds.isEmpty()) { rd=d; //TODO: is this really correct? What case does it handle? } else { for (Declaration r: rds) { if (!r.isDefault()) { //just take the first one :-/ //TODO: this is very wrong! Instead, make them all default! rd = r; break; } } } if (rd!=null) { addAddAnnotationProposal(node, "default", "Make Default", rd, proposals, project); } }*/ } static void addMakeDefaultDecProposal( Collection<ICompletionProposal> proposals, IProject project, Node node) { Declaration dec = annotatedNode(node); addAddAnnotationProposal(node, dec.isShared() ? "default" : "shared default", dec.isShared() ? "Make Default" : "Make Shared Default", dec, proposals, project); } static void addMakeFormalDecProposal( Collection<ICompletionProposal> proposals, IProject project, Node node) { Declaration dec = annotatedNode(node); addAddAnnotationProposal(node, dec.isShared() ? "formal" : "shared formal", dec.isShared() ? "Make Formal" : "Make Shared Formal", dec, proposals, project); } static void addMakeAbstractDecProposal( Collection<ICompletionProposal> proposals, IProject project, Node node) { Declaration dec = annotatedNode(node); if (dec instanceof Class) { addAddAnnotationProposal(node, "abstract", "Make Abstract", dec, proposals, project); } } public static void addMakeNativeProposal( final Collection<ICompletionProposal> proposals, final IProject project, final Node node, final Tree.CompilationUnit rootNode, final IFile file) { if (node instanceof Tree.ImportPath) { new Visitor() { @Override public void visit(Tree.ModuleDescriptor that) { if (node instanceof Tree.ImportPath) { Tree.ImportPath ip = (Tree.ImportPath) node; Module module = (Module) ip.getModel(); Backends backends = module.getNativeBackends(); TextFileChange change = new TextFileChange( "Declare Module Native", file); StringBuilder annotation = new StringBuilder(); appendNative(annotation, backends); change.setEdit(new InsertEdit( that.getStartIndex(), annotation + " ")); proposals.add(new CorrectionProposal( "Declare module '" + annotation + "'", change, null)); } super.visit(that); } @Override public void visit(Tree.ImportModule that) { if (that.getImportPath()==node) { Module module = (Module) that.getImportPath() .getModel(); Backends backends = module.getNativeBackends(); TextFileChange change = new TextFileChange( "Declare Import Native", file); StringBuilder annotation = new StringBuilder(); appendNative(annotation, backends); change.setEdit(new InsertEdit( that.getStartIndex(), annotation + " ")); proposals.add(new CorrectionProposal( "Declare import '" + annotation + "'", change, null)); } super.visit(that); } }.visit(rootNode); } } static void addMakeContainerAbstractProposal( Collection<ICompletionProposal> proposals, IProject project, Node node) { Declaration dec; if (node instanceof Tree.Declaration) { Tree.Declaration dn = (Tree.Declaration) node; Scope container = dn.getDeclarationModel().getContainer(); if (container instanceof Declaration) { dec = (Declaration) container; } else { return; } } else { dec = (Declaration) node.getScope(); } addAddAnnotationProposal(node, "abstract", "Make Abstract", dec, proposals, project); } static void addMakeVariableProposal( Collection<ICompletionProposal> proposals, IProject project, Node node) { Tree.Term term; if (node instanceof Tree.AssignmentOp) { Tree.AssignOp ao = (Tree.AssignOp) node; term = ao.getLeftTerm(); } else if (node instanceof Tree.UnaryOperatorExpression) { Tree.PrefixOperatorExpression poe = (Tree.PrefixOperatorExpression) node; term = poe.getTerm(); } else if (node instanceof Tree.MemberOrTypeExpression) { term = (Tree.MemberOrTypeExpression) node; } else if (node instanceof Tree.SpecifierStatement) { Tree.SpecifierStatement ss = (Tree.SpecifierStatement) node; term = ss.getBaseMemberExpression(); } else { return; } if (term instanceof Tree.MemberOrTypeExpression) { Tree.MemberOrTypeExpression mte = (Tree.MemberOrTypeExpression) term; Declaration dec = mte.getDeclaration(); if (dec instanceof Value) { Value value = (Value) dec; if (value.getOriginalDeclaration()==null && !value.isTransient()) { addAddAnnotationProposal(node, "variable", "Make Variable", dec, proposals, project); if (dec.isClassMember()) { addAddAnnotationProposal(node, "late", "Make Late", dec, proposals, project); } } } } } static void addMakeVariableDecProposal( Collection<ICompletionProposal> proposals, IProject project, Tree.Declaration node) { final Declaration dec = node.getDeclarationModel(); if (dec instanceof Value && node instanceof Tree.AttributeDeclaration) { final Value v = (Value) dec; if (!v.isVariable() && !v.isTransient()) { addAddAnnotationProposal(node, "variable", "Make Variable", dec, proposals, project); } } } static void addMakeVariableDecProposal( Collection<ICompletionProposal> proposals, IProject project, Tree.CompilationUnit cu, Node node) { final Tree.SpecifierOrInitializerExpression sie = (Tree.SpecifierOrInitializerExpression) node; class GetInitializedVisitor extends Visitor { Value dec; @Override public void visit(Tree.AttributeDeclaration that) { super.visit(that); if (that.getSpecifierOrInitializerExpression()==sie) { dec = that.getDeclarationModel(); } } } GetInitializedVisitor v = new GetInitializedVisitor(); v.visit(cu); addAddAnnotationProposal(node, "variable", "Make Variable", v.dec, proposals, project); } static void addMakeSharedProposalForSupertypes( Collection<ICompletionProposal> proposals, IProject project, Node node) { if (node instanceof Tree.ClassOrInterface) { Tree.ClassOrInterface cin = (Tree.ClassOrInterface) node; ClassOrInterface ci = cin.getDeclarationModel(); Type extendedType = ci.getExtendedType(); if (extendedType!=null) { addMakeSharedProposal(proposals, project, extendedType.getDeclaration()); for (Type typeArgument: extendedType.getTypeArgumentList()) { addMakeSharedProposal(proposals, project, typeArgument.getDeclaration()); } } List<Type> satisfiedTypes = ci.getSatisfiedTypes(); if (satisfiedTypes!=null) { for (Type satisfiedType: satisfiedTypes) { addMakeSharedProposal(proposals, project, satisfiedType.getDeclaration()); for (Type typeArgument: satisfiedType.getTypeArgumentList()) { addMakeSharedProposal(proposals, project, typeArgument.getDeclaration()); } } } } } static void addMakeRefinedSharedProposal( Collection<ICompletionProposal> proposals, IProject project, Node node) { if (node instanceof Tree.Declaration) { Tree.Declaration tdn = (Tree.Declaration) node; Declaration refined = tdn.getDeclarationModel() .getRefinedDeclaration(); if (refined.isDefault() || refined.isFormal()) { addMakeSharedProposal(proposals, project, refined); } else { addAddAnnotationProposal(node, "shared default", "Make Shared Default", refined, proposals, project); } } } static void addMakeSharedProposal( Collection<ICompletionProposal> proposals, IProject project, Node node) { Referenceable dec = null; List<Type> typeArgumentList = null; if (node instanceof Tree.StaticMemberOrTypeExpression) { Tree.StaticMemberOrTypeExpression qmte = (Tree.StaticMemberOrTypeExpression) node; dec = qmte.getDeclaration(); } //TODO: handle much more kinds of types! else if (node instanceof Tree.SimpleType) { Tree.SimpleType st = (Tree.SimpleType) node; dec = st.getDeclarationModel(); } else if (node instanceof Tree.OptionalType) { Tree.OptionalType ot = (Tree.OptionalType) node; if (ot.getDefiniteType() instanceof Tree.SimpleType) { Tree.SimpleType st = (Tree.SimpleType) ot.getDefiniteType(); dec = st.getDeclarationModel(); } } else if (node instanceof Tree.IterableType) { Tree.IterableType it = (Tree.IterableType) node; if (it.getElementType() instanceof Tree.SimpleType) { Tree.SimpleType st = (Tree.SimpleType) it.getElementType(); dec = st.getDeclarationModel(); } } else if (node instanceof Tree.SequenceType) { Tree.SequenceType qt = (Tree.SequenceType) node; if (qt.getElementType() instanceof Tree.SimpleType) { Tree.SimpleType st = (Tree.SimpleType) qt.getElementType(); dec = st.getDeclarationModel(); } } else if (node instanceof Tree.ImportMemberOrType) { Tree.ImportMemberOrType imt = (Tree.ImportMemberOrType) node; dec = imt.getDeclarationModel(); } else if (node instanceof Tree.ImportPath) { Tree.ImportPath ip = (Tree.ImportPath) node; dec = ip.getModel(); } else if (node instanceof Tree.TypedDeclaration) { Tree.TypedDeclaration tdn = (Tree.TypedDeclaration) node; TypedDeclaration td = tdn.getDeclarationModel(); if (td!=null) { Type pt = td.getType(); dec = pt.getDeclaration(); typeArgumentList = pt.getTypeArgumentList(); } } else if (node instanceof Tree.Parameter) { Tree.Parameter parameter = (Tree.Parameter) node; Parameter param = parameter.getParameterModel(); if (param!=null && param.getType()!=null) { Type pt = param.getType(); dec = pt.getDeclaration(); typeArgumentList = pt.getTypeArgumentList(); } } addMakeSharedProposal(proposals, project, dec); if (typeArgumentList != null) { for (Type typeArgument: typeArgumentList) { addMakeSharedProposal(proposals, project, typeArgument.getDeclaration()); } } } static void addMakeSharedProposal( Collection<ICompletionProposal> proposals, IProject project, Type type) { if (type!=null) { if (type.isUnion()) { for (Type caseType: type.getCaseTypes()) { addMakeSharedProposal(proposals, project, caseType); for (Type typeArgument: caseType.getTypeArgumentList()) { addMakeSharedProposal(proposals, project, typeArgument); } } } else if (type.isIntersection()) { for (Type satisfiedType: type.getSatisfiedTypes()) { addMakeSharedProposal(proposals, project, satisfiedType); for (Type typeArgument: satisfiedType.getTypeArgumentList()) { addMakeSharedProposal(proposals, project, typeArgument); } } } else { addMakeSharedProposal(proposals, project, type.getDeclaration()); } } } static void addMakeSharedProposal( Collection<ICompletionProposal> proposals, IProject project, Referenceable ref) { if (ref!=null) { if (ref instanceof TypedDeclaration || ref instanceof ClassOrInterface || ref instanceof TypeAlias) { if (!((Declaration) ref).isShared()) { addAddAnnotationProposal(null, "shared", "Make Shared", ref, proposals, project); } } else if (ref instanceof Package) { if (!((Package) ref).isShared()) { addAddAnnotationProposal(null, "shared", "Make Shared", ref, proposals, project); } } } } static void addMakeSharedDecProposal( Collection<ICompletionProposal> proposals, IProject project, Node node) { if (node instanceof Tree.Declaration) { Tree.Declaration dn = (Tree.Declaration) node; addAddAnnotationProposal(node, "shared", "Make Shared", dn.getDeclarationModel(), proposals, project); } } }