/*
* Copyright 2009-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.jdt.groovy.internal.compiler.ast;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import groovy.lang.GroovyRuntimeException;
import groovyjarjarasm.asm.Opcodes;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.Comment;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.PackageNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.TaskEntry;
import org.codehaus.groovy.ast.expr.AnnotationConstantExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.ErrorCollector;
import org.codehaus.groovy.control.Janitor;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.codehaus.groovy.control.Phases;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.io.ReaderSource;
import org.codehaus.groovy.control.messages.ExceptionMessage;
import org.codehaus.groovy.control.messages.LocatedMessage;
import org.codehaus.groovy.control.messages.Message;
import org.codehaus.groovy.control.messages.SimpleMessage;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import org.codehaus.groovy.eclipse.GroovyLogManager;
import org.codehaus.groovy.eclipse.TraceCategory;
import org.codehaus.groovy.syntax.CSTNode;
import org.codehaus.groovy.syntax.PreciseSyntaxException;
import org.codehaus.groovy.syntax.RuntimeParserException;
import org.codehaus.groovy.syntax.SyntaxException;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.tools.GroovyClass;
import org.codehaus.jdt.groovy.control.EclipseSourceUnit;
import org.codehaus.jdt.groovy.core.dom.GroovyCompilationUnit;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.groovy.core.Activator;
import org.eclipse.jdt.groovy.core.util.GroovyUtils;
import org.eclipse.jdt.internal.compiler.ClassFile;
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.AnnotationMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
import org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference;
import org.eclipse.jdt.internal.compiler.ast.CharLiteral;
import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.DoubleLiteral;
import org.eclipse.jdt.internal.compiler.ast.FalseLiteral;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FloatLiteral;
import org.eclipse.jdt.internal.compiler.ast.ImportReference;
import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
import org.eclipse.jdt.internal.compiler.ast.Javadoc;
import org.eclipse.jdt.internal.compiler.ast.Literal;
import org.eclipse.jdt.internal.compiler.ast.LongLiteral;
import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.NormalAnnotation;
import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedAllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation;
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.StringLiteral;
import org.eclipse.jdt.internal.compiler.ast.TrueLiteral;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.ast.Wildcard;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
import org.eclipse.jdt.internal.core.util.Util;
/**
* A subtype of JDT CompilationUnitDeclaration that represents a groovy source file. It overrides methods as appropriate, delegating
* to the groovy infrastructure.
*
* @author Andy Clement
*/
public class GroovyCompilationUnitDeclaration extends CompilationUnitDeclaration {
// The groovy compilation unit shared by all files in the same project
private CompilationUnit groovyCompilationUnit;
// The groovy sourceunit (a member of the groovyCompilationUnit)
private SourceUnit groovySourceUnit;
private CompilerOptions compilerOptions;
public static boolean defaultCheckGenerics = false;
public static boolean earlyTransforms = true;
static {
try {
String value = System.getProperty("earlyTransforms");
if (value != null) {
if (value.equalsIgnoreCase("true")) {
log("groovyeclipse.earlyTransforms = true");
earlyTransforms = true;
} else if (value.equalsIgnoreCase("false")) {
log("groovyeclipse.earlyTransforms = false");
earlyTransforms = false;
}
}
} catch (Throwable ignored) {
}
}
private boolean isScript = false;
private TraitHelper traitHelper = new TraitHelper();
private static final boolean DEBUG_TASK_TAGS = false;
public GroovyCompilationUnitDeclaration(
ProblemReporter problemReporter,
CompilationResult compilationResult,
int sourceLength,
CompilationUnit groovyCompilationUnit,
SourceUnit groovySourceUnit,
CompilerOptions compilerOptions) {
super(problemReporter, compilationResult, sourceLength);
this.groovyCompilationUnit = groovyCompilationUnit;
this.groovySourceUnit = groovySourceUnit;
this.compilerOptions = compilerOptions;
}
/**
* Drives the Groovy Compilation Unit for this project through to the specified phase. Yes on a call for one groovy file to
* processToPhase(), all the groovy files in the project proceed to that phase. This isn't ideal but doesn't necessarily cause a
* problem. But it does mean progress reporting for the compilation is incorrect as it jumps rather than smoothly going from 1
* to 100%.
*
* @param phase the phase to process up to
* @return true if clean processing, false otherwise
*/
public boolean processToPhase(int phase) {
// GRECLIPSE-1776 start
// Try to discard cached class loaders for traits
if (phase == Phases.CANONICALIZATION) {
for (ModuleNode module : groovyCompilationUnit.getAST().getModules()) {
for (ClassNode classNode : module.getClasses()) {
if (traitHelper.isTrait(classNode)) {
GroovyParser.tidyCache();
break;
}
}
}
}
// GRECLIPSE-1776 end
boolean alreadyHasErrors = compilationResult.hasErrors();
// Our replacement error collector doesn't cause an exception, instead they are checked for post 'compile'
try {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(groovyCompilationUnit.getTransformLoader());
groovyCompilationUnit.compile(phase);
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
if (groovySourceUnit.getErrorCollector().hasErrors()) {
recordProblems(groovySourceUnit.getErrorCollector().getErrors());
return false;
} else {
return true;
}
} catch (MultipleCompilationErrorsException mce) {
fixGroovyRuntimeException(mce);
if (GroovyLogManager.manager.hasLoggers()) {
GroovyLogManager.manager.log(TraceCategory.COMPILER, mce.getMessage());
}
ErrorCollector collector = mce.getErrorCollector();
if (collector.getErrorCount() == 1 && mce.getErrorCollector().getError(0) instanceof ExceptionMessage) {
Exception cause = ((ExceptionMessage) mce.getErrorCollector().getError(0)).getCause();
if (cause instanceof AbortCompilation) {
throw (AbortCompilation) cause;
}
}
recordProblems(mce.getErrorCollector().getErrors());
} catch (GroovyBugError gbe) {
if (GroovyLogManager.manager.hasLoggers()) {
GroovyLogManager.manager.log(TraceCategory.COMPILER, gbe.getBugText());
}
if (gbe.getCause() instanceof AbortCompilation) {
AbortCompilation abort = (AbortCompilation) gbe.getCause();
if (!abort.isSilent) {
if (abort.problem != null) {
problemReporter.record(abort.problem, compilationResult, this, true);
} else {
throw abort;
}
}
} else if (alreadyHasErrors) {
Util.log(new Status(IStatus.INFO, Activator.PLUGIN_ID,
"Ignoring GroovyBugError since it is likely caused by earlier issues", gbe));
} else {
Util.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Groovy compiler error", gbe));
// Need to record these problems as compiler errors since some users will not think to check the log.
// This is mostly a fix for problems like those in GRECLIPSE-1420, where a GBE is thrown when it is really just a syntax problem.
SyntaxErrorMessage syntaxError = new SyntaxErrorMessage(new SyntaxException("Groovy compiler error: " + gbe.getBugText(), gbe, 1, 0), groovySourceUnit);
ErrorCollector collector = groovySourceUnit.getErrorCollector();
collector.addError(syntaxError);
recordProblems(collector.getErrors());
}
}
return false;
}
/** Unwraps any SyntaxExceptions embedded within a GroovyRuntimeException. */
private void fixGroovyRuntimeException(MultipleCompilationErrorsException mce) {
List<SyntaxException> syntaxErrors = new ArrayList<SyntaxException>();
for (Iterator<? extends Message> it = mce.getErrorCollector().getErrors().iterator(); it.hasNext();) {
Message m = it.next();
if (m instanceof ExceptionMessage) {
ExceptionMessage em = (ExceptionMessage) m;
if (em.getCause() instanceof GroovyRuntimeException &&
((GroovyRuntimeException) em.getCause()).getCause() instanceof SyntaxException) {
syntaxErrors.add((SyntaxException) em.getCause().getCause());
it.remove();
}
}
}
for (SyntaxException se : syntaxErrors) {
mce.getErrorCollector().addError(se, groovySourceUnit);
}
}
//------------------------------------------------------------------------------------------------------------------
/**
* Returns the Groovy compilation unit shared by all files in the same project.
*/
public CompilationUnit getCompilationUnit() {
return groovyCompilationUnit;
}
/**
* Populates the compilation unit based on the successful parse.
*/
public void populateCompilationUnitDeclaration() {
UnitPopulator populator = new UnitPopulator();
populator.populate(this, groovySourceUnit);
}
private final static boolean DEBUG = false;
// FIXASC are costly regens being done for all the classes???
@Override
public void generateCode() {
boolean successful = processToPhase(Phases.ALL);
if (successful) {
// At the end of this method we want to make this call for each of the classes generated during processing
//
// compilationResult.record(classname.toCharArray(), new GroovyClassFile(classname, classbytes, foundBinding, path));
//
// For each generated class (in groovyCompilationUnit.getClasses()) we know:
// String classname = groovyClass.getName(); = this is the name of the generated type (doesn't matter where the
// declaration was)
// byte[] classbytes = groovyClass.getBytes(); = duh
// String path = groovyClass.getName().replace('.', '/'); = where to put it on disk
// The only tricky piece of information is discovering the binding that gave rise to the type. This is complicated
// in groovy because it is not policing that the package name matches the directory structure.
// Effectively the connection between the TypeDeclaration (which points to the binding) and the
// groovy created component is lost - if that were maintained we would not have to go hunting for it.
// On finishing processing we have access to the generated classes but GroovyClassFile objects have no idea what
// their originating ClassNode was.
// Under eclipse I've extended GroovyClassFile objects to remember their sourceUnit and ClassNode - this means
// we have to do very little hunting for the binding and don't have to mess around with strings (chopping off
// packages, etc).
// This returns all of them, for all source files
List<GroovyClass> classes = groovyCompilationUnit.getClasses();
if (DEBUG) {
log("Processing sourceUnit " + groovySourceUnit.getName());
}
for (GroovyClass clazz : classes) {
ClassNode classnode = clazz.getClassNode();
if (DEBUG) {
log("Looking at class " + clazz.getName());
log("ClassNode where it came from " + classnode);
}
// Only care about those coming about because of this groovySourceUnit
if (clazz.getSourceUnit() == groovySourceUnit) {
if (DEBUG) {
log("It is from this source unit");
}
// Worth continuing
String classname = clazz.getName();
SourceTypeBinding binding = null;
if (types != null && types.length != 0) {
binding = findBinding(types, clazz.getClassNode());
}
if (DEBUG) {
log("Binding located? " + (binding != null));
}
if (binding == null) {
// closures will be represented as InnerClassNodes
ClassNode current = classnode;
while (current instanceof InnerClassNode && binding == null) {
current = ((InnerClassNode) current).getOuterClass();
binding = findBinding(types, current);
if (DEBUG) {
log("Had another look because it is in an InnerClassNode, found binding? " + (binding != null));
}
}
}
boolean isScript = false;
// Suppress class file output if it is a script
// null binding implies synthetic type, which we assume cannot be a script
if (binding != null && binding.scope != null && (binding.scope.parent instanceof GroovyCompilationUnitScope)) {
GroovyCompilationUnitScope gcuScope = (GroovyCompilationUnitScope) binding.scope.parent;
if (gcuScope.isScript()) {
isScript = true;
}
}
if (!isScript) {
byte[] classbytes = clazz.getBytes();
String path = clazz.getName().replace('.', '/');
GroovyClassFile classFile = new GroovyClassFile(classname, classbytes, binding, path);
char[] classNameChars = classname.toCharArray();
if (binding == null) {
// GRECLIPSE-1653 this type likely added by AST transform and is synthetic
Map<char[], ClassFile> compiledTypes = Map.class.cast(compilationResult.compiledTypes);
compiledTypes.put(classNameChars, classFile);
} else {
compilationResult.record(classNameChars, classFile);
}
}
}
}
} else {
// GRECLIPSE-1773
// We should create problem types if some types are not compiled successfully as it is done for Java types.
// Otherwise incremental builder is not able to recompile dependencies of broken types.
if (types != null) {
for (TypeDeclaration type : types) {
if (type.binding != null) {
ClassFile.createProblemType(type, compilationResult);
}
}
}
}
}
private static void log(String message) {
System.out.println(message);
}
private static SourceTypeBinding findBinding(TypeDeclaration[] typedeclarations, ClassNode cnode) {
for (TypeDeclaration typedeclaration : typedeclarations) {
GroovyTypeDeclaration groovyTypeDeclaration = (GroovyTypeDeclaration) typedeclaration;
if (groovyTypeDeclaration.getClassNode().equals(cnode)) {
return groovyTypeDeclaration.binding;
}
if (typedeclaration.memberTypes != null) {
SourceTypeBinding binding = findBinding(typedeclaration.memberTypes, cnode);
if (binding != null) {
return binding;
}
}
}
return null;
}
// here be dragons
private void recordProblems(List<?> errors) {
// FIXASC look at this error situation (described below), surely we need to do it?
// Due to the nature of driving all groovy entities through compilation together, we can accumulate messages for other
// compilation units whilst processing the one we wanted to. Per GRE396 this can manifest as recording the wrong thing
// against the wrong type. That is the only case I have seen of it, so I'm not putting in the general mechanism for all
// errors yet, I'm just dealing with RuntimeParserExceptions. The general strategy would be to compare the ModuleNode
// for each message with the ModuleNode currently being processed - if they differ then this isn't a message for this
// unit and so we ignore it. If we do deal with it then we remember that we did (in errorsRecorded) and remove it from
// the list of those to process.
List<Message> errorsRecorded = new ArrayList<Message>();
// FIXASC poor way to get the errors attached to the files
// FIXASC does groovy ever produce warnings? How are they treated here?
for (Iterator<?> iterator = errors.iterator(); iterator.hasNext();) {
SyntaxException syntaxException = null;
Message message = (Message) iterator.next();
StringWriter sw = new StringWriter();
message.write(new PrintWriter(sw));
String msg = sw.toString();
CategorizedProblem p = null;
int line = 0;
int sev = 0;
int scol = 0;
int ecol = 0;
// LocatedMessage instances are produced sometimes, e.g. by grails ast transforms, use the context for position
if (message instanceof LocatedMessage) {
CSTNode context = ((LocatedMessage) message).getContext();
if (context instanceof Token) {
line = context.getStartLine();
scol = context.getStartColumn();
String text = ((Token) context).getText();
ecol = scol + (text == null ? 1 : (text.length() - 1));
}
}
if (message instanceof SimpleMessage) {
SimpleMessage simpleMessage = (SimpleMessage) message;
sev |= ProblemSeverities.Error;
String simpleText = simpleMessage.getMessage();
if (simpleText.length() > 1 && simpleText.charAt(0) == '\n') {
simpleText = simpleText.substring(1);
}
msg = "Groovy:" + simpleText;
if (msg.indexOf("\n") != -1) {
msg = msg.substring(0, msg.indexOf("\n"));
}
}
if (message instanceof SyntaxErrorMessage) {
SyntaxErrorMessage errorMessage = (SyntaxErrorMessage) message;
syntaxException = errorMessage.getCause();
sev |= ProblemSeverities.Error;
// FIXASC in the short term, prefixed groovy to indicate
// where it came from
String actualMessage = syntaxException.getMessage();
if (actualMessage.length() > 1 && actualMessage.charAt(0) == '\n') {
actualMessage = actualMessage.substring(1);
}
msg = "Groovy:" + actualMessage;
if (msg.indexOf("\n") != -1) {
msg = msg.substring(0, msg.indexOf("\n"));
}
line = syntaxException.getLine();
scol = errorMessage.getCause().getStartColumn();
ecol = errorMessage.getCause().getEndColumn() - 1;
}
int soffset = -1;
int eoffset = -1;
if (message instanceof ExceptionMessage) {
ExceptionMessage em = (ExceptionMessage) message;
sev |= ProblemSeverities.Error;
if (em.getCause() instanceof RuntimeParserException) {
RuntimeParserException rpe = (RuntimeParserException) em.getCause();
sev |= ProblemSeverities.Error;
msg = "Groovy:" + rpe.getMessage();
if (msg.indexOf("\n") != -1) {
msg = msg.substring(0, msg.indexOf("\n"));
}
ModuleNode errorModuleNode = rpe.getModule();
ModuleNode thisModuleNode = this.getModuleNode();
if (!errorModuleNode.equals(thisModuleNode)) {
continue;
}
soffset = rpe.getNode().getStart();
eoffset = rpe.getNode().getEnd() - 1;
// need to work out the line again as it may be wrong
line = 0;
while (compilationResult.lineSeparatorPositions[line] < soffset
&& line < compilationResult.lineSeparatorPositions.length) {
line++;
}
line++; // from an array index to a real 'line number'
}
}
if (syntaxException instanceof PreciseSyntaxException) {
soffset = ((PreciseSyntaxException) syntaxException).getStartOffset();
eoffset = ((PreciseSyntaxException) syntaxException).getEndOffset();
// need to work out the line again as it may be wrong
line = 0;
while (line < compilationResult.lineSeparatorPositions.length
&& compilationResult.lineSeparatorPositions[line] < soffset) {
line++;
}
;
line++; // from an array index to a real 'line number'
} else {
if (soffset == -1) {
soffset = getOffset(compilationResult.lineSeparatorPositions, line, scol);
}
if (eoffset == -1) {
eoffset = getOffset(compilationResult.lineSeparatorPositions, line, ecol);
}
}
if (soffset > eoffset) {
eoffset = soffset;
}
if (soffset > sourceEnd) {
soffset = sourceEnd;
eoffset = sourceEnd;
}
p = new DefaultProblemFactory().createProblem(getFileName(), 0, new String[] {msg}, 0, new String[] {msg}, sev, soffset, eoffset, line, scol);
problemReporter.record(p, compilationResult, this, false);
errorsRecorded.add(message);
log(String.valueOf(compilationResult.getFileName()) + ": " + line + " " + msg);
}
errors.removeAll(errorsRecorded);
}
private int getOffset(int[] lineSeparatorPositions, int line, int col) {
if (lineSeparatorPositions.length > (line - 2) && line > 1) {
return lineSeparatorPositions[line - 2] + col;
} else {
return col;
}
}
//------------------------------------------------------------------------------------------------------------------
@Override
public CompilationUnitScope buildCompilationUnitScope(LookupEnvironment lookupEnvironment) {
GroovyCompilationUnitScope gcus = new GroovyCompilationUnitScope(this, lookupEnvironment);
gcus.setIsScript(isScript);
return gcus;
}
public ModuleNode getModuleNode() {
return groovySourceUnit == null ? null : groovySourceUnit.getAST();
}
public SourceUnit getSourceUnit() {
return groovySourceUnit;
}
// TODO: Find a better home for this?
@Override
public org.eclipse.jdt.core.dom.CompilationUnit getSpecialDomCompilationUnit(org.eclipse.jdt.core.dom.AST ast) {
return new GroovyCompilationUnit(ast);
}
// for testing
public String print() {
return toString();
}
public GroovyCompilationUnitScope getScope() {
return (GroovyCompilationUnitScope) scope;
}
@Override
public void resolve() {
processToPhase(Phases.SEMANTIC_ANALYSIS);
checkForTags();
setComments();
}
/**
* Check any comments from the source file for task tag markers.
*/
private void checkForTags() {
if (this.compilerOptions == null) {
return;
}
List<Comment> comments = groovySourceUnit.getComments();
if (comments == null) {
return;
}
char[][] taskTags = this.compilerOptions.taskTags;
char[][] taskPriorities = this.compilerOptions.taskPriorities;
boolean caseSensitiveTags = this.compilerOptions.isTaskCaseSensitive;
try {
if (taskTags != null) {
// For each comment find all task tags within it and cope with
for (Comment comment : comments) {
List<TaskEntry> allTasksInComment = new ArrayList<TaskEntry>();
for (int t = 0; t < taskTags.length; t++) {
String taskTag = new String(taskTags[t]);
String taskPriority = null;
if (taskPriorities != null) {
taskPriority = new String(taskPriorities[t]);
}
allTasksInComment.addAll(comment.getPositionsOf(taskTag, taskPriority,
compilationResult.lineSeparatorPositions, caseSensitiveTags));
}
if (!allTasksInComment.isEmpty()) {
// Need to check quickly for clashes
for (int t1 = 0; t1 < allTasksInComment.size(); t1++) {
for (int t2 = 0; t2 < allTasksInComment.size(); t2++) {
if (t1 == t2)
continue;
TaskEntry taskOne = allTasksInComment.get(t1);
TaskEntry taskTwo = allTasksInComment.get(t2);
if (DEBUG_TASK_TAGS) {
log("Comparing " + taskOne.toString() + " and " + taskTwo.toString());
}
if ((taskOne.start + taskOne.taskTag.length() + 1) == taskTwo.start) {
// Adjacent tags
taskOne.isAdjacentTo = taskTwo;
} else {
if ((taskOne.getEnd() > taskTwo.start) && (taskOne.start < taskTwo.start)) {
taskOne.setEnd(taskTwo.start - 1);
if (DEBUG_TASK_TAGS) {
log("trim " + taskOne.toString() + " and " + taskTwo.toString());
}
} else if (taskTwo.getEnd() > taskOne.start && taskTwo.start < taskOne.start) {
taskTwo.setEnd(taskOne.start - 1);
if (DEBUG_TASK_TAGS) {
log("trim " + taskOne.toString() + " and " + taskTwo.toString());
}
}
}
}
}
for (TaskEntry taskEntry : allTasksInComment) {
this.problemReporter.referenceContext = this;
if (DEBUG_TASK_TAGS) {
log("Adding task " + taskEntry.toString());
}
problemReporter.task(taskEntry.taskTag, taskEntry.getText(), taskEntry.taskPriority, taskEntry.start, taskEntry.getEnd());
}
}
}
}
} catch (AbortCompilation ac) {
// that is ok... probably cancelled
} catch (Throwable t) {
Util.log(t, "Unexpected problem processing task tags in " + groovySourceUnit.getName());
new RuntimeException("Unexpected problem processing task tags in " + groovySourceUnit.getName(), t).printStackTrace();
}
}
/**
* Take the comments information from the parse and apply it to the compilation unit
*/
private void setComments() {
List<Comment> groovyComments = this.groovySourceUnit.getComments();
if (groovyComments == null || groovyComments.size() == 0) {
return;
}
this.comments = new int[groovyComments.size()][2];
for (int c = 0, max = groovyComments.size(); c < max; c++) {
Comment groovyComment = groovyComments.get(c);
this.comments[c] = groovyComment.getPositions(compilationResult.lineSeparatorPositions);
}
}
@Override
public void analyseCode() {
processToPhase(Phases.CANONICALIZATION);
}
@Override
public void cleanUp() {
super.cleanUp();
if (groovySourceUnit instanceof EclipseSourceUnit) {
((EclipseSourceUnit) groovySourceUnit).resolver.cleanUp();
}
}
/*
// @Override
// public void abort(int abortLevel, CategorizedProblem problem) {
// // FIXASC look at callers of this, should we be following the abort on first problem policy?
// super.abort(abortLevel, problem);
// }
// @Override
// public void checkUnusedImports() {
// super.checkUnusedImports();
// }
// @Override
// public CompilationResult compilationResult() {
// return super.compilationResult();
// }
// @Override
// public TypeDeclaration declarationOfType(char[][] typeName) {
// return super.declarationOfType(typeName);
// }
// @Override
// public void finalizeProblems() {
// super.finalizeProblems();
// }
// @Override
// public char[] getFileName() {
// return super.getFileName();
// }
// @Override
// public char[] getMainTypeName() {
// // FIXASC necessary to return something for groovy?
// return super.getMainTypeName();
// }
// @Override
// public boolean hasErrors() {
// return super.hasErrors();
// }
// @Override
// public boolean isEmpty() {
// return super.isEmpty();
// }
// @Override
// public boolean isPackageInfo() {
// return super.isPackageInfo();
// }
// @Override
// public StringBuffer print(int indent, StringBuffer output) {
// // FIXASC additional stuff to print?
// return super.print(indent, output);
// }
// @Override
// public void propagateInnerEmulationForAllLocalTypes() {
// // FIXASC anything to do here for groovy inner types?
// super.propagateInnerEmulationForAllLocalTypes();
// }
// @Override
// public void record(LocalTypeBinding localType) {
// super.record(localType);
// }
// @Override
// public void recordStringLiteral(StringLiteral literal, boolean fromRecovery) {
// // FIXASC assert not called for groovy, surely
// super.recordStringLiteral(literal, fromRecovery);
// }
// @Override
// public void tagAsHavingErrors() {
// super.tagAsHavingErrors();
// }
// @Override
// public void traverse(ASTVisitor visitor, CompilationUnitScope unitScope) {
// // FIXASC are we well formed enough for this?
// super.traverse(visitor, unitScope);
// }
// @Override
// public ASTNode concreteStatement() {
// // FIXASC assert not called for groovy, surely
// return super.concreteStatement();
// }
// @Override
// public boolean isImplicitThis() {
// // FIXASC assert not called for groovy, surely
// return super.isImplicitThis();
// }
// @Override
// public boolean isSuper() {
// // FIXASC assert not called for groovy, surely
// return super.isSuper();
// }
// @Override
// public boolean isThis() {
// // FIXASC assert not called for groovy, surely
// return super.isThis();
// }
// @Override
// public int sourceEnd() {
// return super.sourceEnd();
// }
// @Override
// public int sourceStart() {
// return super.sourceStart();
// }
// @Override
// public String toString() {
// // FIXASC anything to add?
// return super.toString();
// }
// @Override
// public void traverse(ASTVisitor visitor, BlockScope scope) {
// // FIXASC in a good state for traversal? what would cause this to trigger?
// super.traverse(visitor, scope);
// }
*/
public void tagAsScript() {
this.isScript = true;
}
//------------------------------------------------------------------------------------------------------------------
/**
* Helps check if some class node is trait.
*/
private class TraitHelper {
private boolean toBeInitialized = true;
private boolean lookForTraitAlias = false;
private void initialize() {
if (imports != null) {
for (ImportReference i : imports) {
String importedType = i.toString();
if ("groovy.transform.Trait".equals(importedType)) {
lookForTraitAlias = true;
break;
}
if (importedType.endsWith(".Trait")) {
lookForTraitAlias = false;
break;
}
if ("groovy.transform.*".equals(importedType)) {
lookForTraitAlias = true;
}
}
toBeInitialized = true;
}
}
private boolean isTrait(ClassNode classNode) {
if (classNode == null) {
return false;
}
if (toBeInitialized) {
initialize();
}
List<AnnotationNode> annotations = classNode.getAnnotations();
if (annotations.size() > 0) {
for (AnnotationNode annotation : annotations) {
if ("groovy.transform.Trait".equals(annotation.getClassNode().getName())) {
return true;
}
if (lookForTraitAlias && "Trait".equals(annotation.getClassNode().getName())) {
return true;
}
}
}
return false;
}
}
/**
* Support code for {@link #populateCompilationUnitDeclaration}.
*/
public static class UnitPopulator {
private Janitor janitor;
private SourceUnit sourceUnit;
private GroovyCompilationUnitDeclaration unitDeclaration;
private boolean hasAnonInners;
private boolean checkGenerics = defaultCheckGenerics;
/**
* Keeps track of anonymous inner type outer methods.
* NOTE: Only used if hasAnonInners is {@code true}.
*/
private Map<MethodNode, AbstractMethodDeclaration> enclosingMethodMap;
void populate(GroovyCompilationUnitDeclaration target, SourceUnit source) {
unitDeclaration = target;
sourceUnit = source;
ModuleNode moduleNode = sourceUnit.getAST();
try {
createPackageDeclaration(moduleNode);
createImportDeclarations(moduleNode);
createTypeDeclarations(moduleNode);
} finally {
if (janitor != null) {
janitor.cleanup();
janitor = null;
}
}
}
private void createPackageDeclaration(ModuleNode moduleNode) {
if (moduleNode.hasPackageName()) {
String packageName = moduleNode.getPackageName();
if (packageName.endsWith(".")) {
packageName = packageName.substring(0, packageName.length() - 1);
}
PackageNode packageNode = moduleNode.getPackage();
char[][] splits = CharOperation.splitOn('.', packageName.toCharArray());
long[] positions = positionsFor(splits, startOffset(packageNode), endOffset(packageNode));
ImportReference ref = new ImportReference(splits, positions, true, ClassFileConstants.AccDefault);
ref.declarationEnd = ref.sourceEnd + trailerLength(packageNode);
ref.declarationSourceStart = ref.sourceStart - 8; // "package ".length()
ref.declarationSourceEnd = ref.sourceEnd;
unitDeclaration.currentPackage = ref;
}
}
private void createImportDeclarations(ModuleNode moduleNode) {
List<ImportNode> importNodes = moduleNode.getImports();
List<ImportNode> importPackages = moduleNode.getStarImports();
Map<String, ImportNode> importStatics = moduleNode.getStaticImports();
Map<String, ImportNode> importStaticStars = moduleNode.getStaticStarImports();
int importCount = importNodes.size() + importPackages.size() + importStatics.size() + importStaticStars.size();
if (importCount > 0) {
List<ImportReference> importReferences = new ArrayList<ImportReference>(importCount);
// type imports
for (ImportNode importNode : importNodes) {
int typeStartOffset = importNode.getTypeStart(),
typeEndOffset = importNode.getTypeEnd(),
endOffset = typeEndOffset;
if (typeStartOffset == 0) {
// incomplete import created during recovery
continue;
}
char[][] splits = CharOperation.splitOn('.', importNode.getClassName().toCharArray());
ImportReference ref;
if (importNode.getAlias() == null || importNode.getAlias().length() < 1) {
long[] positions = positionsFor(splits, typeStartOffset, typeEndOffset);
ref = new ImportReference(splits, positions, false, ClassFileConstants.AccDefault);
} else {
endOffset = endOffset(importNode);
long[] positions = positionsFor(splits, typeStartOffset, endOffset);
ref = new AliasImportReference(importNode.getAlias().toCharArray(), splits, positions, false, ClassFileConstants.AccDefault);
}
ref.sourceEnd = Math.max(endOffset - 1, ref.sourceStart); // For error reporting, Eclipse wants -1
if (ref.sourceEnd < 0) {
// synthetic node; set all source positions to "unknown"
ref.declarationSourceStart = -1;
ref.declarationSourceEnd = -2;
ref.declarationEnd = -2;
ref.sourceEnd = -2;
} else {
ref.declarationEnd = ref.sourceEnd + trailerLength(importNode);
ref.declarationSourceStart = startOffset(importNode);
ref.declarationSourceEnd = ref.sourceEnd;
}
importReferences.add(ref);
}
// star imports
for (ImportNode importPackage : importPackages) {
String importText = importPackage.getText();
// when calculating these offsets, assume no extraneous whitespace
int packageStartOffset = importPackage.getStart() + "import ".length(),
packageEndOffset = packageStartOffset + importText.length() - "import ".length() - ".*".length(),
endOffset = endOffset(importPackage);
char[][] splits = CharOperation.splitOn('.', importPackage.getPackageName().substring(0, importPackage.getPackageName().length() - 1).toCharArray());
ImportReference ref = new ImportReference(splits, positionsFor(splits, packageStartOffset, packageEndOffset), true, ClassFileConstants.AccDefault);
// import * style only have slocs for the entire ImportNode and not for the embedded type
ref.sourceEnd = Math.max(endOffset - 1, ref.sourceStart); // For error reporting, Eclipse wants -1
ref.declarationEnd = ref.sourceEnd + trailerLength(importPackage);
ref.declarationSourceStart = importPackage.getStart();
ref.declarationSourceEnd = ref.sourceEnd;
importReferences.add(ref);
}
// static imports
for (Map.Entry<String, ImportNode> importStatic : importStatics.entrySet()) {
ImportNode importNode = importStatic.getValue();
char[][] splits = CharOperation.splitOn('.', (importNode.getClassName() + '.' + importNode.getFieldName()).toCharArray());
int typeStartOffset = importNode.getTypeStart(),
typeEndOffset = importNode.getTypeEnd(),
endOffset = typeEndOffset;
ImportReference ref = null;
if (importNode.getAlias() == null || importNode.getAlias().length() < 1) {
long[] positions = positionsFor(splits, typeStartOffset, typeEndOffset);
ref = new ImportReference(splits, positions, false, ClassFileConstants.AccDefault | ClassFileConstants.AccStatic);
} else {
endOffset = endOffset(importNode);
long[] positions = positionsFor(splits, typeStartOffset, endOffset);
ref = new AliasImportReference(importNode.getAlias().toCharArray(), splits, positions, false, ClassFileConstants.AccDefault | ClassFileConstants.AccStatic);
}
ref.sourceEnd = Math.max(endOffset - 1, ref.sourceStart); // For error reporting, Eclipse wants -1
ref.declarationEnd = ref.sourceEnd + trailerLength(importNode);
ref.declarationSourceStart = startOffset(importNode);
ref.declarationSourceEnd = ref.sourceEnd;
importReferences.add(ref);
}
// static star imports
for (Map.Entry<String, ImportNode> importStaticStar : importStaticStars.entrySet()) {
String classname = importStaticStar.getKey();
ImportNode importNode = importStaticStar.getValue();
int typeStartOffset = Math.max(0, importNode.getTypeStart()),
typeEndOffset = Math.max(0, importNode.getTypeEnd()),
endOffset = endOffset(importNode);
char[][] splits = CharOperation.splitOn('.', classname.toCharArray());
long[] positions = positionsFor(splits, typeStartOffset, typeEndOffset);
ImportReference ref = new ImportReference(splits, positions, true, ClassFileConstants.AccDefault | ClassFileConstants.AccStatic);
ref.sourceEnd = Math.max(endOffset - 1, ref.sourceStart); // For error reporting, Eclipse wants -1
ref.declarationEnd = ref.sourceEnd + trailerLength(importNode);
ref.declarationSourceStart = importNode.getStart();
ref.declarationSourceEnd = ref.sourceEnd;
importReferences.add(ref);
}
// ensure proper lexical order
if (!importReferences.isEmpty()) {
ImportReference[] refs = importReferences.toArray(new ImportReference[importReferences.size()]);
Arrays.sort(refs, new Comparator<ImportReference>() {
public int compare(ImportReference left, ImportReference right) {
return left.sourceStart - right.sourceStart;
}
});
for (ImportReference ref : refs) {
if (ref.declarationSourceStart > 0 && (ref.declarationEnd - ref.declarationSourceStart + 1) < 0) {
throw new IllegalStateException("Import reference alongside class " + moduleNode.getClasses().get(0)
+ " will trigger later failure: " + ref.toString() + " declSourceStart="
+ ref.declarationSourceStart + " declEnd=" + ref.declarationEnd);
}
}
unitDeclaration.imports = refs;
}
}
}
private void createTypeDeclarations(ModuleNode moduleNode) {
List<ClassNode> moduleClassNodes = moduleNode.getClasses();
hasAnonInners = false;
for (ClassNode classNode : moduleClassNodes) {
if (isAnon(classNode)) {
hasAnonInners = true;
enclosingMethodMap = new HashMap<MethodNode, AbstractMethodDeclaration>();
break;
}
}
List<TypeDeclaration> typeDeclarations = new ArrayList<TypeDeclaration>();
Map<ClassNode, TypeDeclaration> fromClassNodeToDecl = new HashMap<ClassNode, TypeDeclaration>();
CompilationResult compilationResult = unitDeclaration.compilationResult;
char[] mainName = toMainName(compilationResult.getFileName());
boolean isInner = false;
List<ClassNode> classNodes = null;
classNodes = moduleClassNodes;
Map<ClassNode, List<TypeDeclaration>> innersToRecord = new HashMap<ClassNode, List<TypeDeclaration>>();
for (ClassNode classNode : classNodes) {
if (!classNode.isPrimaryClassNode()) {
continue;
}
GroovyTypeDeclaration typeDeclaration = new GroovyTypeDeclaration(compilationResult, classNode);
typeDeclaration.annotations = createAnnotations(classNode.getAnnotations());
if (classNode instanceof InnerClassNode) {
isInner = true;
} else {
typeDeclaration.name = classNode.getNameWithoutPackage().toCharArray();
if (!CharOperation.equals(typeDeclaration.name, mainName)) {
typeDeclaration.bits |= ASTNode.IsSecondaryType;
}
isInner = false;
}
boolean isInterface = classNode.isInterface();
boolean isEnum = (classNode.getModifiers() & Opcodes.ACC_ENUM) != 0;
int mods = classNode.getModifiers();
if (isTrait(classNode)) {
mods |= Opcodes.ACC_INTERFACE;
}
if ((mods & Opcodes.ACC_ENUM) != 0) {
// remove final
mods = mods & ~Opcodes.ACC_FINAL;
}
// FIXASC should this modifier be set?
// mods |= Opcodes.ACC_PUBLIC;
// FIXASC should not do this for inner classes, just for top level types
// FIXASC does this make things visible that shouldn't be?
mods = mods & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
if (!isInner) {
if ((mods & Opcodes.ACC_STATIC) != 0) {
mods = mods & ~(Opcodes.ACC_STATIC);
}
}
typeDeclaration.modifiers = mods & ~((isInterface || isEnum) ? Opcodes.ACC_ABSTRACT : 0);
fixupSourceLocationsForTypeDeclaration(typeDeclaration, classNode);
GenericsType[] generics = classNode.getGenericsTypes();
if (generics != null && generics.length > 0) {
typeDeclaration.typeParameters = createTypeParametersForGenerics(classNode.getGenericsTypes());
}
configureSuperClass(typeDeclaration, classNode.getSuperClass(), isEnum, isTrait(classNode));
configureSuperInterfaces(typeDeclaration, classNode);
typeDeclaration.methods = createMethodAndConstructorDeclarations(classNode, isEnum, typeDeclaration, compilationResult);
typeDeclaration.fields = createFieldDeclarations(classNode, isEnum);
typeDeclaration.properties = classNode.getProperties();
if (classNode instanceof InnerClassNode) {
InnerClassNode innerClassNode = (InnerClassNode) classNode;
ClassNode outerClass = innerClassNode.getOuterClass();
String outername = outerClass.getNameWithoutPackage();
String newInner = innerClassNode.getNameWithoutPackage().substring(outername.length() + 1);
typeDeclaration.name = newInner.toCharArray();
// Record that we need to set the parent of this inner type later
List<TypeDeclaration> inners = innersToRecord.get(outerClass);
if (inners == null) {
inners = new ArrayList<TypeDeclaration>();
innersToRecord.put(outerClass, inners);
}
inners.add(typeDeclaration);
if (isAnon(classNode)) {
typeDeclaration.bits |= (ASTNode.IsAnonymousType | ASTNode.IsLocalType);
typeDeclaration.allocation = new QualifiedAllocationExpression(typeDeclaration);
typeDeclaration.allocation.enclosingInstance = new NullLiteral(typeDeclaration.sourceStart, typeDeclaration.sourceEnd);
typeDeclaration.allocation.sourceStart = typeDeclaration.sourceStart;
typeDeclaration.allocation.sourceEnd = typeDeclaration.bodyEnd;
typeDeclaration.allocation.statementEnd = typeDeclaration.bodyEnd;
typeDeclaration.allocation.type = typeDeclaration.superclass;
typeDeclaration.name = CharOperation.NO_CHAR;
}
} else {
typeDeclarations.add(typeDeclaration);
}
fromClassNodeToDecl.put(classNode, typeDeclaration);
}
// For inner types, now attach them to their parents. This was not done earlier as sometimes the types are processed in
// such an order that inners are dealt with before outers
for (Map.Entry<ClassNode, List<TypeDeclaration>> innersToRecordEntry : innersToRecord.entrySet()) {
ClassNode outer = innersToRecordEntry.getKey();
TypeDeclaration outerTypeDeclaration = fromClassNodeToDecl.get(outer);
// Check if there is a problem locating the parent for the inner
if (outerTypeDeclaration == null) {
throw new GroovyEclipseBug("Failed to find the type declaration for " + outer.getText());
}
List<TypeDeclaration> newInnersList = innersToRecordEntry.getValue();
for (Iterator<TypeDeclaration> iterator = newInnersList.iterator(); iterator.hasNext();) {
GroovyTypeDeclaration inner = (GroovyTypeDeclaration) iterator.next();
if ((inner.bits & ASTNode.IsAnonymousType) > 0) {
iterator.remove();
MethodNode enclosingMethodGroovy = inner.getClassNode().getEnclosingMethod();
if (enclosingMethodGroovy == null) {
// probably an anon type inside a script
ClassNode outerClass = inner.getClassNode().getOuterClass();
enclosingMethodGroovy = outerClass.getMethod("run", Parameter.EMPTY_ARRAY);
if (enclosingMethodGroovy == null) {
throw new GroovyEclipseBug("Failed to find the enclosing method for anonymous type " + inner.getClassNode().getName());
}
}
AbstractMethodDeclaration enclosingMethodJDT = enclosingMethodMap.get(enclosingMethodGroovy);
enclosingMethodJDT.bits |= ASTNode.HasLocalType;
inner.enclosingMethod = enclosingMethodJDT;
// just a dummy scope to be filled in for real later. needed for structure requesting
enclosingMethodJDT.scope = new MethodScope(outerTypeDeclaration.scope, enclosingMethodJDT, enclosingMethodJDT.isStatic());
if (inner.enclosingMethod.statements == null || inner.enclosingMethod.statements.length == 0) {
inner.enclosingMethod.statements = new Statement[] { inner.allocation };
} else {
Statement[] newStatements = new Statement[inner.enclosingMethod.statements.length + 1];
System.arraycopy(inner.enclosingMethod.statements, 0, newStatements, 0, inner.enclosingMethod.statements.length);
newStatements[inner.enclosingMethod.statements.length] = inner.allocation;
inner.enclosingMethod.statements = newStatements;
}
((GroovyTypeDeclaration) outerTypeDeclaration).addAnonymousType(inner);
}
}
outerTypeDeclaration.memberTypes = newInnersList.toArray(new TypeDeclaration[newInnersList.size()]);
}
// clean up
enclosingMethodMap = null;
unitDeclaration.types = typeDeclarations.toArray(new TypeDeclaration[typeDeclarations.size()]);
}
/**
* Build JDT representations of all the fields on the groovy type. <br>
* Enum field handling<br>
* Groovy handles them as follows: they have the ACC_ENUM bit set and the type is the type of the declaring enum type. When
* building declarations, if you want the SourceTypeBinding to correctly build an enum field binding (in
* SourceTypeBinding.resolveTypeFor(FieldBinding)) then you need to: (1) avoid setting modifiers, the enum fields are not
* expected to have any modifiers (2) leave the type as null, that is how these things are identified by JDT.
*/
private FieldDeclaration[] createFieldDeclarations(ClassNode classNode, boolean isEnum) {
List<FieldDeclaration> fieldDeclarations = new ArrayList<FieldDeclaration>();
List<FieldNode> fieldNodes = classNode.getFields();
boolean isTrait = isTrait(classNode);
if (fieldNodes != null) {
for (FieldNode fieldNode : fieldNodes) {
if (isTrait && !(fieldNode.isPublic() && fieldNode.isStatic() && fieldNode.isFinal())) {
continue;
}
if (isEnum && (fieldNode.getName().equals("MAX_VALUE") || fieldNode.getName().equals("MIN_VALUE"))) {
continue;
}
boolean isEnumField = (fieldNode.getModifiers() & Opcodes.ACC_ENUM) != 0;
boolean isSynthetic = (fieldNode.getModifiers() & Opcodes.ACC_SYNTHETIC) != 0;
if (!isSynthetic) {
// JavaStubGenerator ignores private fields but I don't think we want to here
FieldDeclarationWithInitializer fieldDeclaration =
new FieldDeclarationWithInitializer(fieldNode.getName().toCharArray(), 0, 0);
fieldDeclaration.annotations = createAnnotations(fieldNode.getAnnotations());
if (!isEnumField) {
fieldDeclaration.modifiers = fieldNode.getModifiers() & ~Opcodes.ACC_ENUM; // Seems like this has already been taken away
fieldDeclaration.type = createTypeReferenceForClassNode(fieldNode.getType());
if (fieldNode.isStatic() && fieldNode.isFinal() &&
fieldNode.getInitialExpression() instanceof ConstantExpression) {
// this needs to be set for static finals to correctly determine constant status
fieldDeclaration.initialization = createConstantExpression((ConstantExpression) fieldNode.getInitialExpression());
}
}
fieldDeclaration.initializer = fieldNode.getInitialExpression();
fixupSourceLocationsForFieldDeclaration(fieldDeclaration, fieldNode, isEnumField);
fieldDeclarations.add(fieldDeclaration);
}
}
}
return fieldDeclarations.toArray(new FieldDeclaration[fieldDeclarations.size()]);
}
/**
* Build JDT representations of all the method/ctors on the groovy type.
*/
private AbstractMethodDeclaration[] createMethodAndConstructorDeclarations(ClassNode classNode, boolean isEnum,
GroovyTypeDeclaration typeDeclaration, CompilationResult compilationResult) {
List<AbstractMethodDeclaration> accumulatedDeclarations = new ArrayList<AbstractMethodDeclaration>();
createConstructorDeclarations(classNode, isEnum, accumulatedDeclarations);
createMethodDeclarations(classNode, isEnum, typeDeclaration, accumulatedDeclarations);
return accumulatedDeclarations.toArray(new AbstractMethodDeclaration[accumulatedDeclarations.size()]);
}
/**
* Build JDT representations of all the constructors on the groovy type.
*/
private void createConstructorDeclarations(ClassNode classNode, boolean isEnum,
List<AbstractMethodDeclaration> accumulatedMethodDeclarations) {
if (isTrait(classNode)) {
return;
}
List<ConstructorNode> constructorNodes = classNode.getDeclaredConstructors();
char[] ctorName = null;
if (classNode instanceof InnerClassNode) {
InnerClassNode innerClassNode = (InnerClassNode) classNode;
ClassNode outerClass = innerClassNode.getOuterClass();
String outername = outerClass.getNameWithoutPackage();
String newInner = innerClassNode.getNameWithoutPackage().substring(outername.length() + 1);
ctorName = newInner.toCharArray();
} else {
ctorName = classNode.getNameWithoutPackage().toCharArray();
}
// Do we need a default constructor?
boolean needsDefaultCtor = constructorNodes.size() == 0 && !classNode.isInterface();
if (needsDefaultCtor) {
ConstructorDeclaration constructor = new ConstructorDeclaration(unitDeclaration.compilationResult);
constructor.bits |= ASTNode.IsDefaultConstructor;
if (isEnum) {
constructor.modifiers = ClassFileConstants.AccPrivate;
} else {
constructor.modifiers = ClassFileConstants.AccPublic;
}
constructor.selector = ctorName;
accumulatedMethodDeclarations.add(constructor);
}
for (ConstructorNode constructorNode : constructorNodes) {
ConstructorDeclaration constructorDeclaration = new ConstructorDeclaration(unitDeclaration.compilationResult);
fixupSourceLocationsForConstructorDeclaration(constructorDeclaration, constructorNode);
constructorDeclaration.annotations = createAnnotations(constructorNode.getAnnotations());
// FIXASC should we just use the constructor node modifiers or does groovy make all constructors public apart from those on enums?
constructorDeclaration.modifiers = isEnum ? ClassFileConstants.AccPrivate : ClassFileConstants.AccPublic;
constructorDeclaration.selector = ctorName;
constructorDeclaration.arguments = createArguments(constructorNode.getParameters(), false);
constructorDeclaration.thrownExceptions = createTypeReferencesForClassNodes(constructorNode.getExceptions());
if (constructorNode.hasDefaultValue()) {
createConstructorVariants(constructorNode, constructorDeclaration, accumulatedMethodDeclarations, unitDeclaration.compilationResult, isEnum);
} else {
accumulatedMethodDeclarations.add(constructorDeclaration);
}
if (hasAnonInners) {
enclosingMethodMap.put(constructorNode, constructorDeclaration);
}
}
if (earlyTransforms) {
executeEarlyTransforms_ConstructorRelated(ctorName, classNode, accumulatedMethodDeclarations);
}
}
/**
* Build JDT representations of all the methods on the groovy type
*
* @param typeDeclaration the type declaration the method is being created for
*/
private void createMethodDeclarations(ClassNode classNode, boolean isEnum,
GroovyTypeDeclaration typeDeclaration, List<AbstractMethodDeclaration> accumulatedDeclarations) {
boolean isTrait = isTrait(classNode);
List<MethodNode> methods = classNode.getMethods();
for (MethodNode methodNode : methods) {
if (isTrait && (methodNode.isPrivate() || methodNode.isStatic())) {
continue;
}
if (isEnum && methodNode.isSynthetic()) {
// skip synthetic methods in enums
continue;
// String name = methodNode.getName();
// Parameter[] params = methodNode.getParameters();
// if (name.equals("values") && params.length == 0) {
// continue;
// }
// if (name.equals("valueOf") && params.length == 1 && params[0].getType().equals(ClassHelper.STRING_TYPE)) {
// continue;
// }
}
MethodDeclaration methodDeclaration = createMethodDeclaration(classNode, isEnum, methodNode, unitDeclaration.compilationResult);
if (methodNode.isAbstract()) {
typeDeclaration.bits |= ASTNode.HasAbstractMethods;
}
if (methodNode.hasDefaultValue()) {
createMethodVariants(classNode, methodNode, isEnum, methodDeclaration, accumulatedDeclarations, unitDeclaration.compilationResult);
} else {
accumulatedDeclarations.add(methodDeclaration);
}
if (hasAnonInners) {
enclosingMethodMap.put(methodNode, methodDeclaration);
}
}
}
/**
* Create a JDT MethodDeclaration that represents a groovy MethodNode
*/
private MethodDeclaration createMethodDeclaration(ClassNode classNode, boolean isEnum,
MethodNode methodNode, CompilationResult compilationResult) {
if (classNode.isAnnotationDefinition()) {
AnnotationMethodDeclaration methodDeclaration = new AnnotationMethodDeclaration(compilationResult);
int modifiers = methodNode.getModifiers();
modifiers &= ~(ClassFileConstants.AccSynthetic | ClassFileConstants.AccTransient);
methodDeclaration.annotations = createAnnotations(methodNode.getAnnotations());
methodDeclaration.modifiers = modifiers;
if (methodNode.hasAnnotationDefault()) {
methodDeclaration.modifiers |= ClassFileConstants.AccAnnotationDefault;
}
methodDeclaration.selector = methodNode.getName().toCharArray();
fixupSourceLocationsForMethodDeclaration(methodDeclaration, methodNode);
ClassNode returnType = methodNode.getReturnType();
methodDeclaration.returnType = createTypeReferenceForClassNode(returnType);
return methodDeclaration;
} else {
MethodDeclaration methodDeclaration = new MethodDeclaration(compilationResult);
GenericsType[] generics = methodNode.getGenericsTypes();
if (generics != null && generics.length > 0) {
methodDeclaration.typeParameters = createTypeParametersForGenerics(generics);
}
boolean isMain = false;
// Note: modifiers for the MethodBinding constructed for this declaration will be created marked with
// AccVarArgs if the bitset for the type reference in the final argument is marked IsVarArgs
int modifiers = methodNode.getModifiers();
modifiers &= ~(ClassFileConstants.AccSynthetic | ClassFileConstants.AccTransient);
methodDeclaration.annotations = createAnnotations(methodNode.getAnnotations());
methodDeclaration.modifiers = modifiers;
methodDeclaration.selector = methodNode.getName().toCharArray();
Parameter[] params = methodNode.getParameters();
ClassNode returnType = methodNode.getReturnType();
// source of 'static main(args)' would become 'static Object main(Object args)' - so transform here
if ((modifiers & ClassFileConstants.AccStatic) != 0 && params != null && params.length == 1 && methodNode.getName().equals("main")) {
Parameter p = params[0];
if (p.getType() == null || p.getType().getName().equals(ClassHelper.OBJECT)) {
String name = p.getName();
params = new Parameter[1];
params[0] = new Parameter(ClassHelper.STRING_TYPE.makeArray(), name);
if (returnType.getName().equals(ClassHelper.OBJECT)) {
returnType = ClassHelper.VOID_TYPE;
}
}
}
methodDeclaration.arguments = createArguments(params, isMain);
methodDeclaration.returnType = createTypeReferenceForClassNode(returnType);
methodDeclaration.thrownExceptions = createTypeReferencesForClassNodes(methodNode.getExceptions());
fixupSourceLocationsForMethodDeclaration(methodDeclaration, methodNode);
return methodDeclaration;
}
}
/**
* Called if a constructor has some 'defaulting' arguments and will compute all the variants (including the one with all
* parameters).
*/
private void createConstructorVariants(ConstructorNode constructorNode, ConstructorDeclaration constructorDecl,
List<AbstractMethodDeclaration> accumulatedDeclarations, CompilationResult compilationResult, boolean isEnum) {
List<Argument[]> variants = getVariantsAllowingForDefaulting(constructorNode.getParameters(), constructorDecl.arguments);
for (Argument[] variant : variants) {
ConstructorDeclaration constructorDeclaration = new ConstructorDeclaration(compilationResult);
constructorDeclaration.annotations = createAnnotations(constructorNode.getAnnotations());
constructorDeclaration.modifiers = isEnum ? ClassFileConstants.AccPrivate : ClassFileConstants.AccPublic;
constructorDeclaration.selector = constructorDecl.selector;
constructorDeclaration.arguments = variant;
fixupSourceLocationsForConstructorDeclaration(constructorDeclaration, constructorNode);
addUnlessDuplicate(accumulatedDeclarations, constructorDeclaration);
}
}
/**
* Called if a method has some 'defaulting' arguments and will compute all the variants (including the one with all parameters).
*/
private void createMethodVariants(ClassNode classNode, MethodNode method, boolean isEnum, MethodDeclaration methodDecl,
List<AbstractMethodDeclaration> accumulatedDeclarations, CompilationResult compilationResult) {
List<Argument[]> variants = getVariantsAllowingForDefaulting(method.getParameters(), methodDecl.arguments);
for (Argument[] variant : variants) {
MethodDeclaration variantMethodDeclaration = genMethodDeclarationVariant(classNode, method, isEnum, variant,
methodDecl.returnType, compilationResult);
addUnlessDuplicate(accumulatedDeclarations, variantMethodDeclaration);
}
}
//--------------------------------------------------------------------------------------------------------------
private void configureSuperClass(TypeDeclaration typeDeclaration, ClassNode superclass, boolean isEnum, boolean isTrait) {
if (isEnum && superclass.getName().equals("java.lang.Enum") || isTrait) {
// Don't wire it in, JDT will do it
typeDeclaration.superclass = null;
} else {
// If the start position is 0 the superclass wasn't actually declared, it was added by Groovy
if (!(superclass.getStart() == 0 && superclass.equals(ClassHelper.OBJECT_TYPE))) {
typeDeclaration.superclass = createTypeReferenceForClassNode(superclass);
}
}
}
private void configureSuperInterfaces(TypeDeclaration typeDeclaration, ClassNode classNode) {
ClassNode[] interfaces = classNode.getInterfaces();
if (interfaces != null && interfaces.length > 0) {
typeDeclaration.superInterfaces = new TypeReference[interfaces.length];
for (int i = 0; i < interfaces.length; i++) {
typeDeclaration.superInterfaces[i] = createTypeReferenceForClassNode(interfaces[i]);
}
} else {
typeDeclaration.superInterfaces = new TypeReference[0];
}
}
private Annotation[] createAnnotations(List<AnnotationNode> groovyAnnotations) {
if (groovyAnnotations != null && !groovyAnnotations.isEmpty()) {
List<Annotation> annotations = new ArrayList<Annotation>(groovyAnnotations.size());
for (AnnotationNode annotationNode : groovyAnnotations) {
ClassNode annoType = annotationNode.getClassNode();
TypeReference annotationReference = createTypeReferenceForClassNode(annoType);
annotationReference.sourceStart = annotationNode.getStart();
annotationReference.sourceEnd = annotationNode.getEnd();
Map<String, Expression> memberValuePairs = annotationNode.getMembers();
if (memberValuePairs == null || memberValuePairs.isEmpty()) {
MarkerAnnotation annotation = new MarkerAnnotation(annotationReference, annotationNode.getStart());
annotation.declarationSourceEnd = annotation.sourceEnd;
annotations.add(annotation);
} else if (memberValuePairs.size() == 1 && memberValuePairs.containsKey("value")) {
Map.Entry<String, Expression> memberValuePair = memberValuePairs.entrySet().iterator().next();
Expression value = memberValuePair.getValue();
//if (value instanceof AnnotationConstantExpression) {
// Annotation[] containees = createAnnotations(Collections.singletonList(
// (AnnotationNode) ((AnnotationConstantExpression) value).getValue()));
// ContainerAnnotation annotation = new ContainerAnnotation(containees[0], null, null);
// annotation.declarationSourceEnd = annotation.sourceStart + annoType.getNameWithoutPackage().length() - 1; // TODO: What if name is qualified?
// annotations.add(annotation);
//} else {
SingleMemberAnnotation annotation = new SingleMemberAnnotation(annotationReference, annotationNode.getStart());
annotation.declarationSourceEnd = annotation.sourceStart + annoType.getNameWithoutPackage().length() - 1; // TODO: What if name is qualified?
annotation.memberValue = createAnnotationMemberExpression(value);
annotations.add(annotation);
//}
} else {
NormalAnnotation annotation = new NormalAnnotation(annotationReference, annotationNode.getStart());
annotation.declarationSourceEnd = annotation.sourceStart + annoType.getNameWithoutPackage().length() - 1; // TODO: What if name is qualified?
annotation.memberValuePairs = createAnnotationMemberValuePairs(memberValuePairs);
annotations.add(annotation);
}
}
return annotations.toArray(new Annotation[annotations.size()]);
}
return null;
}
private org.eclipse.jdt.internal.compiler.ast.Expression createAnnotationMemberExpression(Expression expr) {
if (expr instanceof ListExpression) {
ListExpression list = (ListExpression) expr;
ArrayInitializer arrayInitializer = new ArrayInitializer();
arrayInitializer.sourceStart = expr.getStart();
arrayInitializer.sourceEnd = expr.getEnd();
int n = list.getExpressions().size();
arrayInitializer.expressions = new org.eclipse.jdt.internal.compiler.ast.Expression[n];
for (int i = 0; i < n; i += 1) {
arrayInitializer.expressions[i] = createAnnotationMemberExpression(list.getExpression(i));
}
return arrayInitializer;
} else if (expr instanceof AnnotationConstantExpression) {
Annotation[] annos = createAnnotations(Collections.singletonList(
(AnnotationNode) ((AnnotationConstantExpression) expr).getValue()));
assert annos != null && annos.length == 1;
return annos[0];
} else if (expr instanceof ConstantExpression) {
Literal literal = createConstantExpression((ConstantExpression) expr);
if (literal != null) return literal;
} else if (expr instanceof PropertyExpression) {
PropertyExpression prop = (PropertyExpression) expr;
assert prop.getProperty() instanceof ConstantExpression;
if (prop.getPropertyAsString().equals("class")) {
return new ClassLiteralAccess(expr.getEnd(), createTypeReferenceForClassLiteral(prop));
}
// could still be a class literal; Groovy does not require ".class" -- resolved in MemberValuePair
char[][] tokens = CharOperation.splitOn('.', prop.getText().toCharArray());
// guess the intermediate positions based on start offset and token lengths
int n = tokens.length, s = prop.getObjectExpression().getStart();
long[] positions = new long[n];
for (int i = 0; i < n; i += 1) {
positions[i] = toPos(s, s + tokens[i].length - 1);
s += tokens[i].length;
}
assert s <= expr.getEnd();
return new QualifiedNameReference(tokens, positions, expr.getStart(), expr.getEnd());
} else if (expr instanceof VariableExpression) {
String name = ((VariableExpression) expr).getName();
// could be a class literal; Groovy does not require ".class" -- resolved in MemberValuePair
return new SingleNameReference(name.toCharArray(), toPos(expr.getStart(), expr.getEnd() - 1));
} else if (expr instanceof ClosureExpression) {
// annotation is something like "@Tag(value = { some computation })" return "Closure.class" to appease JDT
return new ClassLiteralAccess(expr.getEnd(), new SingleTypeReference("Closure".toCharArray(), toPos(expr.getStart(), expr.getEnd())));
} else {
Util.log(IStatus.WARNING, "Unhandled annotation value type: " + expr.getClass().getSimpleName());
}
// must be non-null or there will be NPEs in MVP
return new NullLiteral(expr.getStart(), expr.getEnd());
}
private org.eclipse.jdt.internal.compiler.ast.MemberValuePair[] createAnnotationMemberValuePairs(Map<String, Expression> memberValuePairs) {
List<org.eclipse.jdt.internal.compiler.ast.MemberValuePair> mvps =
new ArrayList<org.eclipse.jdt.internal.compiler.ast.MemberValuePair>(memberValuePairs.size());
for (Map.Entry<String, Expression> memberValuePair : memberValuePairs.entrySet()) {
char[] name = memberValuePair.getKey().toCharArray();
int start = memberValuePair.getValue().getStart() - name.length - 1, until = memberValuePair.getValue().getEnd();
org.eclipse.jdt.internal.compiler.ast.Expression value = createAnnotationMemberExpression(memberValuePair.getValue());
mvps.add(new org.eclipse.jdt.internal.compiler.ast.MemberValuePair(name, start, until, value));
}
return mvps.toArray(new org.eclipse.jdt.internal.compiler.ast.MemberValuePair[mvps.size()]);
}
private Literal createConstantExpression(ConstantExpression expr) {
int start = expr.getStart(), until = expr.getEnd();
String type = expr.getType().getName();
Object value = expr.getValue();
if (type.equals("java.lang.Object")) {
assert value == null;
return new NullLiteral(start, until);
} else if (type.equals("java.lang.String")) {
return new StringLiteral(((String) value).toCharArray(), start, until - 1, expr.getLineNumber());
} else if (type.equals("boolean") || type.equals("java.lang.Boolean")) {
if (value.toString().equals("true")) {
return new TrueLiteral(start, until);
} else {
return new FalseLiteral(start, until);
}
} else if (type.equals("int") || type.equals("java.lang.Integer")) {
// TODO: Should return a unary minus expression for negative values...
char[] chars = String.valueOf(Math.abs((Integer) value)).toCharArray();
return IntLiteral.buildIntLiteral(chars, start, start + chars.length);
} else if (type.equals("long") || type.equals("java.lang.Long")) {
char[] chars = (String.valueOf(Math.abs((Long) value)) + 'L').toCharArray();
return LongLiteral.buildLongLiteral(chars, start, start + chars.length);
} else if (type.equals("double") || type.equals("java.lang.Double")) {
return new DoubleLiteral(value.toString().toCharArray(), start, until);
} else if (type.equals("float") || type.equals("java.lang.Float")) {
return new FloatLiteral(value.toString().toCharArray(), start, until);
}/* else if (type.equals("byte") || type.equals("java.lang.Byte") ||
type.equals("short") || type.equals("java.lang.Short")) {
char[] chars = value.toString().toCharArray();
return IntLiteral.buildIntLiteral(chars, start, start + chars.length);
}*/ else if (type.equals("char") || type.equals("java.lang.Character")) {
return new CharLiteral(value.toString().toCharArray(), start, until);
} else if (type.equals("java.math.BigDecimal")) {
return new DoubleLiteral(value.toString().toCharArray(), start, until);
} else if (type.equals("java.math.BigInteger")) {
long thing = ((BigInteger) value).abs().longValue();
char[] chars = (String.valueOf(thing) + 'L').toCharArray();
return LongLiteral.buildLongLiteral(chars, start, start + chars.length);
} else {
Util.log(IStatus.WARNING, "Unhandled annotation constant type: " + expr.getType().getName());
return null;
}
}
/**
* Creates JDT Argument representations of Groovy parameters.
*/
private Argument[] createArguments(Parameter[] ps, boolean isMain) {
if (ps == null || ps.length == 0) {
return null;
}
Argument[] arguments = new Argument[ps.length];
for (int i = 0; i < ps.length; i++) {
Parameter parameter = ps[i];
TypeReference parameterTypeReference = createTypeReferenceForClassNode(parameter.getType());
long pos;
int pstart = parameter.getStart();
if (parameter.getStart() == 0 && parameter.getEnd() == 0) {
pos = toPos(-1, -2);
pstart = -1;
} else {
pos = toPos(parameter.getStart(), parameter.getEnd() - 1);
}
arguments[i] = new Argument(parameter.getName().toCharArray(), pos, parameterTypeReference, ClassFileConstants.AccPublic);
arguments[i].declarationSourceStart = pstart;
}
if (isVargs(ps)) {
arguments[ps.length - 1].type.bits |= ASTNode.IsVarArgs;
}
return arguments;
}
/**
* Creates JDT TypeReference that represents the given array ClassNode.
* The name of the node is expected to be like 'java.lang.String[][]'.
* Primitives should be handled by the other create method (sig like '[[I').
*/
private TypeReference createTypeReferenceForArrayNameTrailingBrackets(ClassNode node, int start, int until) {
String name = node.getName();
int dim = 0;
int pos = name.length() - 2;
ClassNode componentType = node;
// jump back counting dimensions
while (pos > 0 && name.charAt(pos) == '[') {
dim += 1;
pos -= 2;
componentType = componentType.getComponentType();
}
if (componentType.isPrimitive()) {
Integer ii = charToTypeId.get(name.charAt(dim));
if (ii == null) {
throw new IllegalStateException("node " + node + " reported it had a primitive component type, but it does not...");
} else {
TypeReference baseTypeReference = TypeReference.baseTypeReference(ii, dim);
baseTypeReference.sourceStart = start;
baseTypeReference.sourceEnd = start + componentType.getName().length();
return baseTypeReference;
}
}
if (dim == 0) {
throw new IllegalStateException("Array classnode with dimensions 0?? node:" + node.getName());
}
// array component is something like La.b.c; ... or sometimes just [[Z (where Z is a type, not primitive)
String arrayComponentTypename = name.substring(0, pos + 2);
return createTypeReferenceForArrayName(arrayComponentTypename, componentType, dim, start, until);
}
/**
* Creates JDT TypeReference that represents the given array ClassNode.
* Format will be '[[I' or '[[Ljava.lang.String;'. This latter form is
* really not right but Groovy can produce it so we need to cope with it.
*/
private TypeReference createTypeReferenceForArrayNameLeadingBrackets(ClassNode node, int start, int until) {
String name = node.getName();
int dim = 0;
ClassNode componentType = node;
while (name.charAt(dim) == '[') {
dim += 1;
componentType = componentType.getComponentType();
}
if (componentType.isPrimitive()) {
Integer ii = charToTypeId.get(name.charAt(dim));
if (ii == null) {
throw new IllegalStateException("node " + node + " reported it had a primitive component type, but it does not...");
} else {
TypeReference baseTypeReference = TypeReference.baseTypeReference(ii, dim);
baseTypeReference.sourceStart = start;
baseTypeReference.sourceEnd = start + componentType.getName().length();
return baseTypeReference;
}
} else {
String arrayComponentTypename = name.substring(dim);
if (arrayComponentTypename.charAt(arrayComponentTypename.length() - 1) == ';') {
arrayComponentTypename = name.substring(dim + 1, name.length() - 1); // chop off '['s 'L' and ';'
}
return createTypeReferenceForArrayName(arrayComponentTypename, componentType, dim, start, until);
}
}
private TypeReference createTypeReferenceForArrayName(String typeName, ClassNode typeNode, int dim, int start, int until) {
if (!typeNode.isUsingGenerics()) {
if (typeName.indexOf('.') < 0) {
// For a single array reference, for example 'String[]' start will be 'S' and end will be the char after ']'. When the
// ArrayTypeReference is built we need these positions for the result: sourceStart - the 'S'; sourceEnd - the ']';
// originalSourceEnd - the 'g'
ArrayTypeReference atr = new ArrayTypeReference(typeName.toCharArray(), dim, toPos(start, until - 1));
atr.originalSourceEnd = atr.sourceStart + typeName.length() - 1;
return atr;
} else {
// For a qualified array reference, for example 'java.lang.Number[][]' start will be 'j' and end will be the char after ']'.
// When the ArrayQualifiedTypeReference is built we need these positions for the result: sourceStart - the 'j'; sourceEnd - the
// final ']'; the positions computed for the reference components would be j..a l..g and N..r
char[][] compoundName = CharOperation.splitOn('.', typeName.toCharArray());
ArrayQualifiedTypeReference aqtr = new ArrayQualifiedTypeReference(compoundName, dim,
positionsFor(compoundName, start, (until == -2 ? -2 : until - dim * 2)));
aqtr.sourceEnd = until == -2 ? -2 : until - 1;
return aqtr;
}
} else {
GenericsType[] generics = typeNode.getGenericsTypes();
TypeReference[] typeArgs = new TypeReference[generics.length];
for (int i = 0; i < generics.length; i += 1) {
typeArgs[i] = createTypeReferenceForGenerics(generics[i]);
}
if (typeName.indexOf('.') < 0) {
ParameterizedSingleTypeReference pstr = new ParameterizedSingleTypeReference(typeName.toCharArray(), typeArgs, dim, toPos(start, until - 1));
pstr.originalSourceEnd = pstr.sourceStart + typeName.length() - 1; // TODO: Should this include generics?
return pstr;
} else {
char[][] compoundName = CharOperation.splitOn('.', typeName.toCharArray());
TypeReference[][] compoundArgs = new TypeReference[compoundName.length][];
compoundArgs[compoundName.length - 1] = typeArgs;
ParameterizedQualifiedTypeReference pqtr = new ParameterizedQualifiedTypeReference(compoundName, compoundArgs, dim,
positionsFor(compoundName, start, (until == -2 ? -2 : until - dim * 2)));
pqtr.sourceEnd = until == -2 ? -2 : until - 1;
return pqtr;
}
}
}
private TypeReference createTypeReferenceForClassLiteral(PropertyExpression expression) {
// FIXASC ignore type parameters for now
Expression candidate = expression.getObjectExpression();
List<char[]> nameParts = new LinkedList<char[]>();
while (candidate instanceof PropertyExpression) {
nameParts.add(0, ((PropertyExpression) candidate).getPropertyAsString().toCharArray());
candidate = ((PropertyExpression) candidate).getObjectExpression();
}
if (candidate instanceof VariableExpression) {
nameParts.add(0, ((VariableExpression) candidate).getName().toCharArray());
}
char[][] namePartsArr = nameParts.toArray(new char[nameParts.size()][]);
long[] poss = positionsFor(namePartsArr, expression.getObjectExpression().getStart(), expression.getObjectExpression().getEnd());
TypeReference ref;
if (namePartsArr.length > 1) {
ref = new QualifiedTypeReference(namePartsArr, poss);
} else if (namePartsArr.length == 1) {
ref = new SingleTypeReference(namePartsArr[0], poss[0]);
} else {
// should not happen
ref = TypeReference.baseTypeReference(nameToPrimitiveTypeId.get("void"), 0);
}
return ref;
}
private TypeReference[] createTypeReferencesForClassNodes(ClassNode[] classNodes) {
if (classNodes == null) return null;
final int n = classNodes.length;
if (n == 0) return null;
final TypeReference[] refs = new TypeReference[n];
for (int i = 0; i < n; i += 1) {
refs[i] = createTypeReferenceForClassNode(classNodes[i]);
}
return refs;
}
private TypeReference createTypeReferenceForClassNode(ClassNode classNode) {
return createTypeReferenceForClassNode(classNode, startOffset(classNode), endOffset(classNode));
}
private TypeReference createTypeReferenceForClassNode(ClassNode classNode, int start, int end) {
List<TypeReference> typeArguments = null;
// need to distinguish between raw usage of a type 'List' and generics usage 'List<T>'
// it basically depends upon whether the type variable reference can be resolved within
// the current 'scope' - if it cannot then this is probably a raw reference (yes?)
if (classNode.isUsingGenerics()) {
GenericsType[] genericsInfo = classNode.getGenericsTypes();
if (genericsInfo != null) {
for (int g = 0; g < genericsInfo.length; g++) {
TypeReference tr = createTypeReferenceForGenerics(genericsInfo[g]);
if (tr != null) {
if (typeArguments == null) {
typeArguments = new ArrayList<TypeReference>();
}
typeArguments.add(tr);
}
}
}
}
String name = classNode.getName();
if (name.length() == 1 && name.charAt(0) == '?') {
return new Wildcard(Wildcard.UNBOUND);
}
int arrayLoc = name.indexOf("[");
if (arrayLoc == 0) {
return createTypeReferenceForArrayNameLeadingBrackets(classNode, start, end);
} else if (arrayLoc > 0) {
return createTypeReferenceForArrayNameTrailingBrackets(classNode, start, end);
}
if (nameToPrimitiveTypeId.containsKey(name)) {
return TypeReference.baseTypeReference(nameToPrimitiveTypeId.get(name), 0);
}
if (name.indexOf(".") == -1) {
if (typeArguments == null) {
TypeReference tr = verify(new SingleTypeReference(name.toCharArray(), toPos(start, end - 1)));
if (!checkGenerics) {
tr.bits |= ASTNode.IgnoreRawTypeCheck;
}
return tr;
} else {
// FIXASC determine when array dimension used in this case, is it 'A<T[]> or some silliness?
long l = toPos(start, end - 1);
return new ParameterizedSingleTypeReference(name.toCharArray(),
typeArguments.toArray(new TypeReference[typeArguments.size()]), 0, l);
}
} else {
char[][] compoundName = CharOperation.splitOn('.', name.toCharArray());
if (typeArguments == null) {
TypeReference tr = new QualifiedTypeReference(compoundName, positionsFor(compoundName, start, end));
if (!checkGenerics) {
tr.bits |= ASTNode.IgnoreRawTypeCheck;
}
return tr;
} else {
// FIXASC support individual parameterization of component references A<String>.B<Wibble>
TypeReference[][] typeReferences = new TypeReference[compoundName.length][];
typeReferences[compoundName.length - 1] = typeArguments.toArray(new TypeReference[typeArguments.size()]);
return new ParameterizedQualifiedTypeReference(compoundName, typeReferences, 0, positionsFor(compoundName, start, end));
}
}
}
private TypeReference createTypeReferenceForGenerics(GenericsType genericsType) {
if (genericsType.isWildcard()) {
ClassNode[] bounds = genericsType.getUpperBounds();
if (bounds != null) {
// FIXASC other bounds?
// positions example: (29>31)Set<(33>54)? extends (43>54)Serializable>
TypeReference boundReference = createTypeReferenceForClassNode(bounds[0]);
Wildcard wildcard = new Wildcard(Wildcard.EXTENDS);
wildcard.sourceStart = genericsType.getStart();
wildcard.sourceEnd = boundReference.sourceEnd();
wildcard.bound = boundReference;
return wildcard;
} else if (genericsType.getLowerBound() != null) {
// positions example: (67>69)Set<(71>84)? super (79>84)Number>
TypeReference boundReference = createTypeReferenceForClassNode(genericsType.getLowerBound());
Wildcard wildcard = new Wildcard(Wildcard.SUPER);
wildcard.sourceStart = genericsType.getStart();
wildcard.sourceEnd = boundReference.sourceEnd();
wildcard.bound = boundReference;
return wildcard;
} else {
Wildcard w = new Wildcard(Wildcard.UNBOUND);
w.sourceStart = genericsType.getStart();
w.sourceEnd = genericsType.getStart();
return w;
}
// FIXASC what does the check on this next really line mean?
} else if (!genericsType.getType().isGenericsPlaceHolder()) {
TypeReference typeReference = createTypeReferenceForClassNode(genericsType.getType());
return typeReference;
} else {
// this means it is a placeholder. As an example, if the reference is to 'List'
// then the genericsType info may include a placeholder for the type variable (as the user
// didn't fill it in as anything) and so for this example the genericsType is 'E extends java.lang.Object'
// I don't think we need a type reference for this as the type references we are constructed
// here are representative of what the user did in the source, not the resolved result of that.
// throw new GroovyEclipseBug();
return null;
}
}
/**
* <b>Example:</b> {@code Foo<T extends Number & I>} <br>
* the type parameter is T, the 'type' is Number and the bounds for the
* type parameter are just the extra bound I
*/
private TypeParameter[] createTypeParametersForGenerics(GenericsType[] generics) {
final int n = generics.length;
TypeParameter[] typeParameters = new TypeParameter[n];
for (int i = 0; i < n; i += 1) {
TypeParameter typeParameter = new TypeParameter();
typeParameters[i] = typeParameter;
typeParameter.name = generics[i].getName().toCharArray();
int offset = generics[i].getStart(),
length = typeParameter.name.length;
typeParameter.sourceStart = offset;
typeParameter.sourceEnd = offset + length;
ClassNode[] upperBounds = generics[i].getUpperBounds();
if (upperBounds != null && upperBounds.length > 0) {
String source = String.valueOf(GroovyUtils.readSourceRange(
sourceUnit, generics[i].getStart(), generics[i].getLength()));
// recheck offset with each bound because many will have sloc
int _start = startOffset(upperBounds[0]),
_until = endOffset(upperBounds[0]);
if (_until > 0) {
source = source.substring(_start - offset);
length = _until - _start;
offset = _start;
} else {
// move past T
offset += length;
source = source.substring(length);
// move past "extends" and w.spaces
Matcher m = EXTENDS.matcher(source);
if (m.find()) {
length = m.group().length();
offset += length;
source = source.substring(length);
}
length = upperBounds[0].getName().length();
// TODO: Is this correct for qualified and unqualified occurrences?
String name = GroovyUtils.splitName(upperBounds[0])[1];
assert length == source.indexOf(name) + name.length();
// Would a ClassNode with its own generics ever be missing sloc?
assert source.length() == length || source.charAt(length) != '<';
}
typeParameter.type = createTypeReferenceForClassNode(upperBounds[0], offset, offset + length);
for (int j = 1, k = upperBounds.length; j < k; j += 1) {
if (j == 1) typeParameter.bounds = new TypeReference[k - 1];
_start = startOffset(upperBounds[j]);
_until = endOffset(upperBounds[j]);
if (_until > 0) {
source = source.substring(_start - offset);
length = _until - _start;
offset = _start;
} else { // it appears only MetaClass or GeneratedClosure could get here...
offset += length;
// move past bounds type
source = source.substring(length);
// move past "&" and w.spaces
Matcher m = AND.matcher(source);
if (m.find()) {
length = m.group().length();
offset += length;
source = source.substring(length);
}
length = upperBounds[0].getName().length();
}
typeParameter.bounds[j - 1] = createTypeReferenceForClassNode(upperBounds[j], offset, offset + length);
typeParameter.bounds[j - 1].bits |= ASTNode.IsSuperType;
}
}
}
return typeParameters;
}
private static final Pattern AND = Pattern.compile("^\\s*&\\s*");
private static final Pattern EXTENDS = Pattern.compile("^\\s*extends\\s+");
private static final Map<Character, Integer> charToTypeId = new HashMap<Character, Integer>();
private static final Map<String, Integer> nameToPrimitiveTypeId = new HashMap<String, Integer>();
static {
charToTypeId.put('D', TypeIds.T_double);
charToTypeId.put('I', TypeIds.T_int);
charToTypeId.put('F', TypeIds.T_float);
charToTypeId.put('J', TypeIds.T_long);
charToTypeId.put('Z', TypeIds.T_boolean);
charToTypeId.put('B', TypeIds.T_byte);
charToTypeId.put('C', TypeIds.T_char);
charToTypeId.put('S', TypeIds.T_short);
nameToPrimitiveTypeId.put("double", TypeIds.T_double);
nameToPrimitiveTypeId.put("int", TypeIds.T_int);
nameToPrimitiveTypeId.put("float", TypeIds.T_float);
nameToPrimitiveTypeId.put("long", TypeIds.T_long);
nameToPrimitiveTypeId.put("boolean", TypeIds.T_boolean);
nameToPrimitiveTypeId.put("byte", TypeIds.T_byte);
nameToPrimitiveTypeId.put("char", TypeIds.T_char);
nameToPrimitiveTypeId.put("short", TypeIds.T_short);
nameToPrimitiveTypeId.put("void", TypeIds.T_void);
}
//--------------------------------------------------------------------------------------------------------------
/**
* For some input array (usually representing a reference), work out the offset positions, assuming they are dotted. <br>
* Currently this uses the size of each component to move from start towards end. For the very last one it makes the end
* position 'end' because in some cases just adding 1+length of previous reference isn't enough. For example in java.util.List[]
* the end will be the end of [] but reference will only contain 'java' 'util' 'List'
* <p>
* Because the 'end' is quite often wrong right now (for example on a return type 'java.util.List[]' the end can be two
* characters off the end (set to the start of the method name...) - we are just computing the positional information from the
* start.
* <p>
* FIXASC: seems that sometimes, especially for types that are defined as 'def', but are converted to java.lang.Object, end
* < start. This causes no end of problems. I don't think it is so much the 'declaration' as the fact that is no reference and
* really what is computed here is the reference for something actually specified in the source code. Coming up with fake
* positions for something not specified is not entirely unreasonable we should check
* if the reference in particular needed creating at all in the first place...
*/
private long[] positionsFor(char[][] reference, long start, long end) {
long[] result = new long[reference.length];
if (start == -1 && end == -2) {
for (int i = 0, max = result.length; i < max; i++) {
result[i] = ((-1L << 32) | -2L);
}
return result;
}
if (start < end) {
// Do the right thing
long pos = start;
for (int i = 0, max = result.length; i < max; i++) {
long s = pos;
pos = pos + reference[i].length - 1; // jump to the last char of the name
result[i] = ((s << 32) | pos);
pos += 2; // jump onto the following '.' then off it
}
} else {
// FIXASC this case shouldn't happen (end<start) - uncomment following if to collect diagnostics
long pos = (start << 32) | start;
for (int i = 0, max = result.length; i < max; i++) {
result[i] = pos;
}
}
return result;
}
/** Check for trailing semicolons, spaces, tabs, etc. */
private int trailerLength(org.codehaus.groovy.ast.ASTNode node) {
int length = 0;
if (sourceUnit != null)
if (node.getLastLineNumber() > 0) {
if (janitor == null) janitor = new Janitor();
ReaderSource source = sourceUnit.getSource();
String line = source.getLine(node.getLastLineNumber(), janitor);
StringBuilder sb = null;
int c = 0, endPos = node.getLastColumnNumber() - 1;
while (endPos < line.length() && ((c = line.charAt(endPos++)) == ';' || c == ' ' || c == '\t')) {
(sb != null ? sb : (sb = new StringBuilder())).appendCodePoint(c);
}
if (sb != null) length += sb.length();
}
return length;
}
private int startOffset(org.codehaus.groovy.ast.ASTNode node) {
int s = node.getStart();
int e = node.getEnd();
if (s == 0 && e == 0) {
return -1;
} else {
return s;
}
}
private int endOffset(org.codehaus.groovy.ast.ASTNode node) {
int s = node.getStart();
int e = node.getEnd();
if (s == 0 && e == 0) {
return -2;
} else {
return e;
}
}
private boolean isAnon(ClassNode classNode) {
// FIXADE does Groovy support non-anon local types???
return classNode.getEnclosingMethod() != null ||
// check to see if anon type inside of a script
(classNode.getOuterClass() != null && classNode.getOuterClass().isScript());
}
private boolean isTrait(ClassNode classNode) {
return unitDeclaration.traitHelper.isTrait(classNode);
}
/**
* @return true if this is varargs, using the same definition as in AsmClassGenerator.isVargs(Parameter[])
*/
private boolean isVargs(Parameter[] parameters) {
if (parameters.length == 0) {
return false;
}
Parameter last = parameters[parameters.length - 1];
ClassNode type = last.getType();
return type.isArray();
}
private char[] toMainName(char[] fileName) {
if (fileName == null) {
return new char[0];
}
int start = CharOperation.lastIndexOf('/', fileName) + 1;
if (start == 0 || start < CharOperation.lastIndexOf('\\', fileName))
start = CharOperation.lastIndexOf('\\', fileName) + 1;
int end = CharOperation.lastIndexOf('.', fileName);
if (end == -1)
end = fileName.length;
return CharOperation.subarray(fileName, start, end);
}
// because 'length' is computed as 'end-start+1' and start==-1 indicates it does not exist, then
// to have a length of 0 the end must be -2.
private static long NON_EXISTENT_POSITION = ((-1L << 32) | -2L);
/**
* Pack start and end positions into a long - no adjustments are made to the values passed in, the caller must make any required
* adjustments.
*/
private static long toPos(long start, long end) {
if (start == 0 && end <= 0) {
return NON_EXISTENT_POSITION;
} else if (start < 0 || end < 0) {
return NON_EXISTENT_POSITION;
}
return ((start << 32) | end);
}
/**
* Find any javadoc that terminates on one of the two lines before the specified line, return the first bit encountered. A
* little crude but will cover a lot of common cases... <br>
*/
// FIXASC when the parser correctly records javadoc for nodes alongside them during a parse, we will not have to search
private Javadoc findJavadoc(int line) {
for (Comment comment : sourceUnit.getComments()) {
if (comment.isJavadoc()) {
if (comment.getLastLine() + 1 == line || (comment.getLastLine() + 2 == line && !comment.usedUp)) {
int[] pos = comment.getPositions(unitDeclaration.compilationResult.lineSeparatorPositions);
comment.usedUp = true;
return new Javadoc(pos[0], pos[1]);
}
}
}
return null;
}
/**
* Try to get the source locations for type declarations to be as correct as possible
*/
private void fixupSourceLocationsForTypeDeclaration(GroovyTypeDeclaration typeDeclaration, ClassNode classNode) {
// start and end of the name of class
if (classNode instanceof InnerClassNode) {
// anonynymous inner classes do not have start and end set aproproately
typeDeclaration.sourceStart = classNode.getNameStart();
typeDeclaration.sourceEnd = classNode.getNameEnd();
} else {
// scripts do not have a name, so use start instead
typeDeclaration.sourceStart = Math.max(classNode.getNameStart(), classNode.getStart());
typeDeclaration.sourceEnd = Math.max(classNode.getNameEnd(), classNode.getStart());
}
// start and end of the entire declaration including Javadoc and ending at the last close bracket
int line = classNode.getLineNumber();
Javadoc doc = findJavadoc(line);
if (doc != null) {
if (unitDeclaration.imports != null && unitDeclaration.imports.length > 0) {
if (doc.sourceStart < unitDeclaration.imports[unitDeclaration.imports.length - 1].sourceStart) {
// ignore the doc if it should be associated with and import statement
doc = null;
}
} else if (unitDeclaration.currentPackage != null) {
if (doc.sourceStart < unitDeclaration.currentPackage.sourceStart) {
// ignore the doc if it should be associated with the package statement
doc = null;
}
}
}
typeDeclaration.javadoc = doc;
typeDeclaration.declarationSourceStart = doc == null ? classNode.getStart() : doc.sourceStart;
// Without the -1 we can hit AIOOBE in org.eclipse.jdt.internal.core.Member.getJavadocRange where it calls getText()
// because the source range length causes us to ask for more data than is in the buffer. What does this mean?
// For hovers, the AIOOBE is swallowed and you just see no hover box.
typeDeclaration.declarationSourceEnd = classNode.getEnd() - 1;
// * start at the opening brace and end at the closing brace
// except that scripts do not have a name, use the start instead
// FIXADE this is not exactly right since getNameEnd() comes before extends and implements clauses
typeDeclaration.bodyStart = Math.max(classNode.getNameEnd(), classNode.getStart());
// seems to be the same as declarationSourceEnd
typeDeclaration.bodyEnd = classNode.getEnd() - 1;
// start of the modifiers after the javadoc
typeDeclaration.modifiersSourceStart = classNode.getStart();
}
/**
* Try to get the source locations for constructor declarations to be as correct as possible
*/
private void fixupSourceLocationsForConstructorDeclaration(ConstructorDeclaration ctorDeclaration, ConstructorNode ctorNode) {
ctorDeclaration.sourceStart = ctorNode.getNameStart();
ctorDeclaration.sourceEnd = ctorNode.getNameEnd();
// start and end of method declaration including JavaDoc
// ending with closing '}' or ';' if abstract
int line = ctorNode.getLineNumber();
Javadoc doc = findJavadoc(line);
ctorDeclaration.javadoc = doc;
ctorDeclaration.declarationSourceStart = doc == null ? ctorNode.getStart() : doc.sourceStart;
ctorDeclaration.declarationSourceEnd = ctorNode.getEnd() - 1;
// start of method's modifier list (after Javadoc is ended)
ctorDeclaration.modifiersSourceStart = ctorNode.getStart();
// opening bracket
ctorDeclaration.bodyStart =
// try for opening bracket
ctorNode.getCode() != null ? ctorNode.getCode().getStart()
:
// handle abstract constructor. not sure if this can ever happen, but you never know with Groovy
ctorNode.getNameEnd();
// closing bracket or ';' same as declarationSourceEnd
ctorDeclaration.bodyEnd = ctorNode.getEnd() - 1;
}
/**
* Try to get the source locations for method declarations to be as correct as possible
*/
private void fixupSourceLocationsForMethodDeclaration(MethodDeclaration methodDeclaration, MethodNode methodNode) {
// run() method for scripts has no name, so use the start of the method instead
methodDeclaration.sourceStart = Math.max(methodNode.getNameStart(), methodNode.getStart());
methodDeclaration.sourceEnd = Math.max(methodNode.getNameEnd(), methodNode.getStart());
// start and end of method declaration including JavaDoc
// ending with closing '}' or ';' if abstract
int line = methodNode.getLineNumber();
Javadoc doc = findJavadoc(line);
methodDeclaration.javadoc = doc;
methodDeclaration.declarationSourceStart = doc == null ? methodNode.getStart() : doc.sourceStart;
methodDeclaration.declarationSourceEnd = methodNode.getEnd() - 1;
// start of method's modifier list (after Javadoc is ended)
methodDeclaration.modifiersSourceStart = methodNode.getStart();
// opening bracket
methodDeclaration.bodyStart =
// try for opening bracket
methodNode.getCode() != null ? methodNode.getCode().getStart()
:
// run() method for script has no opening bracket
// also need to handle abstract methods
Math.max(methodNode.getNameEnd(), methodNode.getStart());
// closing bracket or ';' same as declarationSourceEnd
methodDeclaration.bodyEnd = methodNode.getEnd() - 1;
}
/**
* Try to get the source locations for field declarations to be as correct as possible
*/
private void fixupSourceLocationsForFieldDeclaration(FieldDeclaration fieldDeclaration, FieldNode fieldNode, boolean isEnumField) {
// TODO (groovy) each area marked with a '*' is only approximate
// and can be revisited to make more precise
// Here, we distinguish between the declaration and the fragment
// e.g.- def x = 9, y = "l"
// 'x = 9,' and 'y = "l"' are the fragments and 'def x = 9, y = "l"' is the declaration
// the start and end of the fragment name
fieldDeclaration.sourceStart = fieldNode.getNameStart();
fieldDeclaration.sourceEnd = fieldNode.getNameEnd();
// start of the declaration (including javadoc?)
int line = fieldNode.getLineNumber();
Javadoc doc = findJavadoc(line);
fieldDeclaration.javadoc = doc;
if (isEnumField) {
// they have no 'leading' type declaration or modifiers
fieldDeclaration.declarationSourceStart = doc == null ? fieldNode.getStart() : doc.sourceStart;
fieldDeclaration.declarationSourceEnd = fieldNode.getEnd() - 1;
} else {
fieldDeclaration.declarationSourceStart = doc == null ? fieldNode.getStart() : doc.sourceStart;
// the end of the fragment including initializer (and trailing ',')
fieldDeclaration.declarationSourceEnd = fieldNode.getEnd() - 1;
}
// * first character of the declaration's modifier
fieldDeclaration.modifiersSourceStart = fieldNode.getStart();
// end of the entire Field declaration (after all fragments and including ';' if exists)
fieldDeclaration.declarationEnd = fieldNode.getEnd();
// * end of the type declaration part of the declaration (the same for each fragment)
// eg- int x, y corresponds to the location after 'int'
fieldDeclaration.endPart1Position = fieldNode.getNameStart();
// * just before the start of the next fragment
// (or the end of the entire declaration if it is the last one)
// (how is this different from declarationSourceEnd?)
fieldDeclaration.endPart2Position = fieldNode.getEnd() - 1;
}
/**
* Create a JDT representation of a groovy MethodNode - but with some parameters defaulting
*/
private MethodDeclaration genMethodDeclarationVariant(ClassNode classNode, MethodNode methodNode, boolean isEnum,
Argument[] alternativeArguments, TypeReference returnType, CompilationResult compilationResult) {
MethodDeclaration methodDeclaration = createMethodDeclaration(classNode, isEnum, methodNode, compilationResult);
methodDeclaration.arguments = alternativeArguments;
fixupSourceLocationsForMethodDeclaration(methodDeclaration, methodNode);
return methodDeclaration;
}
/**
* In the given list of groovy parameters, some are defined as defaulting to an initial value. This method computes all the
* variants of defaulting parameters allowed and returns a List of Argument arrays. Each argument array represents a variation.
*/
private List<Argument[]> getVariantsAllowingForDefaulting(Parameter[] groovyParams, Argument[] jdtArguments) {
List<Argument[]> variants = new ArrayList<Argument[]>();
int psCount = groovyParams.length;
Parameter[] wipableParameters = new Parameter[psCount];
System.arraycopy(groovyParams, 0, wipableParameters, 0, psCount);
// Algorithm: wipableParameters is the 'full list' of parameters at the start. As the loop is repeated, all the non-null
// values in the list indicate a parameter variation. On each repeat we null the last one in the list that
// has an initial expression. This is repeated until there are no more left to null.
List<Argument> oneVariation = new ArrayList<Argument>();
int nextToLetDefault = -1;
do {
oneVariation.clear();
nextToLetDefault = -1;
// Create a variation based on the non null entries left in th elist
for (int p = 0; p < psCount; p++) {
if (wipableParameters[p] != null) {
oneVariation.add(jdtArguments[p]);
if (wipableParameters[p].hasInitialExpression()) {
nextToLetDefault = p;
}
}
}
if (nextToLetDefault != -1) {
wipableParameters[nextToLetDefault] = null;
}
Argument[] argumentsVariant = (oneVariation.size() == 0 ? null
: oneVariation.toArray(new Argument[oneVariation.size()]));
variants.add(argumentsVariant);
} while (nextToLetDefault != -1);
return variants;
}
/**
* Add the new declaration to the list of those already built unless it clashes with an existing one. This can happen where the
* default parameter mechanism causes creation of a variant that collides with an existing declaration. I'm not sure if Groovy
* should be reporting an error when this occurs, but Grails does actually do it and gets no error.
*/
private void addUnlessDuplicate(List<AbstractMethodDeclaration> accumulatedDeclarations, AbstractMethodDeclaration newDeclaration) {
boolean isDuplicate = false;
for (AbstractMethodDeclaration aMethodDecl : accumulatedDeclarations) {
if (CharOperation.equals(aMethodDecl.selector, newDeclaration.selector)) {
Argument[] mdArgs = aMethodDecl.arguments;
Argument[] vmdArgs = newDeclaration.arguments;
int mdArgsLen = mdArgs == null ? 0 : mdArgs.length;
int vmdArgsLen = vmdArgs == null ? 0 : vmdArgs.length;
if (mdArgsLen == vmdArgsLen) {
boolean argsTheSame = true;
for (int i = 0; i < mdArgsLen; i++) {
// FIXASC this comparison can fail if some are fully qualified and some not - in fact it
// suggests that default param variants should be built by augmentMethod() in a similar
// way to the GroovyObject methods, rather than during type declaration construction
if (!CharOperation.equals(mdArgs[i].type.getTypeName(), vmdArgs[i].type.getTypeName())) {
argsTheSame = false;
break;
}
}
if (argsTheSame) {
isDuplicate = true;
break;
}
}
}
}
if (!isDuplicate) {
accumulatedDeclarations.add(newDeclaration);
}
}
/**
* Augment set of constructors based on annotations. If the annotations are going to trigger additional constructors later, add
* them here.
*/
private void executeEarlyTransforms_ConstructorRelated(char[] ctorName, ClassNode classNode,
List<AbstractMethodDeclaration> accumulatedMethodDeclarations) {
List<AnnotationNode> annos = classNode.getAnnotations();
boolean hasImmutableAnnotation = false;
if (annos != null) {
for (AnnotationNode anno : annos) {
if (anno.getClassNode() != null) {
String annoName = anno.getClassNode().getName();
if (annoName.equals("groovy.transform.Immutable")) {
hasImmutableAnnotation = true;
} else if (annoName.equals("Immutable")) {
// do our best to see if this is the real groovy @Immutable class node
ModuleNode module = classNode.getModule();
if (module != null) {
ClassNode imp = module.getImportType("Immutable");
if (imp == null || imp.getName().equals("groovy.transform.Immutable")) {
hasImmutableAnnotation = true;
}
}
}
}
}
}
// TODO probably ought to check if clashing import rather than assuming it is groovy-eclipse Immutable (even though that is
// very likely)
if (hasImmutableAnnotation) {
// @Immutable action: new constructor
// TODO Should check against existing ones before creating a duplicate but quite ugly, and
// groovy will be checking anyway...
List<FieldNode> fields = classNode.getFields();
if (fields.size() > 0) {
// only add constructor if one or more fields.
// when no fields are present, fall back on the default generated constructor
Argument[] arguments = new Argument[fields.size()];
for (int i = 0; i < fields.size(); i++) {
FieldNode field = fields.get(i);
TypeReference parameterTypeReference = createTypeReferenceForClassNode(field.getType());
// TODO should set type reference position
arguments[i] = new Argument(fields.get(i).getName().toCharArray(), toPos(field.getStart(), field.getEnd() - 1),
parameterTypeReference, ClassFileConstants.AccPublic);
arguments[i].declarationSourceStart = fields.get(i).getStart();
}
ConstructorDeclaration constructor = new ConstructorDeclaration(unitDeclaration.compilationResult);
constructor.selector = ctorName;
constructor.modifiers = ClassFileConstants.AccPublic;
constructor.arguments = arguments;
accumulatedMethodDeclarations.add(constructor);
}
}
}
/**
* Check the supplied TypeReference. If there are problems with the construction of a TypeReference then these may not surface
* until it is used later, perhaps when reconciling. The easiest way to check there will not be problems later is to check it at
* construction time.
*
* @param toVerify the type reference to check
* @param does the type reference really exist in the source or is it conjured up based on the source
* @return the verified type reference
* @throws IllegalStateException if the type reference is malformed
*/
private TypeReference verify(TypeReference toVerify) {
if (GroovyCheckingControl.checkTypeReferences) {
if (toVerify.getClass().equals(SingleTypeReference.class)) {
SingleTypeReference str = (SingleTypeReference) toVerify;
if (str.sourceStart == -1) {
if (str.sourceEnd != -2) {
throw new IllegalStateException("TypeReference '" + new String(str.token) + " should end at -2");
}
} else {
if (str.sourceEnd < str.sourceStart) {
throw new IllegalStateException(
"TypeReference '" + new String(str.token) + " should end at " + str.sourceStart + " or later");
}
}
} else {
throw new IllegalStateException("Cannot verify type reference of this class " + toVerify.getClass());
}
}
return toVerify;
}
}
/**
* Holds on to the groovy initializer so we can return it later.
* This is much easier than translating it into a JDT initializer and back again later.
*/
public static class FieldDeclarationWithInitializer extends FieldDeclaration {
private Expression initializer;
public FieldDeclarationWithInitializer(char[] name, int sourceStart, int sourceEnd) {
super(name, sourceStart, sourceEnd);
}
public Expression getGroovyInitializer() {
return initializer;
}
}
}