| /******************************************************************************* |
| * Copyright (c) 2000, 2015 IBM Corporation and others. |
| * 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: |
| * IBM Corporation - initial API and implementation |
| * Stephan Herrmann - Contribution for |
| * Bug 466308 - [hovering] Javadoc header for parameter is wrong with annotation-based null analysis |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.core; |
| |
| import java.util.HashMap; |
| |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.jdt.core.*; |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.compiler.ast.ASTNode; |
| import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; |
| import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess; |
| import org.eclipse.jdt.internal.compiler.ast.Expression; |
| import org.eclipse.jdt.internal.compiler.ast.Literal; |
| import org.eclipse.jdt.internal.compiler.ast.NullLiteral; |
| import org.eclipse.jdt.internal.compiler.ast.OperatorIds; |
| import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; |
| import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; |
| import org.eclipse.jdt.internal.compiler.ast.UnaryExpression; |
| import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers; |
| import org.eclipse.jdt.internal.compiler.parser.RecoveryScanner; |
| import org.eclipse.jdt.internal.core.util.MementoTokenizer; |
| import org.eclipse.jdt.internal.core.util.Util; |
| |
| |
| public class LocalVariable extends SourceRefElement implements ILocalVariable { |
| |
| public static final ILocalVariable[] NO_LOCAL_VARIABLES = new ILocalVariable[0]; |
| |
| String name; |
| public int declarationSourceStart, declarationSourceEnd; |
| public int nameStart, nameEnd; |
| String typeSignature; |
| public IAnnotation[] annotations; |
| private int flags; |
| private boolean isParameter; |
| public IAnnotation[][] annotationsOnDimensions; |
| |
| public LocalVariable( |
| JavaElement parent, |
| String name, |
| int declarationSourceStart, |
| int declarationSourceEnd, |
| int nameStart, |
| int nameEnd, |
| String typeSignature, |
| org.eclipse.jdt.internal.compiler.ast.Annotation[] astAnnotations, |
| int flags, |
| boolean isParameter) { |
| |
| super(parent); |
| this.name = name; |
| this.declarationSourceStart = declarationSourceStart; |
| this.declarationSourceEnd = declarationSourceEnd; |
| this.nameStart = nameStart; |
| this.nameEnd = nameEnd; |
| this.typeSignature = typeSignature; |
| this.annotations = getAnnotations(astAnnotations); |
| this.flags = flags; |
| this.isParameter = isParameter; |
| } |
| public LocalVariable( |
| JavaElement parent, |
| String name, |
| int declarationSourceStart, |
| int declarationSourceEnd, |
| int nameStart, |
| int nameEnd, |
| String typeSignature, |
| org.eclipse.jdt.internal.compiler.ast.Annotation[] astAnnotations, |
| int flags, |
| boolean isParameter, |
| org.eclipse.jdt.internal.compiler.ast.Annotation[][] astAnnotationsOnDimensions) { |
| |
| this(parent, name, declarationSourceStart, declarationSourceEnd, nameStart, |
| nameEnd, typeSignature, astAnnotations, flags, isParameter); |
| |
| int noOfDimensions = astAnnotationsOnDimensions == null ? 0 : astAnnotationsOnDimensions.length; |
| if (noOfDimensions > 0) { |
| this.annotationsOnDimensions = new IAnnotation[noOfDimensions][]; |
| for (int i = 0; i < noOfDimensions; ++i) { |
| this.annotationsOnDimensions[i] = getAnnotations(astAnnotationsOnDimensions[i]); |
| } |
| } |
| } |
| |
| @Override |
| protected void closing(Object info) { |
| // a local variable has no info |
| } |
| |
| @Override |
| protected Object createElementInfo() { |
| // a local variable has no info |
| return null; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (!(o instanceof LocalVariable)) return false; |
| LocalVariable other = (LocalVariable)o; |
| return |
| this.declarationSourceStart == other.declarationSourceStart |
| && this.declarationSourceEnd == other.declarationSourceEnd |
| && this.nameStart == other.nameStart |
| && this.nameEnd == other.nameEnd |
| && super.equals(o); |
| } |
| |
| @Override |
| public boolean exists() { |
| return this.parent.exists(); // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=46192 |
| } |
| |
| @Override |
| protected void generateInfos(Object info, HashMap newElements, IProgressMonitor pm) { |
| // a local variable has no info |
| } |
| |
| @Override |
| public IAnnotation getAnnotation(String annotationName) { |
| for (int i = 0, length = this.annotations.length; i < length; i++) { |
| IAnnotation annotation = this.annotations[i]; |
| if (annotation.getElementName().equals(annotationName)) |
| return annotation; |
| } |
| return super.getAnnotation(annotationName); |
| } |
| |
| @Override |
| public IAnnotation[] getAnnotations() throws JavaModelException { |
| return this.annotations; |
| } |
| |
| private IAnnotation[] getAnnotations(org.eclipse.jdt.internal.compiler.ast.Annotation[] astAnnotations) { |
| int length; |
| if (astAnnotations == null || (length = astAnnotations.length) == 0) |
| return Annotation.NO_ANNOTATIONS; |
| IAnnotation[] result = new IAnnotation[length]; |
| for (int i = 0; i < length; i++) { |
| result[i] = getAnnotation(astAnnotations[i], this); |
| } |
| return result; |
| } |
| |
| private IAnnotation getAnnotation(final org.eclipse.jdt.internal.compiler.ast.Annotation annotation, JavaElement parentElement) { |
| final int typeStart = annotation.type.sourceStart(); |
| final int typeEnd = annotation.type.sourceEnd(); |
| final int sourceStart = annotation.sourceStart(); |
| final int sourceEnd = annotation.declarationSourceEnd; |
| class LocalVarAnnotation extends Annotation { |
| IMemberValuePair[] memberValuePairs; |
| public LocalVarAnnotation(JavaElement localVar, String elementName) { |
| super(localVar, elementName); |
| } |
| @Override |
| public IMemberValuePair[] getMemberValuePairs() throws JavaModelException { |
| return this.memberValuePairs; |
| } |
| @Override |
| public ISourceRange getNameRange() throws JavaModelException { |
| return new SourceRange(typeStart, typeEnd - typeStart + 1); |
| } |
| @Override |
| public ISourceRange getSourceRange() throws JavaModelException { |
| return new SourceRange(sourceStart, sourceEnd - sourceStart + 1); |
| } |
| @Override |
| public boolean exists() { |
| return this.parent.exists(); |
| } |
| } |
| String annotationName = new String(CharOperation.concatWith(annotation.type.getTypeName(), '.')); |
| LocalVarAnnotation localVarAnnotation = new LocalVarAnnotation(parentElement, annotationName); |
| org.eclipse.jdt.internal.compiler.ast.MemberValuePair[] astMemberValuePairs = annotation.memberValuePairs(); |
| int length; |
| IMemberValuePair[] memberValuePairs; |
| if (astMemberValuePairs == null || (length = astMemberValuePairs.length) == 0) { |
| memberValuePairs = Annotation.NO_MEMBER_VALUE_PAIRS; |
| } else { |
| memberValuePairs = new IMemberValuePair[length]; |
| for (int i = 0; i < length; i++) { |
| org.eclipse.jdt.internal.compiler.ast.MemberValuePair astMemberValuePair = astMemberValuePairs[i]; |
| MemberValuePair memberValuePair = new MemberValuePair(new String(astMemberValuePair.name)); |
| memberValuePair.value = getAnnotationMemberValue(memberValuePair, astMemberValuePair.value, localVarAnnotation); |
| memberValuePairs[i] = memberValuePair; |
| } |
| } |
| localVarAnnotation.memberValuePairs = memberValuePairs; |
| return localVarAnnotation; |
| } |
| |
| /* |
| * Creates the value wrapper from the given expression, and sets the valueKind on the given memberValuePair |
| */ |
| private Object getAnnotationMemberValue(MemberValuePair memberValuePair, Expression expression, JavaElement parentElement) { |
| if (expression instanceof NullLiteral) { |
| return null; |
| } else if (expression instanceof Literal) { |
| ((Literal) expression).computeConstant(); |
| return Util.getAnnotationMemberValue(memberValuePair, expression.constant); |
| } else if (expression instanceof org.eclipse.jdt.internal.compiler.ast.Annotation) { |
| memberValuePair.valueKind = IMemberValuePair.K_ANNOTATION; |
| return getAnnotation((org.eclipse.jdt.internal.compiler.ast.Annotation) expression, parentElement); |
| } else if (expression instanceof ClassLiteralAccess) { |
| ClassLiteralAccess classLiteral = (ClassLiteralAccess) expression; |
| char[] typeName = CharOperation.concatWith(classLiteral.type.getTypeName(), '.'); |
| memberValuePair.valueKind = IMemberValuePair.K_CLASS; |
| return new String(typeName); |
| } else if (expression instanceof QualifiedNameReference) { |
| char[] qualifiedName = CharOperation.concatWith(((QualifiedNameReference) expression).tokens, '.'); |
| memberValuePair.valueKind = IMemberValuePair.K_QUALIFIED_NAME; |
| return new String(qualifiedName); |
| } else if (expression instanceof SingleNameReference) { |
| char[] simpleName = ((SingleNameReference) expression).token; |
| if (simpleName == RecoveryScanner.FAKE_IDENTIFIER) { |
| memberValuePair.valueKind = IMemberValuePair.K_UNKNOWN; |
| return null; |
| } |
| memberValuePair.valueKind = IMemberValuePair.K_SIMPLE_NAME; |
| return new String(simpleName); |
| } else if (expression instanceof ArrayInitializer) { |
| memberValuePair.valueKind = -1; // modified below by the first call to getMemberValue(...) |
| Expression[] expressions = ((ArrayInitializer) expression).expressions; |
| int length = expressions == null ? 0 : expressions.length; |
| Object[] values = new Object[length]; |
| for (int i = 0; i < length; i++) { |
| int previousValueKind = memberValuePair.valueKind; |
| Object value = getAnnotationMemberValue(memberValuePair, expressions[i], parentElement); |
| if (previousValueKind != -1 && memberValuePair.valueKind != previousValueKind) { |
| // values are heterogeneous, value kind is thus unknown |
| memberValuePair.valueKind = IMemberValuePair.K_UNKNOWN; |
| } |
| values[i] = value; |
| } |
| if (memberValuePair.valueKind == -1) |
| memberValuePair.valueKind = IMemberValuePair.K_UNKNOWN; |
| return values; |
| } else if (expression instanceof UnaryExpression) { //to deal with negative numerals (see bug - 248312) |
| UnaryExpression unaryExpression = (UnaryExpression) expression; |
| if ((unaryExpression.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT == OperatorIds.MINUS) { |
| if (unaryExpression.expression instanceof Literal) { |
| Literal subExpression = (Literal) unaryExpression.expression; |
| subExpression.computeConstant(); |
| return Util.getNegativeAnnotationMemberValue(memberValuePair, subExpression.constant); |
| } |
| } |
| memberValuePair.valueKind = IMemberValuePair.K_UNKNOWN; |
| return null; |
| } else { |
| memberValuePair.valueKind = IMemberValuePair.K_UNKNOWN; |
| return null; |
| } |
| } |
| |
| @Override |
| public IJavaElement getHandleFromMemento(String token, MementoTokenizer memento, WorkingCopyOwner owner) { |
| switch (token.charAt(0)) { |
| case JEM_COUNT: |
| return getHandleUpdatingCountFromMemento(memento, owner); |
| } |
| return this; |
| } |
| |
| @Override |
| protected void getHandleMemento(StringBuffer buff) { |
| getHandleMemento(buff, true); |
| } |
| |
| protected void getHandleMemento(StringBuffer buff, boolean memoizeParent) { |
| if (memoizeParent) |
| ((JavaElement)getParent()).getHandleMemento(buff); |
| buff.append(getHandleMementoDelimiter()); |
| buff.append(this.name); |
| buff.append(JEM_COUNT); |
| buff.append(this.declarationSourceStart); |
| buff.append(JEM_COUNT); |
| buff.append(this.declarationSourceEnd); |
| buff.append(JEM_COUNT); |
| buff.append(this.nameStart); |
| buff.append(JEM_COUNT); |
| buff.append(this.nameEnd); |
| buff.append(JEM_COUNT); |
| escapeMementoName(buff, this.typeSignature); |
| buff.append(JEM_COUNT); |
| buff.append(this.flags); |
| buff.append(JEM_COUNT); |
| buff.append(this.isParameter); |
| if (this.occurrenceCount > 1) { |
| buff.append(JEM_COUNT); |
| buff.append(this.occurrenceCount); |
| } |
| } |
| |
| @Override |
| protected char getHandleMementoDelimiter() { |
| return JavaElement.JEM_LOCALVARIABLE; |
| } |
| |
| @Override |
| public IResource getCorrespondingResource() { |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @since 3.7 |
| */ |
| @Override |
| public IMember getDeclaringMember() { |
| return (IMember) this.parent; |
| } |
| |
| @Override |
| public String getElementName() { |
| return this.name; |
| } |
| |
| @Override |
| public int getElementType() { |
| return LOCAL_VARIABLE; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @since 3.7 |
| */ |
| @Override |
| public int getFlags() { |
| if (this.flags == -1) { |
| SourceMapper mapper= getSourceMapper(); |
| if (mapper != null) { |
| try { |
| // ensure the class file's buffer is open so that source ranges are computed |
| ClassFile classFile = (ClassFile)getClassFile(); |
| if (classFile != null) { |
| classFile.getBuffer(); |
| return mapper.getFlags(this); |
| } |
| } catch(JavaModelException e) { |
| // ignore |
| } |
| } |
| return 0; |
| } |
| return this.flags & ExtraCompilerModifiers.AccJustFlag; |
| } |
| |
| /** |
| * @see IMember#getClassFile() |
| */ |
| @Override |
| public IClassFile getClassFile() { |
| IJavaElement element = getParent(); |
| while (element instanceof IMember) { |
| element= element.getParent(); |
| } |
| if (element instanceof IClassFile) { |
| return (IClassFile) element; |
| } |
| return null; |
| } |
| /** |
| * {@inheritDoc} |
| * @since 3.7 |
| */ |
| @Override |
| public ISourceRange getNameRange() { |
| if (this.nameEnd == -1) { |
| SourceMapper mapper= getSourceMapper(); |
| if (mapper != null) { |
| try { |
| // ensure the class file's buffer is open so that source ranges are computed |
| ClassFile classFile = (ClassFile)getClassFile(); |
| if (classFile != null) { |
| classFile.getBuffer(); |
| return mapper.getNameRange(this); |
| } |
| } catch(JavaModelException e) { |
| // ignore |
| } |
| } |
| return SourceMapper.UNKNOWN_RANGE; |
| } |
| return new SourceRange(this.nameStart, this.nameEnd-this.nameStart+1); |
| } |
| |
| @Override |
| public IPath getPath() { |
| return this.parent.getPath(); |
| } |
| |
| @Override |
| public IResource resource() { |
| return this.parent.resource(); |
| } |
| |
| /** |
| * @see ISourceReference |
| */ |
| @Override |
| public String getSource() throws JavaModelException { |
| IOpenable openable = this.parent.getOpenableParent(); |
| IBuffer buffer = openable.getBuffer(); |
| if (buffer == null) { |
| return null; |
| } |
| ISourceRange range = getSourceRange(); |
| int offset = range.getOffset(); |
| int length = range.getLength(); |
| if (offset == -1 || length == 0 ) { |
| return null; |
| } |
| try { |
| return buffer.getText(offset, length); |
| } catch(RuntimeException e) { |
| return null; |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @since 3.7 |
| */ |
| @Override |
| public ISourceRange getSourceRange() throws JavaModelException { |
| if (this.declarationSourceEnd == -1) { |
| SourceMapper mapper= getSourceMapper(); |
| if (mapper != null) { |
| // ensure the class file's buffer is open so that source ranges are computed |
| ClassFile classFile = (ClassFile)getClassFile(); |
| if (classFile != null) { |
| classFile.getBuffer(); |
| return mapper.getSourceRange(this); |
| } |
| } |
| return SourceMapper.UNKNOWN_RANGE; |
| } |
| return new SourceRange(this.declarationSourceStart, this.declarationSourceEnd-this.declarationSourceStart+1); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @since 3.7 |
| */ |
| @Override |
| public ITypeRoot getTypeRoot() { |
| return this.getDeclaringMember().getTypeRoot(); |
| } |
| |
| @Override |
| public String getTypeSignature() { |
| return this.typeSignature; |
| } |
| |
| @Override |
| public IResource getUnderlyingResource() throws JavaModelException { |
| return this.parent.getUnderlyingResource(); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Util.combineHashCodes(this.parent.hashCode(), this.nameStart); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @since 3.7 |
| */ |
| @Override |
| public boolean isParameter() { |
| return this.isParameter; |
| } |
| |
| @Override |
| public boolean isStructureKnown() throws JavaModelException { |
| return true; |
| } |
| |
| /** |
| * @see org.eclipse.jdt.internal.compiler.lookup.Binding#computeUniqueKey() |
| */ |
| public String getKey(boolean forceOpen) throws JavaModelException { |
| if (this.parent.getElementType() == IJavaElement.METHOD) { |
| StringBuilder buf = new StringBuilder(); |
| if (this.parent instanceof BinaryMethod) |
| buf.append(((BinaryMethod) this.parent).getKey(forceOpen)); |
| else |
| buf.append(((IMethod)this.parent).getKey()); |
| buf.append('#'); |
| buf.append(this.name); |
| if (this.isParameter) { |
| ILocalVariable[] parameters = ((IMethod) this.parent).getParameters(); |
| for (int i = 0; i < parameters.length; i++) { |
| if (this.equals(parameters[i])) { |
| buf.append("#0#").append(i); // always first occurrence, followed by parameter rank //$NON-NLS-1$ |
| break; |
| } |
| } |
| } |
| return buf.toString(); |
| } |
| return null; |
| } |
| |
| @Override |
| protected void toStringInfo(int tab, StringBuffer buffer, Object info, boolean showResolvedInfo) { |
| buffer.append(tabString(tab)); |
| if (info != NO_INFO) { |
| buffer.append(Signature.toString(getTypeSignature())); |
| buffer.append(" "); //$NON-NLS-1$ |
| } |
| toStringName(buffer); |
| } |
| |
| } |