blob: 009015958c2b703c479ff90cec538a0fb047f76d [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Development Tooling"-Software
*
* Copyright 2007 Technical University Berlin, Germany.
*
* 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
* $Id: TypeProposalSubProcessor.java 23438 2010-02-04 20:05:24Z stephan $
*
* Please visit http://www.eclipse.org/objectteams for updates and contact.
*
* Contributors:
* Technical University Berlin - Initial API and implementation
**********************************************************************/
package org.eclipse.objectteams.otdt.internal.ui.text.correction;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.core.ICompilationUnit;
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.Block;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeAnchor;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
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.internal.core.manipulation.StubUtility;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.corext.util.QualifiedTypeNameHistory;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.text.correction.proposals.AddImportCorrectionProposal;
import org.eclipse.jdt.internal.ui.text.correction.proposals.LinkedCorrectionProposal;
import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jdt.ui.text.java.correction.ASTRewriteCorrectionProposal;
import org.eclipse.jdt.ui.text.java.correction.CUCorrectionProposal;
import org.eclipse.jdt.ui.text.java.correction.ICommandAccess;
import org.eclipse.jface.text.IDocument;
import org.eclipse.objectteams.otdt.core.IOTType;
import org.eclipse.objectteams.otdt.core.IRoleType;
import org.eclipse.objectteams.otdt.core.OTModelManager;
import org.eclipse.swt.graphics.Image;
/**
* Process quick fixes related to types.
*
* @author stephan
*/
@SuppressWarnings("restriction")
public class TypeProposalSubProcessor {
private static final String ANCHOR_GROUP_ID = "AnchorNameID"; //$NON-NLS-1$
/**
* Change a simple type role reference to a an anchored type reference.
* Infer possible anchors from the context.
*
* @param cu where everything happens
* @param fullName the qualified name of a role type
* @param type the node specifying the role type (to be replaced)
* @param enclosingTeamName the team containing the role type to change.
*
* all parameters: non-null!
* @return the proposal (always)
*/
@SuppressWarnings("unchecked")
public static LinkedCorrectionProposal changeTypeToAnchored(ICompilationUnit cu,
String fullName,
Name type,
String enclosingTeamName)
{
AST ast = type.getAST();
ASTRewrite rewrite= ASTRewrite.create(ast);
// search candidates for an anchor:
String[] variables = matchingVariables(type, enclosingTeamName);
String firstAnchor = (variables.length>0) ? variables[0] : "aTeam"; //$NON-NLS-1$
// construct and replace 'Type<@anchor>'
SimpleName anchorName = (SimpleName)rewrite.createStringPlaceholder(firstAnchor, ASTNode.SIMPLE_NAME);
TypeAnchor anchor = ast.newTypeAnchor(anchorName);
Name newTypeName= type;
if (type.isQualifiedName())
newTypeName= ((QualifiedName)type).getName();
newTypeName= (Name)ASTNode.copySubtree(ast, newTypeName);
ParameterizedType newType = ast.newParameterizedType(ast.newSimpleType(newTypeName));
newType.typeArguments().add(anchor);
rewrite.replace(type, newType, null);
// assemble a proposal:
Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
int relevance = 13; // FIXME
String label = CorrectionMessages.OTQuickFix_Type_change_type_to_anchored;
LinkedCorrectionProposal proposal = new LinkedCorrectionProposal(label, cu, rewrite, relevance, image);
// setup linked mode:
proposal.addLinkedPosition(rewrite.track(anchorName), true, ANCHOR_GROUP_ID);
if (variables.length > 1) // should we present choices?
for (int i=0; i<variables.length; i++)
proposal.addLinkedPositionProposal(ANCHOR_GROUP_ID, variables[i], null);
return proposal;
}
/** Find the names of all variables in scope that match typeToMatch. */
static private String[] matchingVariables (ASTNode node, String enclosingTeamName) {
ArrayList<String> list = new ArrayList<String>();
ASTNode previous = null;
while (node != null && node.getNodeType() != ASTNode.COMPILATION_UNIT) {
switch (node.getNodeType()) {
case ASTNode.BLOCK:
list.addAll(getMatchingLocalsPriorTo((Block)node, enclosingTeamName, previous));
break;
case ASTNode.METHOD_DECLARATION:
for (Object fieldObj : ((MethodDeclaration)node).parameters()) {
SingleVariableDeclaration param= (SingleVariableDeclaration)fieldObj;
addSingleVarIfMatch(param.getType(), enclosingTeamName, param, list);
}
break;
case ASTNode.ROLE_TYPE_DECLARATION:
case ASTNode.TYPE_DECLARATION:
for (FieldDeclaration field : ((TypeDeclaration)node).getFields())
addFragmentsIfMatch(field.getType(), enclosingTeamName, field.fragments(), list);
break;
}
previous = node;
node = node.getParent();
}
return list.toArray(new String[list.size()]);
}
/**
* Find all local variables within block prior to current that match typeToMatch.
* Return the variables' names.
*/
@SuppressWarnings("rawtypes") // block.statements() is raw type
private static ArrayList<String> getMatchingLocalsPriorTo(Block block,
String enclosingTeamName,
ASTNode current)
{
ArrayList<String> result = new ArrayList<String>();
if (current != null) {
List statements = block.statements();
for (int i=0; i<statements.size(); i++) {
Object statement = statements.get(i);
if (statement instanceof VariableDeclarationStatement) {
VariableDeclarationStatement var = (VariableDeclarationStatement)statement;
addFragmentsIfMatch(var.getType(), enclosingTeamName, var.fragments(), result);
}
if (statement == current)
break;
}
}
return result;
}
/** If typeNode matches typeToMatch add the names of all fragments to result. */
@SuppressWarnings("rawtypes")
private static void addFragmentsIfMatch(Type typeNode, String enclosingTeamName, List fragments, List<String> result)
{
if ( typeNode.isSimpleType()
&& nameEquals(((SimpleType)typeNode).getName(), enclosingTeamName))
{
for (int i=0; i<fragments.size(); i++)
result.add(((VariableDeclarationFragment)fragments.get(i)).getName().getIdentifier());
}
}
private static void addSingleVarIfMatch(Type typeNode, String enclosingTeamName, SingleVariableDeclaration var, List<String> result)
{
if ( typeNode.isSimpleType()
&& nameEquals(((SimpleType)typeNode).getName(), enclosingTeamName))
{
result.add(var.getName().getIdentifier());
}
}
/** Does name match the type to match, either by simple or by qualified name? */
private static boolean nameEquals(Name name, String enclosingTeamName) {
String nameString = name.getFullyQualifiedName();
return enclosingTeamName.equals(nameString)
|| enclosingTeamName.endsWith("."+nameString); //$NON-NLS-1$
}
/**
* Proposal for adding an "import base" statement to the current role file's enclosing team.
* @param cu where everything happens
* @param fullName name of type to import
* @param node node requiring the import
* @param relevance relevance
* @param maxProposals how many proposals should be accepted?
* @return the proposal or null
*/
public static CUCorrectionProposal createImportInRoFisTeamProposal(ICompilationUnit cu, String fullName, Name node, int relevance, int maxProposals)
{
// get the new CU:
try {
IType[] types = cu.getTypes();
IOTType otType = OTModelManager.getOTElement(types[0]);
if (otType == null || !otType.isRole())
return null; // cu is not a rofi
IType teamType = ((IRoleType)otType).getTeamJavaType();
cu = teamType.getCompilationUnit();
if (cu == null)
return null;
} catch (JavaModelException e) {
return null;
}
// get a fresh AST:
ASTParser p = ASTParser.newParser(AST.JLS8);
p.setSource(cu);
p.setResolveBindings(false);
CompilationUnit astCU = (CompilationUnit) p.createAST(null);
for (Object anImport : astCU.imports())
if (((ImportDeclaration)anImport).getName().getFullyQualifiedName().equals(fullName))
return null; // no change needed
// create the proposal -- starting from here code is inspired by
// org.eclipse.jdt.internal.ui.text.correction.UnresolvedElementsSubProcessor.createTypeRefChangeProposal(ICompilationUnit, String, Name, int, int)
ImportRewrite importRewrite= null;
String simpleName= fullName;
String packName= Signature.getQualifier(fullName);
if (packName.length() > 0) { // no imports for primitive types, type variables
importRewrite= StubUtility.createImportRewrite(astCU, true); // OT-modified(SH)
simpleName= importRewrite.addImport(fullName);
}
if (!isLikelyTypeName(simpleName)) {
relevance -= 2;
}
ASTRewriteCorrectionProposal proposal = null;
SimpleName simpleNameNode =
node.isQualifiedName()
? ((QualifiedName)node).getName()
: (SimpleName)node;
if (importRewrite != null && simpleName.equals(simpleNameNode.getIdentifier())) { // import only
// import only
String[] arg= { BasicElementLabels.getJavaElementName(simpleName), BasicElementLabels.getJavaElementName(packName) };
String label= Messages.format(CorrectionMessages.OTQuickFix_Type_add_base_import_to_enclosing_team, arg);
Image image= JavaPluginImages.get(JavaPluginImages.IMG_OBJS_IMPDECL);
int boost= QualifiedTypeNameHistory.getBoost(fullName, 0, maxProposals);
proposal= new AddImportCorrectionProposal(label, cu, relevance + 100 + boost, image, packName, simpleName, simpleNameNode);
proposal.setCommandId(ADD_IMPORT_ID);
proposal.setImportRewrite(importRewrite);
}
// "else" dropped for OT
return proposal;
}
private static final String ADD_IMPORT_ID= "org.eclipse.jdt.ui.correction.addImport"; //$NON-NLS-1$
private static boolean isLikelyTypeName(String name) {
return name.length() > 0 && Character.isUpperCase(name.charAt(0));
}
/**
* handle IProblem.RegularlyImportedBaseclass:
* "change import to "import base ...";
* OR (if cu is a role file):
* add a new "import base" to the enclosing team.
*
* @param selectedNode reference to the type being imported
* @param cu where everything happens
* @return the proposal or null
* @throws JavaModelException exception in one of the various model operations
*/
public static ICommandAccess getMakeImportBase(ASTNode selectedNode, ICompilationUnit cu)
throws JavaModelException
{
if (selectedNode.getNodeType() != ASTNode.SIMPLE_NAME)
return null;
SimpleName name = (SimpleName)selectedNode;
ITypeBinding type = name.resolveTypeBinding();
if (type == null)
return null;
int relevance = 1000; // TODO(SH): compute
IType[] types = cu.getTypes();
for (IType toplevelType : types) {
IOTType ottype = OTModelManager.getOTElement(toplevelType);
if (ottype != null && ottype.isRole())
// no import base in role file, create a new one in the team instead:
return createImportInRoFisTeamProposal(cu, type.getQualifiedName(), name, relevance, 1);
}
ImportRewrite importRewrite = StubUtility.createImportRewrite(cu, true);
if (!importRewrite.setImportBase(type))
return null; // failure
String simpleName = type.getName();
String pack1 = type.getPackage().getNameComponents()[0];
String displayName = pack1+"..."+simpleName; //$NON-NLS-1$
ASTRewriteCorrectionProposal rewriteProposal =
new ASTRewriteCorrectionProposal(
Messages.format(CorrectionMessages.OTQuickFix_Type_convertimporttobase_description,
new String[] {displayName}),
cu,
ASTRewrite.create(selectedNode.getAST()),
relevance,
JavaPluginImages.get(JavaPluginImages.IMG_OBJS_IMPDECL));
rewriteProposal.setImportRewrite(importRewrite);
return rewriteProposal;
}
/**
* Import a base class so that a qualified reference can be replaced by a simple one.
*
* @param selectedNode the qualified reference
* @param cu where everything happens
* @return the proposal or null
* @throws JavaModelException exception in one of the various model operations
*/
public static ICommandAccess getImportBaseclass(ASTNode selectedNode, ICompilationUnit cu) throws JavaModelException
{
if (selectedNode.getNodeType() != ASTNode.QUALIFIED_NAME)
return null;
QualifiedName typeRef = (QualifiedName)selectedNode;
ITypeBinding type = typeRef.resolveTypeBinding();
if (type == null)
return null;
int relevance = 1000; // TODO(SH): compute
IType[] types = cu.getTypes();
boolean isRoleFile = false;
for (IType toplevelType : types) {
IOTType ottype = OTModelManager.getOTElement(toplevelType);
if (ottype != null && ottype.isRole()) {
isRoleFile = true;
break;
}
}
// no import base in role file, create a new one in the team instead?
final IJavaCompletionProposal importProposal = isRoleFile
? createImportInRoFisTeamProposal(cu, type.getQualifiedName(), typeRef, relevance, 1)
: null;
// create the base import
ImportRewrite importRewrite = StubUtility.createImportRewrite(cu, true);
if (!isRoleFile) {
importRewrite.addImport(type);
if (!importRewrite.setImportBase(type))
return null; // failure
}
AST ast = selectedNode.getAST();
ASTRewrite rewrite = ASTRewrite.create(ast);
// replace the type reference with a simple name:
Name simpleTypeRef = ast.newName(typeRef.getName().getIdentifier());
rewrite.replace(selectedNode, simpleTypeRef, null);
// assemble the proposal:
String simpleName = type.getName();
String pack1 = type.getPackage().getNameComponents()[0];
String displayName = pack1+"..."+simpleName; //$NON-NLS-1$
ASTRewriteCorrectionProposal rewriteProposal =
new ASTRewriteCorrectionProposal(
Messages.format(CorrectionMessages.OTQuickFix_Type_convert_fqn_to_importtobase_description,
new String[] {displayName}),
cu,
rewrite,
relevance,
JavaPluginImages.get(JavaPluginImages.IMG_OBJS_IMPDECL))
{
@Override
public void apply(IDocument document) {
super.apply(document);
// propagate to the import proposal which refers to a different CU (that's why be build two separate proposals):
if (importProposal != null)
importProposal.apply(document);
}
};
rewriteProposal.setImportRewrite(importRewrite);
return rewriteProposal;
}
}