| /********************************************************************** |
| * This file is part of "Object Teams Development Tooling"-Software |
| * |
| * Copyright 2010, 2011 Stephan Herrmann. |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Please visit http://www.eclipse.org/objectteams for updates and contact. |
| * |
| * Contributors: |
| * Stephan Herrmann - Initial API and implementation |
| **********************************************************************/ |
| package org.eclipse.objectteams.otdt.internal.ui.text.correction; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.dom.AST; |
| import org.eclipse.jdt.core.dom.ASTNode; |
| import org.eclipse.jdt.core.dom.AbstractMethodMappingDeclaration; |
| import org.eclipse.jdt.core.dom.CallinMappingDeclaration; |
| import org.eclipse.jdt.core.dom.CalloutMappingDeclaration; |
| import org.eclipse.jdt.core.dom.CompilationUnit; |
| import org.eclipse.jdt.core.dom.FieldAccessSpec; |
| import org.eclipse.jdt.core.dom.IMethodBinding; |
| import org.eclipse.jdt.core.dom.IMethodMappingBinding; |
| import org.eclipse.jdt.core.dom.ITypeBinding; |
| import org.eclipse.jdt.core.dom.IVariableBinding; |
| import org.eclipse.jdt.core.dom.MethodMappingElement; |
| import org.eclipse.jdt.core.dom.MethodSpec; |
| import org.eclipse.jdt.core.dom.ParameterizedType; |
| import org.eclipse.jdt.core.dom.RoleTypeDeclaration; |
| import org.eclipse.jdt.core.dom.Type; |
| import org.eclipse.jdt.core.dom.TypeParameter; |
| import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; |
| import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; |
| import org.eclipse.jdt.core.dom.rewrite.ListRewrite; |
| import org.eclipse.jdt.internal.corext.dom.ASTNodes; |
| import org.eclipse.jdt.internal.ui.JavaPluginImages; |
| import org.eclipse.jdt.internal.ui.text.correction.proposals.LinkedCorrectionProposal; |
| import org.eclipse.objectteams.otdt.internal.ui.util.OTStubUtility; |
| import org.eclipse.objectteams.otdt.ui.OTDTUIPlugin; |
| import org.eclipse.text.edits.TextEditGroup; |
| |
| |
| /** |
| * Correction proposals for adding signatures to signature-less method mappings. |
| * Use linked mode if several base methods of the given name exist and are compatible. |
| * |
| * @since 2.1.0; before 2.0.0 everything was inlined in {@link QuickAssistProcessor}. |
| */ |
| @SuppressWarnings("restriction") |
| public class AddMethodMappingSignaturesProposal extends LinkedCorrectionProposal |
| { |
| protected static final String KEY_BASEMETHOD = "basemethod"; //$NON-NLS-1$ |
| |
| private final AbstractMethodMappingDeclaration mapping; |
| |
| public AddMethodMappingSignaturesProposal(ICompilationUnit cu, AbstractMethodMappingDeclaration mapping, int relevance) |
| { |
| super(CorrectionMessages.QuickAssistProcessor_addMethodBindingSignatures_label, |
| cu, null, |
| relevance, JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_ADD)); |
| this.mapping = mapping; |
| } |
| |
| @Override |
| protected ASTRewrite getRewrite() throws CoreException |
| { |
| ASTRewrite rewrite = ASTRewrite.create(mapping.getAST()); |
| ICompilationUnit cu = getCompilationUnit(); |
| TextEditGroup editGroup = new TextEditGroup(CorrectionMessages.QuickAssistProcessor_addSignature_editName); |
| ImportRewrite imports = createImportRewrite((CompilationUnit) ASTNodes.getParent(mapping, ASTNode.COMPILATION_UNIT)); |
| // role method: |
| IMethodBinding roleMethod = ((MethodSpec)mapping.getRoleMappingElement()).resolveBinding(); |
| MethodSpec newSpec = OTStubUtility.createMethodSpec(cu, rewrite, imports, roleMethod, true); |
| convertTypeParameters(mapping.getAST(), roleMethod, newSpec); |
| rewrite.set(mapping, mapping.getRoleElementProperty(), newSpec, editGroup); |
| |
| // base method(s): |
| if (mapping.getNodeType() == ASTNode.CALLIN_MAPPING_DECLARATION) |
| addSignatureToCallinBases(cu, rewrite, imports, (CallinMappingDeclaration) mapping, editGroup, roleMethod); |
| else |
| addSignatureToCalloutBase(cu, rewrite, imports, (CalloutMappingDeclaration) mapping, editGroup, roleMethod); |
| return rewrite; |
| } |
| |
| // helper: add signatures to all base specs of a callin mapping: |
| private void addSignatureToCallinBases(ICompilationUnit cu, |
| ASTRewrite rewrite, |
| ImportRewrite imports, |
| CallinMappingDeclaration mapping, |
| TextEditGroup editGroup, |
| IMethodBinding roleMethod) throws CoreException |
| { |
| @SuppressWarnings("unchecked") |
| List<MethodSpec> oldBaseSpecs = mapping.getBaseMappingElements(); |
| ListRewrite baseMethodsRewrite = rewrite.getListRewrite(mapping, CallinMappingDeclaration.BASE_MAPPING_ELEMENTS_PROPERTY); |
| IMethodMappingBinding mappingBinding = mapping.resolveBinding(); |
| if (mappingBinding != null) { |
| // create proposal from exact methods |
| IMethodBinding[] baseMethods = mappingBinding.getBaseMethods(); |
| for (int i=0; i<baseMethods.length; i++) { |
| IMethodBinding baseMethod = baseMethods[i]; |
| try { |
| MethodSpec newSpec = OTStubUtility.createMethodSpec(cu, rewrite, imports, baseMethod, true); |
| baseMethodsRewrite.replace(oldBaseSpecs.get(i), newSpec, editGroup); |
| } catch (CoreException e) { |
| OTDTUIPlugin.log(e); |
| } |
| } |
| } else { |
| ITypeBinding[] roleParameters = roleMethod.getParameterTypes(); |
| RoleTypeDeclaration role = (RoleTypeDeclaration) mapping.getParent(); |
| for (int i=0; i < oldBaseSpecs.size(); i++) { |
| MethodSpec oldBaseSpec = oldBaseSpecs.get(i); |
| // search matching base methods: |
| List<IMethodBinding> matchingBaseMethods = new ArrayList<IMethodBinding>(); |
| guessBaseMethod(role.resolveBinding(), oldBaseSpec.getName().getIdentifier(), roleParameters, true, matchingBaseMethods); |
| if (matchingBaseMethods.size() == 0) |
| throw new CoreException(new Status(IStatus.ERROR, |
| "org.eclipse.objectteams.otdt.jdt.ui", //$NON-NLS-1$ |
| "Could not find a matching base method")); //$NON-NLS-1$ |
| try { |
| MethodSpec newSpec = OTStubUtility.createMethodSpec(cu, rewrite, imports, matchingBaseMethods.get(0), true); |
| baseMethodsRewrite.replace(oldBaseSpecs.get(i), newSpec, editGroup); |
| // do we have alternatives to propose? |
| if (matchingBaseMethods.size() > 1) { |
| addLinkedPosition(rewrite.track(newSpec), false, KEY_BASEMETHOD); |
| for(IMethodBinding baseMethodBinding : matchingBaseMethods) { |
| MethodSpec bSpec = OTStubUtility.createMethodSpec(cu, rewrite, imports, baseMethodBinding, true); |
| addLinkedPositionProposal(KEY_BASEMETHOD, bSpec.toString(), null); |
| } |
| } |
| } catch (CoreException e) { |
| OTDTUIPlugin.log(e); |
| } |
| } |
| } |
| } |
| |
| // helper: add signature to a callout base spec (method or field) |
| private void addSignatureToCalloutBase(ICompilationUnit cu, |
| ASTRewrite rewrite, |
| ImportRewrite imports, |
| CalloutMappingDeclaration mapping, |
| TextEditGroup editGroup, |
| IMethodBinding roleMethod) |
| { |
| MethodMappingElement baseElement; |
| try { |
| List<IMethodBinding> matchingBaseMethods = new ArrayList<IMethodBinding>(); |
| if (mapping.bindingOperator().isCalloutToField()) { |
| IVariableBinding baseField = ((FieldAccessSpec)mapping.getBaseMappingElement()).resolveBinding(); |
| baseElement = OTStubUtility.createFieldSpec(mapping.getAST(), imports, baseField, true); |
| } else { |
| IMethodMappingBinding mappingBinding = mapping.resolveBinding(); |
| if (mappingBinding != null) { |
| // create proposal from exact method |
| IMethodBinding baseMethod = mappingBinding.getBaseMethods()[0]; |
| baseElement = OTStubUtility.createMethodSpec(cu, rewrite, imports, baseMethod, true); |
| } else { |
| RoleTypeDeclaration role = (RoleTypeDeclaration) mapping.getParent(); |
| ITypeBinding[] roleParameters = roleMethod.getParameterTypes(); |
| MethodSpec oldBaseSpec = (MethodSpec) mapping.getBaseMappingElement(); |
| // search matching base methods: |
| guessBaseMethod(role.resolveBinding(), oldBaseSpec.getName().getIdentifier(), roleParameters, false, matchingBaseMethods); |
| if (matchingBaseMethods.size() == 0) |
| return; |
| baseElement = OTStubUtility.createMethodSpec(cu, rewrite, imports, matchingBaseMethods.get(0), true); |
| } |
| } |
| rewrite.set(mapping, CalloutMappingDeclaration.BASE_MAPPING_ELEMENT_PROPERTY, baseElement, editGroup); |
| // do we have alternatives to propose? |
| if (matchingBaseMethods.size() > 1) { |
| addLinkedPosition(rewrite.track(baseElement), false, KEY_BASEMETHOD); |
| for(IMethodBinding baseMethodBinding : matchingBaseMethods) { |
| MethodSpec bSpec = OTStubUtility.createMethodSpec(cu, rewrite, imports, baseMethodBinding, true); |
| addLinkedPositionProposal(KEY_BASEMETHOD, bSpec.toString(), null); |
| } |
| } |
| } catch (CoreException e) { |
| OTDTUIPlugin.log(e); |
| } |
| } |
| |
| private void guessBaseMethod(ITypeBinding roleType, String selector, ITypeBinding[] roleParameters, boolean isCallin, List<IMethodBinding> result) |
| { |
| Set<String> foundSignatures = new HashSet<String>(); |
| ITypeBinding baseClass = roleType.getBaseClass(); |
| while (baseClass != null) { |
| baseMethods: |
| for (IMethodBinding baseMethod : baseClass.getDeclaredMethods()) { |
| if (!baseMethod.getName().equals(selector)) |
| continue; |
| ITypeBinding[] baseParameters = baseMethod.getParameterTypes(); |
| String signature = makeSignatureKey(baseParameters); |
| if (!foundSignatures.add(signature)) |
| continue; |
| int provided = isCallin ? baseParameters.length : roleParameters.length; |
| int consumed = isCallin ? roleParameters.length : baseParameters.length; |
| if (provided < consumed) |
| continue; |
| for(int i=0; i<consumed; i++) |
| if (!isCompatible(roleParameters[i], baseParameters[i], isCallin)) |
| continue baseMethods; |
| if (baseParameters.length == roleParameters.length) |
| result.add(0, baseMethod); // same number of parameters is considered our best guess |
| else |
| result.add(baseMethod); |
| } |
| baseClass = baseClass.getSuperclass(); |
| } |
| } |
| |
| // simple string repr of a method signature, just for use as a key in a set. |
| private String makeSignatureKey(ITypeBinding[] baseParameters) { |
| StringBuffer buf = new StringBuffer(); |
| for (int i=0; i<baseParameters.length; i++) { |
| if (i > 0) |
| buf.append(','); |
| buf.append(baseParameters[i].getQualifiedName()); |
| } |
| return buf.toString(); |
| } |
| |
| private boolean isCompatible(ITypeBinding roleSideType, ITypeBinding baseSideType, boolean isCallin) { |
| ITypeBinding requiredType = isCallin ? roleSideType : baseSideType; |
| ITypeBinding providedType = isCallin ? baseSideType : roleSideType; |
| |
| if (providedType.isAssignmentCompatible(requiredType)) |
| return true; |
| |
| if (isCallin) { |
| requiredType = requiredType.getBaseClass(); // anticipate lifting |
| if (requiredType == null) |
| return false; |
| } else { |
| providedType = providedType.getBaseClass(); // use lowering |
| if (providedType == null) |
| return false; |
| } |
| if (providedType.isAssignmentCompatible(requiredType)) |
| return true; |
| |
| return false; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private void convertTypeParameters(final AST ast, IMethodBinding roleMethod, MethodSpec destMethodSpec) |
| { |
| for (ITypeBinding typeParameter : roleMethod.getTypeParameters()) { |
| TypeParameter newTypeParameter = ast.newTypeParameter(); |
| newTypeParameter.setName(ast.newSimpleName(typeParameter.getName())); |
| for (ITypeBinding typeBound : typeParameter.getTypeBounds()) |
| newTypeParameter.typeBounds().add(convertType(ast, typeBound)); |
| destMethodSpec.typeParameters().add(newTypeParameter); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Type convertType(AST ast, ITypeBinding typeBinding) { |
| if (typeBinding.isTypeVariable()) |
| return ast.newSimpleType(ast.newSimpleName(typeBinding.getName())); |
| String typeName = typeBinding.getErasure().getQualifiedName(); |
| Type type = (typeName.indexOf('.') > -1) |
| ? ast.newSimpleType(ast.newName(typeName)) |
| : ast.newSimpleType(ast.newSimpleName(typeName)); |
| ITypeBinding[] typeArguments = typeBinding.getTypeArguments(); |
| if (typeArguments.length > 0) { |
| ParameterizedType parameterizedType = ast.newParameterizedType(type); |
| for (ITypeBinding bound : typeArguments) |
| parameterizedType.typeArguments().add(convertType(ast, bound)); |
| return parameterizedType; |
| } |
| return type; |
| } |
| } |