diff options
Diffstat (limited to 'src')
4 files changed, 550 insertions, 515 deletions
diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java index 5d24d81e..c4889bb6 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -26,9 +26,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import org.eclipse.jdt.internal.compiler.env.AccessRestriction; -import org.eclipse.jdt.internal.compiler.lookup.ClassScope; - import lombok.core.Agent; import lombok.patcher.Hook; import lombok.patcher.MethodTarget; @@ -260,94 +257,7 @@ public class EclipsePatcher extends Agent { } private static void patchEcjTransformers(ScriptManager sm, boolean ecj) { - patchDelegate(sm); - patchHandleVal(sm, ecj); - } - - private static void patchDelegate(ScriptManager sm) { - final String TYPEDECLARATION_SIG = "org.eclipse.jdt.internal.compiler.ast.TypeDeclaration"; - -// sm.addScript(ScriptBuilder.exitEarly() -// .target(new MethodTarget(TYPEDECLARATION_SIG, "resolve", "void")) -// .request(StackRequest.THIS) -// .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "handleDelegateForType", "boolean", TYPEDECLARATION_SIG)) -// .build()); - - final String CLASSSCOPE_SIG = "org.eclipse.jdt.internal.compiler.lookup.ClassScope"; - - sm.addScript(ScriptBuilder.exitEarly() - .target(new MethodTarget(CLASSSCOPE_SIG, "buildFieldsAndMethods", "void")) - .request(StackRequest.THIS) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "handleDelegateForType2", "boolean", CLASSSCOPE_SIG)) - .build()); - } - - // Creates a copy of the 'initialization' field on a LocalDeclaration if the type of the LocalDeclaration is 'val', because the completion parser will null this out, - // which in turn stops us from inferring the intended type for 'val x = 5;'. We look at the copy. - // Also patches local declaration to not call .resolveType() on the initializer expression if we've already done so (calling it twice causes weird errors), - // and patches .resolve() on LocalDeclaration itself to just-in-time replace the 'val' vartype with the right one. - - private static void patchHandleVal(ScriptManager sm, boolean ecj) { - final String LOCALDECLARATION_SIG = "org.eclipse.jdt.internal.compiler.ast.LocalDeclaration"; - final String FOREACHSTATEMENT_SIG = "org.eclipse.jdt.internal.compiler.ast.ForeachStatement"; - final String EXPRESSION_SIG = "org.eclipse.jdt.internal.compiler.ast.Expression"; - final String BLOCKSCOPE_SIG = "org.eclipse.jdt.internal.compiler.lookup.BlockScope"; - final String PARSER_SIG = "org.eclipse.jdt.internal.compiler.parser.Parser"; - final String TYPEBINDING_SIG = "org.eclipse.jdt.internal.compiler.lookup.TypeBinding"; - - sm.addScript(ScriptBuilder.exitEarly() - .target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG)) - .request(StackRequest.THIS, StackRequest.PARAM1) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "handleValForLocalDeclaration", "boolean", LOCALDECLARATION_SIG, BLOCKSCOPE_SIG)) - .build()); - - sm.addScript(ScriptBuilder.replaceMethodCall() - .target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG)) - .methodToReplace(new Hook(EXPRESSION_SIG, "resolveType", TYPEBINDING_SIG, BLOCKSCOPE_SIG)) - .requestExtra(StackRequest.THIS) - .replacementMethod(new Hook("lombok.eclipse.agent.PatchFixes", "skipResolveInitializerIfAlreadyCalled2", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG, LOCALDECLARATION_SIG)) - .build()); - - sm.addScript(ScriptBuilder.exitEarly() - .target(new MethodTarget(FOREACHSTATEMENT_SIG, "resolve", "void", BLOCKSCOPE_SIG)) - .request(StackRequest.THIS, StackRequest.PARAM1) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "handleValForForEach", "boolean", FOREACHSTATEMENT_SIG, BLOCKSCOPE_SIG)) - .build()); - - sm.addScript(ScriptBuilder.replaceMethodCall() - .target(new MethodTarget(FOREACHSTATEMENT_SIG, "resolve", "void", BLOCKSCOPE_SIG)) - .methodToReplace(new Hook(EXPRESSION_SIG, "resolveType", TYPEBINDING_SIG, BLOCKSCOPE_SIG)) - .replacementMethod(new Hook("lombok.eclipse.agent.PatchFixes", "skipResolveInitializerIfAlreadyCalled", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG)) - .build()); - - if (!ecj) { - sm.addScript(ScriptBuilder.addField() - .fieldName("$initCopy") - .fieldType("Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;") - .setPublic() - .setTransient() - .targetClass("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration") - .build()); - - sm.addScript(ScriptBuilder.addField() - .fieldName("$iterableCopy") - .fieldType("Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;") - .setPublic() - .setTransient() - .targetClass("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration") - .build()); - - sm.addScript(ScriptBuilder.wrapReturnValue() - .target(new MethodTarget(PARSER_SIG, "consumeExitVariableWithInitialization", "void")) - .request(StackRequest.THIS) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "copyInitializationOfLocalDeclarationForVal", "void", PARSER_SIG)) - .build()); - - sm.addScript(ScriptBuilder.wrapReturnValue() - .target(new MethodTarget(PARSER_SIG, "consumeEnhancedForStatementHeader", "void")) - .request(StackRequest.THIS) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "copyInitializationOfForEachIterable", "void", PARSER_SIG)) - .build()); - } + PatchDelegate.addPatches(sm, ecj); + PatchVal.addPatches(sm, ecj); } } diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java new file mode 100644 index 00000000..fb18958a --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java @@ -0,0 +1,268 @@ +package lombok.eclipse.agent; + +import static lombok.eclipse.Eclipse.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.internal.compiler.CompilationResult; +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; +import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess; +import org.eclipse.jdt.internal.compiler.ast.Clinit; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; +import org.eclipse.jdt.internal.compiler.lookup.ClassScope; +import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; +import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; +import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; + +import lombok.eclipse.Eclipse; +import lombok.eclipse.handlers.EclipseHandlerUtil; +import lombok.patcher.Hook; +import lombok.patcher.MethodTarget; +import lombok.patcher.ScriptManager; +import lombok.patcher.StackRequest; +import lombok.patcher.scripts.ScriptBuilder; + +public class PatchDelegate { + static void addPatches(ScriptManager sm, boolean ecj) { + final String CLASSSCOPE_SIG = "org.eclipse.jdt.internal.compiler.lookup.ClassScope"; + + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget(CLASSSCOPE_SIG, "buildFieldsAndMethods", "void")) + .request(StackRequest.THIS) + .decisionMethod(new Hook(PatchDelegate.class.getName(), "handleDelegateForType", "boolean", CLASSSCOPE_SIG)) + .build()); + } + + public static boolean handleDelegateForType(ClassScope scope) { + TypeDeclaration decl = scope.referenceContext; + if (decl == null) return false; + + if (decl.fields != null) for (FieldDeclaration field : decl.fields) { + if (field.annotations == null) continue; + for (Annotation ann : field.annotations) { + if (ann.type == null) continue; + TypeBinding tb = ann.type.resolveType(decl.initializerScope); + if (!charArrayEquals("lombok", tb.qualifiedPackageName())) continue; + if (!charArrayEquals("Delegate", tb.qualifiedSourceName())) continue; + + List<ClassLiteralAccess> rawTypes = new ArrayList<ClassLiteralAccess>(); + for (MemberValuePair pair : ann.memberValuePairs()) { + if (pair.name == null || charArrayEquals("value", pair.name)) { + if (pair.value instanceof ArrayInitializer) { + for (Expression expr : ((ArrayInitializer)pair.value).expressions) { + if (expr instanceof ClassLiteralAccess) rawTypes.add((ClassLiteralAccess) expr); + } + } + if (pair.value instanceof ClassLiteralAccess) { + rawTypes.add((ClassLiteralAccess) pair.value); + } + } + } + + List<MethodBinding> methodsToDelegate = new ArrayList<MethodBinding>(); + + if (rawTypes.isEmpty()) { + addAllMethodBindings(methodsToDelegate, field.type.resolveType(decl.initializerScope)); + } else { + for (ClassLiteralAccess cla : rawTypes) { + addAllMethodBindings(methodsToDelegate, cla.type.resolveType(decl.initializerScope)); + } + } + + generateDelegateMethods(decl, methodsToDelegate, field.name, ann); + } + } + + return false; + } + + private static void generateDelegateMethods(TypeDeclaration type, List<MethodBinding> methods, char[] delegate, ASTNode source) { + for (MethodBinding binding : methods) { + MethodDeclaration method = generateDelegateMethod(delegate, binding, type.compilationResult, source); + if (type.methods == null) { + type.methods = new AbstractMethodDeclaration[1]; + type.methods[0] = method; + } else { + int insertionPoint; + for (insertionPoint = 0; insertionPoint < type.methods.length; insertionPoint++) { + AbstractMethodDeclaration current = type.methods[insertionPoint]; + if (current instanceof Clinit) continue; + if (Eclipse.isGenerated(current)) continue; + break; + } + AbstractMethodDeclaration[] newArray = new AbstractMethodDeclaration[type.methods.length + 1]; + System.arraycopy(type.methods, 0, newArray, 0, insertionPoint); + if (insertionPoint <= type.methods.length) { + System.arraycopy(type.methods, insertionPoint, newArray, insertionPoint + 1, type.methods.length - insertionPoint); + } + + newArray[insertionPoint] = method; + type.methods = newArray; + } + } + } + + private static MethodDeclaration generateDelegateMethod(char[] name, MethodBinding binding, CompilationResult compilationResult, ASTNode source) { + MethodDeclaration method = new MethodDeclaration(compilationResult); + Eclipse.setGeneratedBy(method, source); + method.modifiers = ClassFileConstants.AccPublic; + method.returnType = Eclipse.makeType(binding.returnType, source, false); + method.annotations = EclipseHandlerUtil.createSuppressWarningsAll(source, null); + if (binding.parameters != null && binding.parameters.length > 0) { + method.arguments = new Argument[binding.parameters.length]; + for (int i = 0; i < method.arguments.length; i++) { + String argName = "$p" + i; + method.arguments[i] = new Argument( + argName.toCharArray(), pos(source), + Eclipse.makeType(binding.parameters[i], source, false), + ClassFileConstants.AccFinal); + } + } + method.selector = binding.selector; + if (binding.thrownExceptions != null && binding.thrownExceptions.length > 0) { + method.thrownExceptions = new TypeReference[binding.thrownExceptions.length]; + for (int i = 0; i < method.thrownExceptions.length; i++) { + method.thrownExceptions[i] = Eclipse.makeType(binding.thrownExceptions[i], source, false); + } + } + + method.typeParameters = null; // TODO think about this + method.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + FieldReference fieldRef = new FieldReference(name, pos(source)); + fieldRef.receiver = new ThisReference(source.sourceStart, source.sourceEnd); + MessageSend call = new MessageSend(); + call.receiver = fieldRef; + call.selector = binding.selector; + if (method.arguments != null) { + call.arguments = new Expression[method.arguments.length]; + for (int i = 0; i < method.arguments.length; i++) { + call.arguments[i] = new SingleNameReference(("$p" + i).toCharArray(), pos(source)); + } + } + + Statement body; + if (method.returnType instanceof SingleTypeReference && ((SingleTypeReference)method.returnType).token == TypeConstants.VOID) { + body = call; + } else { + body = new ReturnStatement(call, source.sourceStart, source.sourceEnd); + } + + method.statements = new Statement[] {body}; + // TODO add Eclipse.setGeneratedBy everywhere. + method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart; + method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd; + return method; + } + + private static void addAllMethodBindings(List<MethodBinding> list, TypeBinding binding) { + List<String> ban = new ArrayList<String>(); + ban.addAll(METHODS_IN_OBJECT); + addAllMethodBindings(list, binding, ban); + } + + private static void addAllMethodBindings(List<MethodBinding> list, TypeBinding binding, List<String> banList) { + if (binding == null) return; + if (binding instanceof ReferenceBinding) { + ReferenceBinding rb = (ReferenceBinding) binding; + for (MethodBinding mb : rb.availableMethods()) { + if (mb.isStatic()) continue; + if (mb.isBridge()) continue; + if (mb.isConstructor()) continue; + if (mb.isDefaultAbstract()) continue; + if (!mb.isPublic()) continue; + if (mb.isSynthetic()) continue; + if (mb.isFinal()) { + banList.add(printSig(mb)); + continue; + } + if (banList.contains(printSig(mb))) continue; + list.add(mb); + } + addAllMethodBindings(list, rb.superclass()); + ReferenceBinding[] interfaces = rb.superInterfaces(); + if (interfaces != null) { + for (ReferenceBinding iface : interfaces) addAllMethodBindings(list, iface); + } + } + } + + private static final List<String> METHODS_IN_OBJECT = Collections.unmodifiableList(Arrays.asList( + "hashCode()", + "equals(java.lang.Object)", + "wait()", + "wait(long)", + "wait(long, int)", + "notify()", + "notifyAll()", + "toString()", + "getClass()", + "clone()", + "finalize()")); + + private static String printSig(MethodBinding binding) { + StringBuilder signature = new StringBuilder(); + + signature.append(binding.selector); + signature.append("("); + boolean first = true; + if (binding.parameters != null) for (TypeBinding param : binding.parameters) { + if (!first) signature.append(", "); + first = false; + signature.append(simpleTypeBindingToString(param)); + } + signature.append(")"); + + return signature.toString(); + } + + private static String simpleTypeBindingToString(TypeBinding binding) { + binding = binding.erasure(); + if (binding != null && binding.isBaseType()) { + return new String (binding.sourceName()); + } else if (binding instanceof ReferenceBinding) { + String pkg = binding.qualifiedPackageName() == null ? "" : new String(binding.qualifiedPackageName()); + String qsn = binding.qualifiedSourceName() == null ? "" : new String(binding.qualifiedSourceName()); + return pkg.isEmpty() ? qsn : (pkg + "." + qsn); + } else if (binding instanceof ArrayBinding) { + StringBuilder out = new StringBuilder(); + out.append(binding.leafComponentType()); + for (int i = 0; i < binding.dimensions(); i++) out.append("[]"); + return out.toString(); + } + + return ""; + } + + private static boolean charArrayEquals(String s, char[] c) { + if (s == null) return c == null; + if (c == null) return false; + + if (s.length() != c.length) return false; + for (int i = 0; i < s.length(); i++) if (s.charAt(i) != c[i]) return false; + return true; + + + } +} diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java index 341c54b3..efe4e18f 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java @@ -34,9 +34,7 @@ import java.util.List; import lombok.core.DiagnosticsReceiver; import lombok.core.PostCompiler; -import lombok.core.AST.Kind; import lombok.eclipse.Eclipse; -import lombok.eclipse.EclipseNode; import lombok.eclipse.handlers.EclipseHandlerUtil; import org.eclipse.jdt.core.IMethod; @@ -44,22 +42,17 @@ import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.internal.compiler.CompilationResult; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; -import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess; import org.eclipse.jdt.internal.compiler.ast.Clinit; -import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.FieldReference; -import org.eclipse.jdt.internal.compiler.ast.ForeachStatement; -import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; import org.eclipse.jdt.internal.compiler.ast.MessageSend; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; -import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; @@ -68,20 +61,14 @@ import org.eclipse.jdt.internal.compiler.ast.ThisReference; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; -import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; -import org.eclipse.jdt.internal.compiler.lookup.Binding; -import org.eclipse.jdt.internal.compiler.lookup.BlockScope; import org.eclipse.jdt.internal.compiler.lookup.ClassScope; import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; import org.eclipse.jdt.internal.compiler.lookup.MethodScope; -import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; -import org.eclipse.jdt.internal.compiler.lookup.TypeIds; -import org.eclipse.jdt.internal.compiler.parser.Parser; public class PatchFixes { public static int fixRetrieveStartingCatchPosition(int in) { @@ -174,414 +161,4 @@ public class PatchFixes { String fileName = path + "/" + name; return new BufferedOutputStream(PostCompiler.wrapOutputStream(out, fileName, DiagnosticsReceiver.CONSOLE)); } - - private static Field astStackField, astPtrField; - - static { - try { - astStackField = Parser.class.getDeclaredField("astStack"); - astStackField.setAccessible(true); - astPtrField = Parser.class.getDeclaredField("astPtr"); - astPtrField.setAccessible(true); - } catch (Exception e) { - // Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this. - } - } - - public static void copyInitializationOfForEachIterable(Parser parser) { - ASTNode[] astStack; - int astPtr; - try { - astStack = (ASTNode[]) astStackField.get(parser); - astPtr = (Integer)astPtrField.get(parser); - } catch (Exception e) { - // Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this. - return; - } - - ForeachStatement foreachDecl = (ForeachStatement) astStack[astPtr]; - ASTNode init = foreachDecl.collection; - if (init == null) return; - if (foreachDecl.elementVariable != null && foreachDecl.elementVariable.type instanceof SingleTypeReference) { - SingleTypeReference ref = (SingleTypeReference) foreachDecl.elementVariable.type; - if (ref.token == null || ref.token.length != 3 || ref.token[0] != 'v' || ref.token[1] != 'a' || ref.token[2] != 'l') return; - } else return; - - try { - if (iterableCopyField != null) iterableCopyField.set(foreachDecl.elementVariable, init); - } catch (Exception e) { - // In ecj mode this field isn't there and we don't need the copy anyway, so, we ignore the exception. - } - } - - public static void copyInitializationOfLocalDeclarationForVal(Parser parser) { - ASTNode[] astStack; - int astPtr; - try { - astStack = (ASTNode[]) astStackField.get(parser); - astPtr = (Integer)astPtrField.get(parser); - } catch (Exception e) { - // Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this. - return; - } - AbstractVariableDeclaration variableDecl = (AbstractVariableDeclaration) astStack[astPtr]; - if (!(variableDecl instanceof LocalDeclaration)) return; - ASTNode init = variableDecl.initialization; - if (init == null) return; - if (variableDecl.type instanceof SingleTypeReference) { - SingleTypeReference ref = (SingleTypeReference) variableDecl.type; - if (ref.token == null || ref.token.length != 3 || ref.token[0] != 'v' || ref.token[1] != 'a' || ref.token[2] != 'l') return; - } else return; - - try { - if (initCopyField != null) initCopyField.set(variableDecl, init); - } catch (Exception e) { - // In ecj mode this field isn't there and we don't need the copy anyway, so, we ignore the exception. - } - } - - private static Field initCopyField, iterableCopyField; - - static { - try { - initCopyField = LocalDeclaration.class.getDeclaredField("$initCopy"); - iterableCopyField = LocalDeclaration.class.getDeclaredField("$iterableCopy"); - } catch (Throwable t) { - //ignore - no $initCopy exists when running in ecj. - } - } - - public static boolean handleValForForEach(ForeachStatement forEach, BlockScope scope) { - if (forEach.elementVariable != null && forEach.elementVariable.type instanceof SingleTypeReference) { - char[] token = ((SingleTypeReference)forEach.elementVariable.type).token; - if (token == null || token.length != 3) return false; - else if (token[0] != 'v' || token[1] != 'a' || token[2] != 'l') return false; - } else return false; - - TypeBinding component = getForEachComponentType(forEach.collection, scope); - TypeReference replacement = Eclipse.makeType(component, forEach.elementVariable.type, false); - - forEach.elementVariable.modifiers |= ClassFileConstants.AccFinal; - forEach.elementVariable.type = replacement != null ? replacement : - new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, Eclipse.poss(forEach.elementVariable.type, 3)); - - return false; - } - - private static TypeBinding getForEachComponentType(Expression collection, BlockScope scope) { - if (collection != null) { - TypeBinding resolved = collection.resolveType(scope); - if (resolved.isArrayType()) { - resolved = ((ArrayBinding) resolved).elementsType(); - return resolved; - } else if (resolved instanceof ReferenceBinding) { - ReferenceBinding iterableType = ((ReferenceBinding)resolved).findSuperTypeOriginatingFrom(TypeIds.T_JavaLangIterable, false); - - TypeBinding[] arguments = null; - if (iterableType != null) switch (iterableType.kind()) { - case Binding.GENERIC_TYPE : // for (T t : Iterable<T>) - in case used inside Iterable itself - arguments = iterableType.typeVariables(); - break; - case Binding.PARAMETERIZED_TYPE : // for(E e : Iterable<E>) - arguments = ((ParameterizedTypeBinding)iterableType).arguments; - break; - } - - if (arguments != null && arguments.length == 1) { - return arguments[0]; - } - } - } - - return null; - } - - public static boolean handleValForLocalDeclaration(LocalDeclaration local, BlockScope scope) { - if (local == null || !LocalDeclaration.class.equals(local.getClass())) return false; - boolean decomponent = false; - - if (local.type instanceof SingleTypeReference) { - char[] token = ((SingleTypeReference)local.type).token; - if (token == null || token.length != 3) return false; - else if (token[0] != 'v' || token[1] != 'a' || token[2] != 'l') return false; - } else return false; - - Expression init = local.initialization; - if (init == null && initCopyField != null) { - try { - init = (Expression) initCopyField.get(local); - } catch (Exception e) { - } - } - - if (init == null && iterableCopyField != null) { - try { - init = (Expression) iterableCopyField.get(local); - decomponent = true; - } catch (Exception e) { - } - } - - TypeReference replacement = null; - if (init != null && decomponent) { - } - - if (init != null) { - TypeBinding resolved = decomponent ? getForEachComponentType(init, scope) : init.resolveType(scope); - if (resolved != null) { - replacement = Eclipse.makeType(resolved, local.type, false); - } - } - - local.modifiers |= ClassFileConstants.AccFinal; - local.type = replacement != null ? replacement : new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, Eclipse.poss(local.type, 3)); - - return false; - } - - public static TypeBinding skipResolveInitializerIfAlreadyCalled(Expression expr, BlockScope scope) { - if (expr.resolvedType != null) return expr.resolvedType; - return expr.resolveType(scope); - } - - public static TypeBinding skipResolveInitializerIfAlreadyCalled2(Expression expr, BlockScope scope, LocalDeclaration decl) { - if (decl != null && LocalDeclaration.class.equals(decl.getClass()) && expr.resolvedType != null) return expr.resolvedType; - return expr.resolveType(scope); - } - - public static boolean handleDelegateForType(TypeDeclaration decl) { - return false; - } - - public static boolean handleDelegateForType2(ClassScope scope) { - TypeDeclaration decl = scope.referenceContext; - if (decl == null) return false; - - boolean continueAdding = false; - - /* debug */ try { - MethodBinding[] existingMethods = (MethodBinding[]) sourceTypeBindingMethodsField.get(decl.binding); - System.out.println("Existing method bindings in type.SourceTypeBinding: " + new String(scope.referenceContext.name)); - for (MethodBinding binding : existingMethods) { - System.out.println(" " + binding); - } - FieldBinding[] existingFields = (FieldBinding[]) sourceTypeBindingFieldsField.get(decl.binding); - System.out.println("Existing field bindings in type.SourceTypeBinding: "); - for (FieldBinding binding : existingFields) { - System.out.println(" " + binding); - } - - if (charArrayEquals("Test", scope.referenceContext.name)) { - for (AbstractMethodDeclaration m : scope.referenceContext.methods) { - if (m instanceof MethodDeclaration) { - if (charArrayEquals("example", m.selector)) { - System.out.println("Example scope now: " + m.scope); - System.out.println("Example binding now: " + m.binding); - if (m.scope == null && m.binding == null) continueAdding = true; - Thread.dumpStack(); - } - } - } - } - } catch (Exception e) { - System.err.println("EXCEPTION DURING DEBUG 1"); - e.printStackTrace(); - } - - if (!continueAdding) return false; - - if (decl.fields != null) for (FieldDeclaration field : decl.fields) { - if (field.annotations == null) continue; - for (Annotation ann : field.annotations) { - if (ann.type == null) continue; - TypeBinding tb = ann.type.resolveType(decl.initializerScope); - if (!charArrayEquals("lombok", tb.qualifiedPackageName())) continue; - if (!charArrayEquals("Delegate", tb.qualifiedSourceName())) continue; - - List<ClassLiteralAccess> rawTypes = new ArrayList<ClassLiteralAccess>(); - for (MemberValuePair pair : ann.memberValuePairs()) { - if (pair.name == null || charArrayEquals("value", pair.name)) { - if (pair.value instanceof ArrayInitializer) { - for (Expression expr : ((ArrayInitializer)pair.value).expressions) { - if (expr instanceof ClassLiteralAccess) rawTypes.add((ClassLiteralAccess) expr); - } - } - if (pair.value instanceof ClassLiteralAccess) { - rawTypes.add((ClassLiteralAccess) pair.value); - } - } - } - - List<MethodBinding> methodsToDelegate = new ArrayList<MethodBinding>(); - - if (rawTypes.isEmpty()) { - addAllMethodBindings(methodsToDelegate, field.type.resolveType(decl.initializerScope)); - } else { - for (ClassLiteralAccess cla : rawTypes) { - addAllMethodBindings(methodsToDelegate, cla.type.resolveType(decl.initializerScope)); - } - } - - System.out.println("About to generate the following methods, all delegating to: this." + new String(field.name)); - for (MethodBinding mb : methodsToDelegate) { - System.out.println(mb); - } - System.out.println("-----------"); - - generateDelegateMethods(decl, methodsToDelegate, field.name, ann); - } - } - - return false; - } - - private static final Method methodScopeCreateMethodMethod; - private static final Field sourceTypeBindingMethodsField, sourceTypeBindingFieldsField; - - static { - Method m = null; - Field f = null, g = null; - Exception ex = null; - - try { - m = MethodScope.class.getDeclaredMethod("createMethod", AbstractMethodDeclaration.class); - m.setAccessible(true); - f = SourceTypeBinding.class.getDeclaredField("methods"); - f.setAccessible(true); - g = SourceTypeBinding.class.getDeclaredField("fields"); - g.setAccessible(true); - } catch (Exception e) { - ex = e; - } - - methodScopeCreateMethodMethod = m; - sourceTypeBindingMethodsField = f; - sourceTypeBindingFieldsField = g; - if (ex != null) throw new RuntimeException(ex); - } - - private static void generateDelegateMethods(TypeDeclaration type, List<MethodBinding> methods, char[] delegate, ASTNode source) { - for (MethodBinding binding : methods) { - MethodDeclaration method = generateDelegateMethod(delegate, binding, type.compilationResult, source); - if (type.methods == null) { - type.methods = new AbstractMethodDeclaration[1]; - type.methods[0] = method; - } else { - int insertionPoint; - for (insertionPoint = 0; insertionPoint < type.methods.length; insertionPoint++) { - AbstractMethodDeclaration current = type.methods[insertionPoint]; - if (current instanceof Clinit) continue; - if (Eclipse.isGenerated(current)) continue; - break; - } - AbstractMethodDeclaration[] newArray = new AbstractMethodDeclaration[type.methods.length + 1]; - System.arraycopy(type.methods, 0, newArray, 0, insertionPoint); - if (insertionPoint <= type.methods.length) { - System.arraycopy(type.methods, insertionPoint, newArray, insertionPoint + 1, type.methods.length - insertionPoint); - } - - newArray[insertionPoint] = method; - type.methods = newArray; -// MethodScope methodScope = new MethodScope(type.scope, method, false); -// -// try { -// MethodBinding methodBinding = (MethodBinding) methodScopeCreateMethodMethod.invoke(methodScope, method); -// System.out.println("SCOPE NOW: " + method.scope); -// -// method.resolve(type.scope); -// System.out.println("Bind now: " + methodBinding.returnType); -// -// MethodBinding[] existing = (MethodBinding[]) sourceTypeBindingMethodsField.get(type.binding); -// if (existing == null) existing = new MethodBinding[] {methodBinding}; -// else { -// MethodBinding[] copy = new MethodBinding[existing.length + 1]; -// System.arraycopy(existing, 0, copy, 0, existing.length); -// copy[existing.length] = methodBinding; -// } -// sourceTypeBindingMethodsField.set(type.binding, existing); -// System.out.println("Added method binding: " + methodBinding); -// System.out.println(method); -// } catch (Exception e) { -// throw new RuntimeException(e); -// } - } - } - } - - private static MethodDeclaration generateDelegateMethod(char[] name, MethodBinding binding, CompilationResult compilationResult, ASTNode source) { - MethodDeclaration method = new MethodDeclaration(compilationResult); - Eclipse.setGeneratedBy(method, source); - method.modifiers = ClassFileConstants.AccPublic; - method.returnType = Eclipse.makeType(binding.returnType, source, false); - method.annotations = EclipseHandlerUtil.createSuppressWarningsAll(source, null); - if (binding.parameters != null && binding.parameters.length > 0) { - method.arguments = new Argument[binding.parameters.length]; - for (int i = 0; i < method.arguments.length; i++) { - String argName = "$p" + i; - method.arguments[i] = new Argument( - argName.toCharArray(), pos(source), - Eclipse.makeType(binding.parameters[i], source, false), - ClassFileConstants.AccFinal); - } - } - method.selector = binding.selector; - if (binding.thrownExceptions != null && binding.thrownExceptions.length > 0) { - method.thrownExceptions = new TypeReference[binding.thrownExceptions.length]; - for (int i = 0; i < method.thrownExceptions.length; i++) { - method.thrownExceptions[i] = Eclipse.makeType(binding.thrownExceptions[i], source, false); - } - } - - method.typeParameters = null; // TODO think about this - method.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; - FieldReference fieldRef = new FieldReference(name, pos(source)); - fieldRef.receiver = new ThisReference(source.sourceStart, source.sourceEnd); - MessageSend call = new MessageSend(); - call.receiver = fieldRef; - call.selector = binding.selector; - if (method.arguments != null) { - call.arguments = new Expression[method.arguments.length]; - for (int i = 0; i < method.arguments.length; i++) { - call.arguments[i] = new SingleNameReference(("$p" + i).toCharArray(), pos(source)); - } - } - - Statement body; - if (method.returnType instanceof SingleTypeReference && ((SingleTypeReference)method.returnType).token == TypeConstants.VOID) { - body = call; - } else { - body = new ReturnStatement(call, source.sourceStart, source.sourceEnd); - } - - method.statements = new Statement[] {body}; - // TODO add Eclipse.setGeneratedBy everywhere. - method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart; - method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd; - return method; - } - - private static void addAllMethodBindings(List<MethodBinding> list, TypeBinding binding) { - if (binding instanceof ReferenceBinding) { - for (MethodBinding mb : ((ReferenceBinding)binding).availableMethods()) { - if (mb.isStatic()) continue; - if (mb.isBridge()) continue; - if (mb.isConstructor()) continue; - if (mb.isDefaultAbstract()) continue; - if (!mb.isPublic()) continue; - if (mb.isSynthetic()) continue; - list.add(mb); - } - } - } - - private static boolean charArrayEquals(String s, char[] c) { - if (s == null) return c == null; - if (c == null) return false; - - if (s.length() != c.length) return false; - for (int i = 0; i < s.length(); i++) if (s.charAt(i) != c[i]) return false; - return true; - - - } } diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java b/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java new file mode 100644 index 00000000..d1294779 --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java @@ -0,0 +1,280 @@ +package lombok.eclipse.agent; + +import java.lang.reflect.Field; + +import lombok.eclipse.Eclipse; +import lombok.patcher.Hook; +import lombok.patcher.MethodTarget; +import lombok.patcher.ScriptManager; +import lombok.patcher.StackRequest; +import lombok.patcher.scripts.ScriptBuilder; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.ForeachStatement; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; +import org.eclipse.jdt.internal.compiler.lookup.Binding; +import org.eclipse.jdt.internal.compiler.lookup.BlockScope; +import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding; +import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; +import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; +import org.eclipse.jdt.internal.compiler.parser.Parser; + +public class PatchVal { + + // Creates a copy of the 'initialization' field on a LocalDeclaration if the type of the LocalDeclaration is 'val', because the completion parser will null this out, + // which in turn stops us from inferring the intended type for 'val x = 5;'. We look at the copy. + // Also patches local declaration to not call .resolveType() on the initializer expression if we've already done so (calling it twice causes weird errors), + // and patches .resolve() on LocalDeclaration itself to just-in-time replace the 'val' vartype with the right one. + + static void addPatches(ScriptManager sm, boolean ecj) { + final String LOCALDECLARATION_SIG = "org.eclipse.jdt.internal.compiler.ast.LocalDeclaration"; + final String FOREACHSTATEMENT_SIG = "org.eclipse.jdt.internal.compiler.ast.ForeachStatement"; + final String EXPRESSION_SIG = "org.eclipse.jdt.internal.compiler.ast.Expression"; + final String BLOCKSCOPE_SIG = "org.eclipse.jdt.internal.compiler.lookup.BlockScope"; + final String PARSER_SIG = "org.eclipse.jdt.internal.compiler.parser.Parser"; + final String TYPEBINDING_SIG = "org.eclipse.jdt.internal.compiler.lookup.TypeBinding"; + + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG)) + .request(StackRequest.THIS, StackRequest.PARAM1) + .decisionMethod(new Hook("lombok.eclipse.agent.PatchVal", "handleValForLocalDeclaration", "boolean", LOCALDECLARATION_SIG, BLOCKSCOPE_SIG)) + .build()); + + sm.addScript(ScriptBuilder.replaceMethodCall() + .target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG)) + .methodToReplace(new Hook(EXPRESSION_SIG, "resolveType", TYPEBINDING_SIG, BLOCKSCOPE_SIG)) + .requestExtra(StackRequest.THIS) + .replacementMethod(new Hook("lombok.eclipse.agent.PatchVal", "skipResolveInitializerIfAlreadyCalled2", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG, LOCALDECLARATION_SIG)) + .build()); + + sm.addScript(ScriptBuilder.replaceMethodCall() + .target(new MethodTarget(FOREACHSTATEMENT_SIG, "resolve", "void", BLOCKSCOPE_SIG)) + .methodToReplace(new Hook(EXPRESSION_SIG, "resolveType", TYPEBINDING_SIG, BLOCKSCOPE_SIG)) + .replacementMethod(new Hook("lombok.eclipse.agent.PatchVal", "skipResolveInitializerIfAlreadyCalled", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG)) + .build()); + + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget(FOREACHSTATEMENT_SIG, "resolve", "void", BLOCKSCOPE_SIG)) + .request(StackRequest.THIS, StackRequest.PARAM1) + .decisionMethod(new Hook("lombok.eclipse.agent.PatchVal", "handleValForForEach", "boolean", FOREACHSTATEMENT_SIG, BLOCKSCOPE_SIG)) + .build()); + + if (!ecj) { + sm.addScript(ScriptBuilder.addField() + .fieldName("$initCopy") + .fieldType("Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;") + .setPublic() + .setTransient() + .targetClass("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration") + .build()); + + sm.addScript(ScriptBuilder.addField() + .fieldName("$iterableCopy") + .fieldType("Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;") + .setPublic() + .setTransient() + .targetClass("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration") + .build()); + + sm.addScript(ScriptBuilder.wrapReturnValue() + .target(new MethodTarget(PARSER_SIG, "consumeExitVariableWithInitialization", "void")) + .request(StackRequest.THIS) + .wrapMethod(new Hook("lombok.eclipse.agent.PatchVal", "copyInitializationOfLocalDeclaration", "void", PARSER_SIG)) + .build()); + + sm.addScript(ScriptBuilder.wrapReturnValue() + .target(new MethodTarget(PARSER_SIG, "consumeEnhancedForStatementHeader", "void")) + .request(StackRequest.THIS) + .wrapMethod(new Hook("lombok.eclipse.agent.PatchVal", "copyInitializationOfForEachIterable", "void", PARSER_SIG)) + .build()); + } + } + + private static final class Reflection { + private static final Field initCopyField, iterableCopyField; + private static final Field astStackField, astPtrField; + + static { + Field a = null, b = null, c = null, d = null; + + try { + a = LocalDeclaration.class.getDeclaredField("$initCopy"); + b = LocalDeclaration.class.getDeclaredField("$iterableCopy"); + } catch (Throwable t) { + //ignore - no $initCopy exists when running in ecj. + } + + try { + c = Parser.class.getDeclaredField("astStack"); + c.setAccessible(true); + d = Parser.class.getDeclaredField("astPtr"); + d.setAccessible(true); + } catch (Exception e) { + // Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this. + } + + initCopyField = a; + iterableCopyField = b; + astStackField = c; + astPtrField = d; + } + } + + public static void copyInitializationOfLocalDeclaration(Parser parser) { + ASTNode[] astStack; + int astPtr; + try { + astStack = (ASTNode[]) Reflection.astStackField.get(parser); + astPtr = (Integer)Reflection.astPtrField.get(parser); + } catch (Exception e) { + // Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this. + return; + } + AbstractVariableDeclaration variableDecl = (AbstractVariableDeclaration) astStack[astPtr]; + if (!(variableDecl instanceof LocalDeclaration)) return; + ASTNode init = variableDecl.initialization; + if (init == null) return; + if (variableDecl.type instanceof SingleTypeReference) { + SingleTypeReference ref = (SingleTypeReference) variableDecl.type; + if (ref.token == null || ref.token.length != 3 || ref.token[0] != 'v' || ref.token[1] != 'a' || ref.token[2] != 'l') return; + } else return; + + try { + if (Reflection.initCopyField != null) Reflection.initCopyField.set(variableDecl, init); + } catch (Exception e) { + // In ecj mode this field isn't there and we don't need the copy anyway, so, we ignore the exception. + } + } + + public static TypeBinding skipResolveInitializerIfAlreadyCalled(Expression expr, BlockScope scope) { + if (expr.resolvedType != null) return expr.resolvedType; + return expr.resolveType(scope); + } + + public static TypeBinding skipResolveInitializerIfAlreadyCalled2(Expression expr, BlockScope scope, LocalDeclaration decl) { + if (decl != null && LocalDeclaration.class.equals(decl.getClass()) && expr.resolvedType != null) return expr.resolvedType; + return expr.resolveType(scope); + } + + public static boolean handleValForLocalDeclaration(LocalDeclaration local, BlockScope scope) { + if (local == null || !LocalDeclaration.class.equals(local.getClass())) return false; + boolean decomponent = false; + + if (local.type instanceof SingleTypeReference) { + char[] token = ((SingleTypeReference)local.type).token; + if (token == null || token.length != 3) return false; + else if (token[0] != 'v' || token[1] != 'a' || token[2] != 'l') return false; + } else return false; + + Expression init = local.initialization; + if (init == null && Reflection.initCopyField != null) { + try { + init = (Expression) Reflection.initCopyField.get(local); + } catch (Exception e) { + } + } + + if (init == null && Reflection.iterableCopyField != null) { + try { + init = (Expression) Reflection.iterableCopyField.get(local); + decomponent = true; + } catch (Exception e) { + } + } + + TypeReference replacement = null; + if (init != null && decomponent) { + } + + if (init != null) { + TypeBinding resolved = decomponent ? getForEachComponentType(init, scope) : init.resolveType(scope); + if (resolved != null) { + replacement = Eclipse.makeType(resolved, local.type, false); + } + } + + local.modifiers |= ClassFileConstants.AccFinal; + local.type = replacement != null ? replacement : new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, Eclipse.poss(local.type, 3)); + + return false; + } + public static void copyInitializationOfForEachIterable(Parser parser) { + ASTNode[] astStack; + int astPtr; + try { + astStack = (ASTNode[]) Reflection.astStackField.get(parser); + astPtr = (Integer) Reflection.astPtrField.get(parser); + } catch (Exception e) { + // Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this. + return; + } + + ForeachStatement foreachDecl = (ForeachStatement) astStack[astPtr]; + ASTNode init = foreachDecl.collection; + if (init == null) return; + if (foreachDecl.elementVariable != null && foreachDecl.elementVariable.type instanceof SingleTypeReference) { + SingleTypeReference ref = (SingleTypeReference) foreachDecl.elementVariable.type; + if (ref.token == null || ref.token.length != 3 || ref.token[0] != 'v' || ref.token[1] != 'a' || ref.token[2] != 'l') return; + } else return; + + try { + if (Reflection.iterableCopyField != null) Reflection.iterableCopyField.set(foreachDecl.elementVariable, init); + } catch (Exception e) { + // In ecj mode this field isn't there and we don't need the copy anyway, so, we ignore the exception. + } + } + + public static boolean handleValForForEach(ForeachStatement forEach, BlockScope scope) { + if (forEach.elementVariable != null && forEach.elementVariable.type instanceof SingleTypeReference) { + char[] token = ((SingleTypeReference)forEach.elementVariable.type).token; + if (token == null || token.length != 3) return false; + else if (token[0] != 'v' || token[1] != 'a' || token[2] != 'l') return false; + } else return false; + + TypeBinding component = getForEachComponentType(forEach.collection, scope); + TypeReference replacement = Eclipse.makeType(component, forEach.elementVariable.type, false); + + forEach.elementVariable.modifiers |= ClassFileConstants.AccFinal; + forEach.elementVariable.type = replacement != null ? replacement : + new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, Eclipse.poss(forEach.elementVariable.type, 3)); + + return false; + } + + private static TypeBinding getForEachComponentType(Expression collection, BlockScope scope) { + if (collection != null) { + TypeBinding resolved = collection.resolveType(scope); + if (resolved.isArrayType()) { + resolved = ((ArrayBinding) resolved).elementsType(); + return resolved; + } else if (resolved instanceof ReferenceBinding) { + ReferenceBinding iterableType = ((ReferenceBinding)resolved).findSuperTypeOriginatingFrom(TypeIds.T_JavaLangIterable, false); + + TypeBinding[] arguments = null; + if (iterableType != null) switch (iterableType.kind()) { + case Binding.GENERIC_TYPE : // for (T t : Iterable<T>) - in case used inside Iterable itself + arguments = iterableType.typeVariables(); + break; + case Binding.PARAMETERIZED_TYPE : // for(E e : Iterable<E>) + arguments = ((ParameterizedTypeBinding)iterableType).arguments; + break; + } + + if (arguments != null && arguments.length == 1) { + return arguments[0]; + } + } + } + + return null; + } +} |