| /********************************************************************** |
| * This file is part of "Object Teams Development Tooling"-Software |
| * |
| * Copyright 2006, 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: BaseImportRewriting.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.assist; |
| |
| |
| import java.util.HashSet; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; |
| import org.eclipse.jdt.core.dom.ArrayType; |
| import org.eclipse.jdt.core.dom.CompilationUnit; |
| import org.eclipse.jdt.core.dom.LiftingType; |
| 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.QualifiedType; |
| import org.eclipse.jdt.core.dom.RoleTypeDeclaration; |
| 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.TypeDeclaration; |
| import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext; |
| import org.eclipse.jdt.internal.compiler.ast.ASTNode; |
| import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.TypeReference; |
| import org.eclipse.jdt.internal.compiler.lookup.Binding; |
| import org.eclipse.jdt.internal.compiler.lookup.Scope; |
| import org.eclipse.objectteams.otdt.internal.corext.RoleFileAdaptor; |
| |
| import base org.eclipse.jdt.core.dom.rewrite.ImportRewrite; |
| import base org.eclipse.jdt.internal.codeassist.CompletionEngine; |
| import base org.eclipse.jdt.internal.codeassist.InternalCompletionProposal; |
| import base org.eclipse.jdt.internal.codeassist.complete.CompletionOnSingleTypeReference; |
| import base org.eclipse.jdt.internal.codeassist.complete.CompletionParser; |
| |
| /** |
| * This team advises the completion engine et al to produce base-imports if appropriate. |
| * It implements a DataRelayChain with the following stages: |
| * |
| * (1) CompletionParser |
| * (2) CompletionOnBaseTypeReference |
| * (3) CompletionEngine |
| * (4) LazyJavaTypeCompletionProposal (in CompletionAdaptor, since type is in a different plug-in). |
| * (5) ImportRewrite |
| * Field: needsBaseImport |
| * Set: passive: Field is externally written |
| * Eval: addEntry <- replace addEntry |
| * -> regular API ("goal") |
| * |
| * Additionally the OrganizeImportsAction is advised, too. Here the initial entry is {@link ImportRewrite#create(CompilationUnit,boolean)} |
| * |
| * @author stephan |
| */ |
| @SuppressWarnings("restriction") |
| public team class BaseImportRewriting |
| { |
| private static BaseImportRewriting instance; |
| public static BaseImportRewriting instance() { |
| return instance; |
| } |
| public BaseImportRewriting() { |
| instance = this; |
| } |
| |
| /** |
| * While parsing we mark a assist type reference right after <code>playedBy</code>. |
| * Marking is done by decorating with a {@link CompletionOnBaseTypeRef} role. |
| */ |
| protected class CompletionParser playedBy CompletionParser |
| { |
| /** Intermediate data storage #1: */ |
| boolean nextIsBaseclass = false; |
| |
| /** Initial entry (input) of this DataRelayChain. */ |
| callin void consumeClassHeaderPlayedBy() { |
| boolean flagSave = nextIsBaseclass; |
| nextIsBaseclass = true; |
| try { |
| base.consumeClassHeaderPlayedBy(); |
| } finally { |
| nextIsBaseclass = flagSave; |
| } |
| } |
| consumeClassHeaderPlayedBy <- replace consumeClassHeaderPlayedBy; |
| |
| /** Entry (output) conditionally passing data to next role. */ |
| void checkRecordBaseclassReference(CompletionOnBaseTypeRef ref) |
| <- after TypeReference createSingleAssistTypeReference(char[] assistName, long position) |
| base when (result instanceof CompletionOnSingleTypeReference) |
| with { ref <- (CompletionOnSingleTypeReference)result } |
| void checkRecordBaseclassReference(CompletionOnBaseTypeRef ref) { |
| if (!this.nextIsBaseclass) |
| // not within required context, cancel the role: |
| BaseImportRewriting.this.unregisterRole(ref, CompletionOnBaseTypeRef.class); |
| } |
| } |
| |
| /** |
| * This role is registered by lifting in {@link CompletionParser#checkRecordBaseclassReference(CompletionOnBaseTypeRef)}. |
| * Its presence marks a type reference as completion on base class. |
| */ |
| protected class CompletionOnBaseTypeRef playedBy CompletionOnSingleTypeReference |
| base when (BaseImportRewriting.this.hasRole(base, CompletionOnBaseTypeRef.class)) |
| { |
| /** adjust pretty printing for testing and debugging. */ |
| @SuppressWarnings("basecall") |
| callin StringBuffer printExpression(int indent, StringBuffer output){ |
| output.append("<CompleteOnBaseclass:");//$NON-NLS-1$ |
| return output.append(getToken()).append('>'); |
| } |
| printExpression <- replace printExpression; |
| |
| char[] getToken() -> char[] getLastToken(); |
| } |
| |
| @SuppressWarnings("decapsulation"/*final baseclass*/) |
| protected class CompletionEngine playedBy CompletionEngine |
| { |
| /** Intermediate data storage #2: */ |
| public boolean assistNodeIsBaseclass = false; |
| |
| /** Entry (input): transfer information from CompletionOnBaseTypeReference role to this role. */ |
| void markAsCompletingBaseclass(ASTNode astNode) { |
| this.assistNodeIsBaseclass = true; |
| } |
| void markAsCompletingBaseclass(ASTNode astNode) |
| <- before boolean complete(ASTNode astNode, ASTNode astNodeParent, ASTNode enclosingNode, CompilationUnitDeclaration compilationUnitDeclaration, Binding qualifiedBinding, Scope scope, boolean insideTypeAnnotation) |
| when (BaseImportRewriting.this.hasRole(astNode, CompletionOnBaseTypeRef.class)); |
| } |
| |
| @SuppressWarnings("decapsulation") |
| protected class ImportRewriteAdaptor playedBy ImportRewrite |
| { |
| /** triggered from OrganizeImportsOperation.run() we create instances of this role: */ |
| void markImportRewrite(ImportRewriteAdaptor rewrite, CompilationUnit astRoot) |
| <- after ImportRewrite create(CompilationUnit astRoot, boolean restoreExistingImports) |
| when (result != null) |
| with { rewrite <- result, astRoot <- astRoot } |
| |
| /** Remember all known base classes within the given CU as needing a base import. */ |
| @SuppressWarnings("unchecked") |
| static void markImportRewrite(ImportRewriteAdaptor rewrite, CompilationUnit astRoot) { |
| try { |
| List<AbstractTypeDeclaration> types = astRoot.types(); |
| if (types != null) |
| for (AbstractTypeDeclaration type : types) { |
| if (type.isRole()) |
| rewrite.cuIsRoFi = true; |
| if (type.isTeam() || type.isRole()) |
| rememberBasesForBaseImport(rewrite, (TypeDeclaration)type); |
| } |
| } catch (Exception e) { |
| JavaCore.getJavaCore().getLog().log(new Status(Status.ERROR, JavaCore.PLUGIN_ID, Status.OK, |
| "Error idendifying base classes for import.", e)); //$NON-NLS-1$ |
| } |
| } |
| |
| /** Remember all base classes referenced by 'theTeam' as needing base import. */ |
| static void rememberBasesForBaseImport(ImportRewriteAdaptor rewrite, TypeDeclaration type) { |
| try { |
| // descend into AST searching for in-line role types: |
| if (type instanceof RoleTypeDeclaration) { |
| Type baseClassType = ((RoleTypeDeclaration)type).getBaseClassType(); |
| if (baseClassType != null) { |
| SimpleName baseName = getSimpleTypeName(baseClassType); |
| if (baseName != null) |
| rewrite.baseNames.add(baseName.getIdentifier()); |
| } |
| } |
| for (Object decl : type.bodyDeclarations()) |
| if (decl instanceof RoleTypeDeclaration) |
| rememberBasesForBaseImport(rewrite, (TypeDeclaration)decl); // recurse |
| else if (type.isTeam() && decl instanceof MethodDeclaration) |
| rememberBasesForBaseImport(rewrite, (MethodDeclaration)decl); // search for declared lifting |
| |
| // also fetch role files: |
| org.eclipse.jdt.core.dom.ASTNode enclosing = type.getParent(); |
| if (enclosing instanceof CompilationUnit) |
| rewrite.baseNames.addAll(RoleFileAdaptor.getRoFiBaseClassNames((CompilationUnit)enclosing)); |
| } catch (Exception javaModelException){ |
| // nop |
| } |
| } |
| |
| static void rememberBasesForBaseImport(ImportRewriteAdaptor rewrite, MethodDeclaration methodDecl) { |
| for (Object param : methodDecl.parameters()) { |
| if (param instanceof SingleVariableDeclaration) { |
| Type type = ((SingleVariableDeclaration) param).getType(); |
| if (type instanceof LiftingType) { |
| SimpleName baseName = getSimpleTypeName(((LiftingType) type).getBaseType()); |
| if (baseName != null) |
| rewrite.baseNames.add(baseName.getIdentifier()); |
| } |
| } |
| } |
| } |
| |
| static SimpleName getSimpleTypeName(Type type) { |
| if (type instanceof ParameterizedType) |
| type = ((ParameterizedType) type).getType(); |
| if (type instanceof ArrayType) |
| type = ((ArrayType) type).getElementType(); |
| if (type.isSimpleType()) { |
| Name name = ((SimpleType) type).getName(); |
| if (name.isSimpleName()) |
| return (SimpleName)name; |
| else |
| return ((QualifiedName)name).getName(); |
| } else if (type.isQualifiedType()) { |
| return ((QualifiedType) type).getName(); |
| } |
| return null; |
| } |
| |
| // === Instance level methods and method bindings: |
| |
| /** Intermediate data storage #3 (passive, externally written from #checkForBaseImport()). */ |
| public boolean needsBaseImport = false; |
| /** Alternative intermediate storage: the stored names should be treated as base classes. */ |
| public HashSet<String> baseNames = new HashSet<String>(); |
| /** Is the current CU a role file? */ |
| boolean cuIsRoFi = false; |
| |
| /** Final entry (output): conditionally change import entry from NORMAL to BASE. */ |
| @SuppressWarnings("basecall") |
| callin void adaptEntry1(String entry) { |
| String adapted = doAdaptEntry(entry); |
| if (adapted != null) |
| base.adaptEntry1(adapted); |
| } |
| /** Final entry (output): conditionally change import entry from NORMAL to BASE. */ |
| @SuppressWarnings("basecall") |
| callin boolean adaptEntry2(String entry) { |
| String adapted = doAdaptEntry(entry); |
| if (adapted != null) |
| return base.adaptEntry2(adapted); |
| return false; |
| } |
| adaptEntry1 <- replace addEntry |
| base when (hasRole(base, ImportRewriteAdaptor.class)); |
| adaptEntry2 <- replace removeEntry |
| base when (hasRole(base, ImportRewriteAdaptor.class)); |
| |
| String doAdaptEntry (String entry) { |
| if (entry.charAt(0) == getNormalChar()) { |
| String[] names = entry.split("\\."); //$NON-NLS-1$ |
| if (needsBaseImport || baseNames.contains(names[names.length-1])) { |
| if (cuIsRoFi) |
| return null; // don't propose to add a base import to a role file |
| else |
| entry = getBaseChar()+entry.substring(1); |
| } |
| } |
| return entry; |
| } |
| |
| // this callin tells the ImportRewrite not to consider local declarations as a conflict, |
| // if we already know that we need a base import |
| // => fixes completing a base class with the same name as its role. |
| callin String internalAddImport(String fullTypeName, ImportRewriteContext context) |
| { |
| if (needsBaseImport) |
| context= new ImportRewriteContext() { |
| @Override |
| public int findInContext(String qualifier, String name, int kind) { |
| return getDefaultImportRewriteContext().findInContext(qualifier, name, kind); |
| // explicitly don't look in declarations! |
| } |
| }; |
| return base.internalAddImport(fullTypeName, context); |
| } |
| String internalAddImport(String fullTypeName, ImportRewriteContext context) |
| <- replace String internalAddImport(String fullTypeName, ImportRewriteContext context); |
| |
| callin void markForBaseImport() { |
| try { |
| this.needsBaseImport = true; |
| base.markForBaseImport(); |
| } finally { |
| BaseImportRewriting.this.unregisterRole(this, ImportRewriteAdaptor.class); |
| } |
| } |
| markForBaseImport <- replace addImportBase; |
| |
| ImportRewriteContext getDefaultImportRewriteContext() -> ImportRewriteContext getDefaultImportRewriteContext(); |
| |
| // callout to constants: |
| char getNormalChar() -> get char NORMAL_PREFIX; |
| char getBaseChar() -> get char BASE_PREFIX; |
| } |
| |
| /** Passive role: provide access to one field. */ |
| protected class CompletionProposal playedBy InternalCompletionProposal { |
| @SuppressWarnings("decapsulation") |
| protected |
| CompletionEngine getEngine() -> get CompletionEngine completionEngine; |
| } |
| |
| /** API for use by LazyJavaTypeCompletionProposal: propagate data from a {@link CompletionEngine} to an {@link ImportRewriteAdaptor}. |
| * @param cp completion proposal being processed |
| * @param rewrite import rewrite that may have to add the "base" modifier. |
| */ |
| public void checkForBaseImport(InternalCompletionProposal as CompletionProposal cp, |
| ImportRewrite as ImportRewriteAdaptor rewrite) |
| { |
| if (cp == null) |
| return; |
| CompletionEngine engine = cp.getEngine(); |
| if (engine != null && rewrite != null) |
| rewrite.needsBaseImport = engine.assistNodeIsBaseclass; |
| } |
| |
| /** |
| * API for org.eclipse.objectteams.otdt.internal.refactoring.adaptor.BaseImporting: |
| * Mark the given simple name as requiring a base import. |
| * @param imports the rewrite in use |
| * @param baseName the simple name of a base class. |
| */ |
| public void markForBaseImport(ImportRewrite as ImportRewriteAdaptor imports, String baseName) { |
| if (imports.baseNames == null) |
| imports.baseNames = new HashSet<String>(); |
| imports.baseNames.add(baseName); |
| } |
| |
| } |