| /******************************************************************************* |
| * Copyright (c) 2014, 2015 GK Software AG. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Stephan Herrmann - initial API and implementation |
| * Lars Vogel <Lars.Vogel@vogella.com> - Contributions for |
| * Bug 473178 |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.classfmt; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.LineNumberReader; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation; |
| import org.eclipse.jdt.internal.compiler.env.IBinaryElementValuePair; |
| import org.eclipse.jdt.internal.compiler.env.ITypeAnnotationWalker; |
| import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment; |
| import org.eclipse.jdt.internal.compiler.lookup.SignatureWrapper; |
| import org.eclipse.jdt.internal.compiler.util.Util; |
| |
| public class ExternalAnnotationProvider { |
| |
| public static final String ANNOTION_FILE_EXTENSION= "eea"; //$NON-NLS-1$ |
| public static final String CLASS_PREFIX = "class "; //$NON-NLS-1$ |
| public static final String SUPER_PREFIX = "super "; //$NON-NLS-1$ |
| |
| /** Representation of a 'nullable' annotation, independent of the concrete annotation name used in Java sources. */ |
| public static final char NULLABLE = '0'; |
| |
| /** Representation of a 'nonnull' annotation, independent of the concrete annotation name used in Java sources. */ |
| public static final char NONNULL = '1'; |
| |
| /** |
| * Represents absence of a null annotation. Useful for removing an existing null annotation. |
| * This character is used only internally, it is not part of the Eclipse External Annotation file format. |
| */ |
| public static final char NO_ANNOTATION = '@'; |
| |
| static final String ANNOTATION_FILE_SUFFIX = ".eea"; //$NON-NLS-1$ |
| |
| private static final String TYPE_PARAMETER_PREFIX = " <"; //$NON-NLS-1$ |
| |
| |
| private String typeName; |
| String typeParametersAnnotationSource; |
| Map<String,String> supertypeAnnotationSources; |
| private Map<String,String> methodAnnotationSources; |
| private Map<String,String> fieldAnnotationSources; |
| |
| /** |
| * Create and initialize. |
| * @param input open input stream to read the annotations from, will be closed by the constructor. |
| * @param typeName slash-separated qualified name of a type |
| * @throws IOException various issues when accessing the annotation file |
| */ |
| public ExternalAnnotationProvider(InputStream input, String typeName) throws IOException { |
| this.typeName = typeName; |
| initialize(input); |
| } |
| |
| private void initialize(InputStream input) throws IOException { |
| LineNumberReader reader = new LineNumberReader(new InputStreamReader(input)); |
| try { |
| assertClassHeader(reader.readLine(), this.typeName); |
| |
| String line; |
| if ((line = reader.readLine()) == null) { |
| return; |
| } |
| if (line.startsWith(TYPE_PARAMETER_PREFIX)) { |
| if ((line = reader.readLine()) == null) // skip first line, second line may contain type parameter annotations |
| return; |
| if (line.startsWith(TYPE_PARAMETER_PREFIX)) { |
| this.typeParametersAnnotationSource = line.substring(TYPE_PARAMETER_PREFIX.length()); |
| if ((line = reader.readLine()) == null) |
| return; |
| } |
| } |
| String pendingLine; |
| do { |
| pendingLine = null; |
| line = line.trim(); |
| if (line.isEmpty()) continue; |
| String rawSig = null, annotSig = null; |
| // selector: |
| String selector = line; |
| boolean isSuper = selector.startsWith(SUPER_PREFIX); |
| if (isSuper) |
| selector = selector.substring(SUPER_PREFIX.length()); |
| int errLine = -1; |
| try { |
| // raw signature: |
| line = reader.readLine(); |
| if (line != null && !line.isEmpty() && line.charAt(0) == ' ') // first signature line is mandatory |
| rawSig = line.substring(1); |
| else |
| errLine = reader.getLineNumber(); |
| // annotated signature: |
| line = reader.readLine(); |
| if (line == null || line.isEmpty()) |
| continue; // skip since optional line with annotations is missing |
| if (line.charAt(0) != ' ') { |
| pendingLine = line; // push back what appears to be the next selector, not a signature |
| continue; |
| } |
| annotSig = line.substring(1); |
| } catch (Exception ex) { |
| // continue to escalate below |
| } |
| if (rawSig == null || annotSig == null) { |
| if (errLine == -1) errLine = reader.getLineNumber(); |
| throw new IOException("Illegal format for annotation file at line "+errLine); //$NON-NLS-1$ |
| } |
| // discard optional meta data (separated by whitespace): |
| annotSig = trimTail(annotSig); |
| if (isSuper) { |
| if (this.supertypeAnnotationSources == null) |
| this.supertypeAnnotationSources = new HashMap<>(); |
| this.supertypeAnnotationSources.put('L'+selector+rawSig+';', annotSig); |
| } else if (rawSig.contains("(")) { //$NON-NLS-1$ |
| if (this.methodAnnotationSources == null) |
| this.methodAnnotationSources = new HashMap<>(); |
| this.methodAnnotationSources.put(selector+rawSig, annotSig); |
| } else { |
| if (this.fieldAnnotationSources == null) |
| this.fieldAnnotationSources = new HashMap<>(); |
| this.fieldAnnotationSources.put(selector+':'+rawSig, annotSig); |
| } |
| } while (((line = pendingLine) != null) || (line = reader.readLine()) != null); |
| } finally { |
| reader.close(); |
| } |
| } |
| |
| /** |
| * Assert that the given line is a class header for 'typeName' (slash-separated qualified name). |
| */ |
| public static void assertClassHeader(String line, String typeName) throws IOException { |
| if (line.startsWith(CLASS_PREFIX)) { |
| line = line.substring(CLASS_PREFIX.length()); |
| } else { |
| throw new IOException("missing class header in annotation file"); //$NON-NLS-1$ |
| } |
| if (!trimTail(line).equals(typeName)) { |
| throw new IOException("mismatching class name in annotation file, expected "+typeName+", but header said "+line); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| /** |
| * Extract the signature from a line of an external annotation file. |
| * Answers null if line is not in the expected format. |
| */ |
| public static String extractSignature(String line) { |
| if (line == null || line.isEmpty() || line.charAt(0) != ' ') |
| return null; |
| return trimTail(line.substring(1)); |
| } |
| |
| /** Lines may contain arbitrary trailing data, separated by white space. */ |
| protected static String trimTail(String line) { |
| int tail = line.indexOf(' '); |
| if (tail == -1) |
| tail = line.indexOf('\t'); |
| if (tail != -1) |
| return line.substring(0, tail); |
| return line; |
| } |
| |
| public ITypeAnnotationWalker forTypeHeader(LookupEnvironment environment) { |
| if (this.typeParametersAnnotationSource != null || this.supertypeAnnotationSources != null) |
| return new DispatchingAnnotationWalker(environment); |
| return ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER; |
| } |
| |
| public ITypeAnnotationWalker forMethod(char[] selector, char[] signature, LookupEnvironment environment) { |
| Map<String, String> sources = this.methodAnnotationSources; |
| if (sources != null) { |
| String source = sources.get(String.valueOf(CharOperation.concat(selector, signature))); |
| if (source != null) |
| return new MethodAnnotationWalker(source.toCharArray(), 0, environment); |
| } |
| return ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER; |
| } |
| |
| public ITypeAnnotationWalker forField(char[] selector, char[] signature, LookupEnvironment environment) { |
| if (this.fieldAnnotationSources != null) { |
| String source = this.fieldAnnotationSources.get(String.valueOf(CharOperation.concat(selector, signature, ':'))); |
| if (source != null) |
| return new FieldAnnotationWalker(source.toCharArray(), 0, environment); |
| } |
| return ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER; |
| } |
| |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("External Annotations for ").append(this.typeName).append('\n'); //$NON-NLS-1$ |
| sb.append("Methods:\n"); //$NON-NLS-1$ |
| if (this.methodAnnotationSources != null) |
| for (Entry<String,String> e : this.methodAnnotationSources.entrySet()) |
| sb.append('\t').append(e.getKey()).append('\n'); |
| return sb.toString(); |
| } |
| |
| abstract class SingleMarkerAnnotation implements IBinaryAnnotation { |
| @Override |
| public IBinaryElementValuePair[] getElementValuePairs() { |
| return ElementValuePairInfo.NoMembers; |
| } |
| protected char[] getBinaryTypeName(char[][] name) { |
| return CharOperation.concat('L', CharOperation.concatWith(name, '/'), ';'); |
| } |
| } |
| |
| SingleMarkerAnnotation NULLABLE_ANNOTATION, NONNULL_ANNOTATION; |
| |
| void initAnnotations(final LookupEnvironment environment) { |
| if (this.NULLABLE_ANNOTATION == null) { |
| this.NULLABLE_ANNOTATION = new SingleMarkerAnnotation() { |
| @Override public char[] getTypeName() { return getBinaryTypeName(environment.getNullableAnnotationName()); } |
| }; |
| } |
| if (this.NONNULL_ANNOTATION == null) { |
| this.NONNULL_ANNOTATION = new SingleMarkerAnnotation() { |
| @Override public char[] getTypeName() { return getBinaryTypeName(environment.getNonNullAnnotationName()); } |
| }; |
| } |
| } |
| |
| /** |
| * Walker for top-level elements of a type (type parameters & super types), |
| * which dispatches to specialized walkers for those details. |
| */ |
| class DispatchingAnnotationWalker implements ITypeAnnotationWalker { |
| |
| private LookupEnvironment environment; |
| private TypeParametersAnnotationWalker typeParametersWalker; |
| |
| public DispatchingAnnotationWalker(LookupEnvironment environment) { |
| this.environment = environment; |
| } |
| public ITypeAnnotationWalker toTypeParameter(boolean isClassTypeParameter, int rank) { |
| String source = ExternalAnnotationProvider.this.typeParametersAnnotationSource; |
| if (source != null) { |
| if (this.typeParametersWalker == null) |
| this.typeParametersWalker = new TypeParametersAnnotationWalker(source.toCharArray(), 0, 0, null, this.environment); |
| return this.typeParametersWalker.toTypeParameter(isClassTypeParameter, rank); |
| } |
| return this; |
| } |
| public ITypeAnnotationWalker toTypeParameterBounds(boolean isClassTypeParameter, int parameterRank) { |
| if (this.typeParametersWalker != null) |
| return this.typeParametersWalker.toTypeParameterBounds(isClassTypeParameter, parameterRank); |
| return this; |
| } |
| public ITypeAnnotationWalker toSupertype(short index, char[] superTypeSignature) { |
| Map<String, String> sources = ExternalAnnotationProvider.this.supertypeAnnotationSources; |
| if (sources != null) { |
| String source = sources.get(String.valueOf(superTypeSignature)); |
| if (source != null) |
| return new SuperTypesAnnotationWalker(source.toCharArray(), this.environment); |
| } |
| return this; |
| } |
| // the rest is borrowed from EMPTY_ANNOTATION_WALKER: |
| public ITypeAnnotationWalker toField() { return this; } |
| public ITypeAnnotationWalker toThrows(int rank) { return this; } |
| public ITypeAnnotationWalker toTypeArgument(int rank) { return this; } |
| public ITypeAnnotationWalker toMethodParameter(short index) { return this; } |
| public ITypeAnnotationWalker toTypeBound(short boundIndex) { return this; } |
| public ITypeAnnotationWalker toMethodReturn() { return this; } |
| public ITypeAnnotationWalker toReceiver() { return this; } |
| public ITypeAnnotationWalker toWildcardBound() { return this; } |
| public ITypeAnnotationWalker toNextArrayDimension() { return this; } |
| public ITypeAnnotationWalker toNextNestedType() { return this; } |
| public IBinaryAnnotation[] getAnnotationsAtCursor(int currentTypeId) { return NO_ANNOTATIONS; } |
| } |
| |
| abstract class BasicAnnotationWalker implements ITypeAnnotationWalker { |
| |
| char[] source; |
| SignatureWrapper wrapper; |
| int pos; |
| int prevTypeArgStart; |
| int currentTypeBound; |
| LookupEnvironment environment; |
| |
| BasicAnnotationWalker(char[] source, int pos, LookupEnvironment environment) { |
| this.source = source; |
| this.pos = pos; |
| this.environment = environment; |
| initAnnotations(environment); |
| } |
| |
| SignatureWrapper wrapperWithStart(int start) { |
| if (this.wrapper == null) |
| this.wrapper = new SignatureWrapper(this.source); |
| this.wrapper.start = start; |
| return this.wrapper; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toReceiver() { |
| return ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toTypeParameter(boolean isClassTypeParameter, int rank) { |
| return ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toTypeParameterBounds(boolean isClassTypeParameter, int parameterRank) { |
| return ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toTypeBound(short boundIndex) { |
| return ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toSupertype(short index, char[] superTypeSignature) { |
| return ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toTypeArgument(int rank) { |
| if (rank == 0) { |
| int start = CharOperation.indexOf('<', this.source, this.pos) + 1; |
| this.prevTypeArgStart = start; |
| return new MethodAnnotationWalker(this.source, start, this.environment); |
| } |
| int next = this.prevTypeArgStart; |
| switch (this.source[next]) { |
| case '*': |
| break; |
| case '-': |
| case '+': |
| next++; |
| //$FALL-THROUGH$ |
| default: |
| next = wrapperWithStart(next).computeEnd(); |
| } |
| next++; |
| this.prevTypeArgStart = next; |
| return new MethodAnnotationWalker(this.source, next, this.environment); |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toWildcardBound() { |
| switch (this.source[this.pos]) { |
| case '-': |
| case '+': |
| return new MethodAnnotationWalker(this.source, this.pos+1, this.environment); |
| default: // includes unbounded '*' |
| return ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER; |
| } |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toNextArrayDimension() { |
| if (this.source[this.pos] == '[') { |
| int newPos = this.pos+1; |
| switch (this.source[newPos]) { |
| case NULLABLE: case NONNULL: newPos++; break; |
| } |
| return new MethodAnnotationWalker(this.source, newPos, this.environment); |
| } |
| return ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toNextNestedType() { |
| return this; // FIXME(stephan) |
| } |
| |
| @Override |
| public IBinaryAnnotation[] getAnnotationsAtCursor(int currentTypeId) { |
| if (this.pos != -1 && this.pos < this.source.length-2) { |
| switch (this.source[this.pos]) { |
| case 'T': |
| case 'L': |
| case '[': |
| case '*': |
| case '+': |
| case '-': |
| switch (this.source[this.pos+1]) { |
| case NULLABLE: |
| return new IBinaryAnnotation[]{ ExternalAnnotationProvider.this.NULLABLE_ANNOTATION }; |
| case NONNULL: |
| return new IBinaryAnnotation[]{ ExternalAnnotationProvider.this.NONNULL_ANNOTATION }; |
| } |
| } |
| } |
| return null; |
| } |
| } |
| |
| /** |
| * Walker that may serve the annotations on type parameters of the current class or method. |
| */ |
| public class TypeParametersAnnotationWalker extends BasicAnnotationWalker { |
| |
| int[] rankStarts; // indices of start positions for type parameters per rank |
| int currentRank; |
| |
| TypeParametersAnnotationWalker(char[] source, int pos, int rank, int[] rankStarts, LookupEnvironment environment) { |
| super(source, pos, environment); |
| this.currentRank = rank; |
| if (rankStarts != null) { |
| this.rankStarts = rankStarts; |
| } else { |
| // eagerly scan all type parameters: |
| int length = source.length; |
| rankStarts = new int[length]; |
| int curRank = 0; |
| // next block cf. BinaryTypeBinding.createTypeVariables(): |
| int depth = 0; |
| boolean pendingVariable = true; |
| scanVariables: { |
| for (int i = pos; i < length; i++) { |
| switch(this.source[i]) { |
| case Util.C_GENERIC_START : |
| depth++; |
| break; |
| case Util.C_GENERIC_END : |
| if (--depth < 0) |
| break scanVariables; |
| break; |
| case Util.C_NAME_END : |
| if ((depth == 0) && (i +1 < length) && (this.source[i+1] != Util.C_COLON)) |
| pendingVariable = true; |
| break; |
| default: |
| if (pendingVariable) { |
| pendingVariable = false; |
| rankStarts[curRank++] = i; |
| } |
| } |
| } |
| } |
| System.arraycopy(rankStarts, 0, this.rankStarts = new int[curRank], 0, curRank); |
| } |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toTypeParameter(boolean isClassTypeParameter, int rank) { |
| if (rank == this.currentRank) |
| return this; |
| if (rank < this.rankStarts.length) |
| return new TypeParametersAnnotationWalker(this.source, this.rankStarts[rank], rank, this.rankStarts, this.environment); |
| return ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toTypeParameterBounds(boolean isClassTypeParameter, int parameterRank) { |
| return new TypeParametersAnnotationWalker(this.source, this.rankStarts[parameterRank], parameterRank, this.rankStarts, this.environment); |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toTypeBound(short boundIndex) { |
| // assume we are positioned either at the start of the bounded type parameter |
| // or at the start of a previous type bound |
| int p = this.pos; |
| int i = this.currentTypeBound; |
| while(true) { |
| // each bound is prefixed with ':' |
| int colon = CharOperation.indexOf(Util.C_COLON, this.source, p); |
| if (colon != -1) |
| p = colon + 1; |
| if (++i > boundIndex) break; |
| // skip next type: |
| p = wrapperWithStart(p).computeEnd()+1; |
| } |
| this.pos = p; |
| this.currentTypeBound = boundIndex; |
| return this; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toField() { |
| throw new UnsupportedOperationException("Cannot navigate to fields"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toMethodReturn() { |
| throw new UnsupportedOperationException("Cannot navigate to method return"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toMethodParameter(short index) { |
| throw new UnsupportedOperationException("Cannot navigate to method parameter"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toThrows(int index) { |
| throw new UnsupportedOperationException("Cannot navigate to throws"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public IBinaryAnnotation[] getAnnotationsAtCursor(int currentTypeId) { |
| if (this.pos != -1 && this.pos < this.source.length-1) { |
| switch (this.source[this.pos]) { |
| case NULLABLE: |
| return new IBinaryAnnotation[]{ ExternalAnnotationProvider.this.NULLABLE_ANNOTATION }; |
| case NONNULL: |
| return new IBinaryAnnotation[]{ ExternalAnnotationProvider.this.NONNULL_ANNOTATION }; |
| } |
| } |
| return super.getAnnotationsAtCursor(currentTypeId); |
| } |
| } |
| |
| /** Walker serving type annotations on a type's supertypes. */ |
| class SuperTypesAnnotationWalker extends BasicAnnotationWalker { |
| |
| SuperTypesAnnotationWalker(char[] source, LookupEnvironment environment) { |
| super(source, 0, environment); |
| } |
| |
| // actual implementation is inherited, main entries: toTypeArgument & getAnnotationsAtCursor |
| |
| @Override |
| public ITypeAnnotationWalker toField() { |
| throw new UnsupportedOperationException("Supertype has no field annotations"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toMethodReturn() { |
| throw new UnsupportedOperationException("Supertype has no method return"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toMethodParameter(short index) { |
| throw new UnsupportedOperationException("Supertype has no method parameter"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toThrows(int index) { |
| throw new UnsupportedOperationException("Supertype has no throws"); //$NON-NLS-1$ |
| } |
| } |
| |
| public interface IMethodAnnotationWalker extends ITypeAnnotationWalker { |
| int getParameterCount(); |
| } |
| class MethodAnnotationWalker extends BasicAnnotationWalker implements IMethodAnnotationWalker { |
| |
| int prevParamStart; |
| TypeParametersAnnotationWalker typeParametersWalker; |
| |
| MethodAnnotationWalker(char[] source, int pos, LookupEnvironment environment) { |
| super(source, pos, environment); |
| } |
| |
| int typeEnd(int start) { |
| while (this.source[start] == '[') { |
| start++; |
| char an = this.source[start]; |
| if (an == NULLABLE || an == NONNULL) |
| start++; |
| } |
| int end = wrapperWithStart(start).computeEnd(); |
| return end; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toTypeParameter(boolean isClassTypeParameter, int rank) { |
| if (this.source[0] == '<') { |
| if (this.typeParametersWalker == null) |
| return this.typeParametersWalker = new TypeParametersAnnotationWalker(this.source, this.pos+1, rank, null, this.environment); |
| return this.typeParametersWalker.toTypeParameter(isClassTypeParameter, rank); |
| } |
| return ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toTypeParameterBounds(boolean isClassTypeParameter, int parameterRank) { |
| if (this.typeParametersWalker != null) |
| return this.typeParametersWalker.toTypeParameterBounds(isClassTypeParameter, parameterRank); |
| return ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toMethodReturn() { |
| int close = CharOperation.indexOf(')', this.source); |
| if (close != -1) { |
| // optimization, see toMethodParameter. |
| this.pos = close+1; |
| return this; |
| } |
| return ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toMethodParameter(short index) { |
| if (index == 0) { |
| int start = CharOperation.indexOf('(', this.source) + 1; |
| this.prevParamStart = start; |
| // optimization: normally we should create a new walker with pos=start, |
| // but since we know the order how BTB/LE call us, we can safely use one walker for all parameters: |
| this.pos = start; |
| return this; |
| } |
| int end = typeEnd(this.prevParamStart); // leverage the fact that all parameters are evaluated in order |
| end++; |
| this.prevParamStart = end; |
| // optimization, see above. |
| this.pos = end; |
| return this; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toThrows(int index) { |
| return this; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toField() { |
| throw new UnsupportedOperationException("Methods have no fields"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public int getParameterCount() { |
| int count = 0; |
| int start = CharOperation.indexOf('(', this.source) + 1; |
| while (start < this.source.length && this.source[start] != ')') { |
| start = typeEnd(start) + 1; |
| count++; |
| } |
| return count; |
| } |
| } |
| |
| class FieldAnnotationWalker extends BasicAnnotationWalker { |
| public FieldAnnotationWalker(char[] source, int pos, LookupEnvironment environment) { |
| super(source, pos, environment); |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toField() { |
| return this; |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toMethodReturn() { |
| throw new UnsupportedOperationException("Field has no method return"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toMethodParameter(short index) { |
| throw new UnsupportedOperationException("Field has no method parameter"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public ITypeAnnotationWalker toThrows(int index) { |
| throw new UnsupportedOperationException("Field has no throws"); //$NON-NLS-1$ |
| } |
| } |
| } |