/******************************************************************************* * Copyright (c) 2006, 2013 BEA Systems, Inc. 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: * wharley@bea.com - initial API and implementation * IBM Corporation - Fix for bug 341494 * IBM Corporation - Fix for bug 328575 * IBM Corporation - Java 8 support *******************************************************************************/ package org.eclipse.jdt.internal.compiler.apt.model; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.apt.dispatch.BaseProcessingEnvImpl; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.Javadoc; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.impl.ReferenceContext; import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding; import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment; import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; import org.eclipse.jdt.internal.compiler.lookup.MethodVerifier; import org.eclipse.jdt.internal.compiler.lookup.PackageBinding; import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TagBits; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; /** * Utilities for working with language elements. * There is one of these for every ProcessingEnvironment. */ public class ElementsImpl implements Elements { // Used for parsing Javadoc comments: matches initial delimiter, followed by whitespace private static final Pattern INITIAL_DELIMITER = Pattern.compile("^\\s*/\\*+"); //$NON-NLS-1$ private final BaseProcessingEnvImpl _env; /* * The processing env creates and caches an ElementsImpl. Other clients should * not create their own; they should ask the env for it. */ public ElementsImpl(BaseProcessingEnvImpl env) { _env = env; } /** * Return all the annotation mirrors on this element, including inherited annotations. * Annotations are inherited only if the annotation type is meta-annotated with @Inherited, * and the annotation is on a class: e.g., annotations are not inherited for interfaces, methods, * or fields. */ @Override public List<? extends AnnotationMirror> getAllAnnotationMirrors(Element e) { // if e is a class, walk up its superclass hierarchy looking for @Inherited annotations not already in the list if (e.getKind() == ElementKind.CLASS && e instanceof TypeElementImpl) { List<AnnotationBinding> annotations = new ArrayList<AnnotationBinding>(); // A class can only have one annotation of a particular annotation type. Set<ReferenceBinding> annotationTypes = new HashSet<ReferenceBinding>(); ReferenceBinding binding = (ReferenceBinding)((TypeElementImpl)e)._binding; boolean checkIfInherited = false; while (null != binding) { if (binding instanceof ParameterizedTypeBinding) { binding = ((ParameterizedTypeBinding) binding).genericType(); } for (AnnotationBinding annotation : Factory.getPackedAnnotationBindings(binding.getAnnotations())) { if (annotation == null) continue; ReferenceBinding annotationType = annotation.getAnnotationType(); if (checkIfInherited && (annotationType.getAnnotationTagBits() & TagBits.AnnotationInherited) == 0) continue; if (!annotationTypes.contains(annotationType)) { annotationTypes.add(annotationType); annotations.add(annotation); } } binding = binding.superclass(); checkIfInherited = true; } List<AnnotationMirror> list = new ArrayList<AnnotationMirror>(annotations.size()); for (AnnotationBinding annotation : annotations) { list.add(_env.getFactory().newAnnotationMirror(annotation)); } return Collections.unmodifiableList(list); } else { return e.getAnnotationMirrors(); } } /** * Compute a list of all the visible entities in this type. Specifically: * <ul> * <li>All nested types declared in this type, including interfaces and enums</li> * <li>All protected or public nested types declared in this type's superclasses * and superinterfaces, that are not hidden by a name collision</li> * <li>All methods declared in this type, including constructors but not * including static or instance initializers, and including abstract * methods and unimplemented methods declared in interfaces</li> * <li>All protected or public methods declared in this type's superclasses, * that are not overridden by another method, but not including constructors * or initializers. Includes abstract methods and methods declared in * superinterfaces but not implemented</li> * <li>All fields declared in this type, including constants</li> * <li>All non-private fields declared in this type's superclasses and * superinterfaces, that are not hidden by a name collision.</li> * </ul> */ @Override public List<? extends Element> getAllMembers(TypeElement type) { if (null == type || !(type instanceof TypeElementImpl)) { return Collections.emptyList(); } ReferenceBinding binding = (ReferenceBinding)((TypeElementImpl)type)._binding; // Map of element simple name to binding Map<String, ReferenceBinding> types = new HashMap<String, ReferenceBinding>(); // Javac implementation does not take field name collisions into account List<FieldBinding> fields = new ArrayList<FieldBinding>(); // For methods, need to compare parameters, not just names Map<String, Set<MethodBinding>> methods = new HashMap<String, Set<MethodBinding>>(); Set<ReferenceBinding> superinterfaces = new LinkedHashSet<ReferenceBinding>(); boolean ignoreVisibility = true; while (null != binding) { addMembers(binding, ignoreVisibility, types, fields, methods); Set<ReferenceBinding> newfound = new LinkedHashSet<ReferenceBinding>(); collectSuperInterfaces(binding, superinterfaces, newfound); for (ReferenceBinding superinterface : newfound) { addMembers(superinterface, false, types, fields, methods); } superinterfaces.addAll(newfound); binding = binding.superclass(); ignoreVisibility = false; } List<Element> allMembers = new ArrayList<Element>(); for (ReferenceBinding nestedType : types.values()) { allMembers.add(_env.getFactory().newElement(nestedType)); } for (FieldBinding field : fields) { allMembers.add(_env.getFactory().newElement(field)); } for (Set<MethodBinding> sameNamedMethods : methods.values()) { for (MethodBinding method : sameNamedMethods) { allMembers.add(_env.getFactory().newElement(method)); } } return allMembers; } /** * Recursively depth-first walk the tree of superinterfaces of a type, collecting * all the unique superinterface bindings. (Note that because of generics, a type may * have multiple unique superinterface bindings corresponding to the same interface * declaration.) * @param existing bindings already in this set will not be re-added or recursed into * @param newfound newly found bindings will be added to this set */ private void collectSuperInterfaces(ReferenceBinding type, Set<ReferenceBinding> existing, Set<ReferenceBinding> newfound) { for (ReferenceBinding superinterface : type.superInterfaces()) { if (!existing.contains(superinterface) && !newfound.contains(superinterface)) { newfound.add(superinterface); collectSuperInterfaces(superinterface, existing, newfound); } } } /** * Add the members of a type to the maps of subtypes, fields, and methods. Add only those * which are non-private and which are not overridden by an already-discovered member. * For fields, add them all; javac implementation does not take field hiding into account. * @param binding the type whose members will be added to the lists * @param ignoreVisibility if true, all members will be added regardless of whether they * are private, overridden, etc. * @param types a map of type simple name to type binding * @param fields a list of field bindings * @param methods a map of method simple name to set of method bindings with that name */ private void addMembers(ReferenceBinding binding, boolean ignoreVisibility, Map<String, ReferenceBinding> types, List<FieldBinding> fields, Map<String, Set<MethodBinding>> methods) { for (ReferenceBinding subtype : binding.memberTypes()) { if (ignoreVisibility || !subtype.isPrivate()) { String name = new String(subtype.sourceName()); if (null == types.get(name)) { types.put(name, subtype); } } } for (FieldBinding field : binding.fields()) { if (ignoreVisibility || !field.isPrivate()) { fields.add(field); } } for (MethodBinding method : binding.methods()) { if (!method.isSynthetic() && (ignoreVisibility || (!method.isPrivate() && !method.isConstructor()))) { String methodName = new String(method.selector); Set<MethodBinding> sameNamedMethods = methods.get(methodName); if (null == sameNamedMethods) { // New method name. Create a set for it and add it to the list. // We don't expect many methods with same name, so only 4 slots: sameNamedMethods = new HashSet<MethodBinding>(4); methods.put(methodName, sameNamedMethods); sameNamedMethods.add(method); } else { // We already have a method with this name. Is this method overridden? boolean unique = true; if (!ignoreVisibility) { for (MethodBinding existing : sameNamedMethods) { MethodVerifier verifier = _env.getLookupEnvironment().methodVerifier(); if (verifier.doesMethodOverride(existing, method)) { unique = false; break; } } } if (unique) { sameNamedMethods.add(method); } } } } } /* (non-Javadoc) * @see javax.lang.model.util.Elements#getBinaryName(javax.lang.model.element.TypeElement) */ @Override public Name getBinaryName(TypeElement type) { TypeElementImpl typeElementImpl = (TypeElementImpl) type; ReferenceBinding referenceBinding = (ReferenceBinding) typeElementImpl._binding; return new NameImpl( CharOperation.replaceOnCopy(referenceBinding.constantPoolName(), '/', '.')); } /* (non-Javadoc) * @see javax.lang.model.util.Elements#getConstantExpression(java.lang.Object) */ @Override public String getConstantExpression(Object value) { if (!(value instanceof Integer) && !(value instanceof Byte) && !(value instanceof Float) && !(value instanceof Double) && !(value instanceof Long) && !(value instanceof Short) && !(value instanceof Character) && !(value instanceof String) && !(value instanceof Boolean)) { throw new IllegalArgumentException("Not a valid wrapper type : " + value.getClass()); //$NON-NLS-1$ } if (value instanceof Character) { StringBuilder builder = new StringBuilder(); builder.append('\'').append(value).append('\''); return String.valueOf(builder); } else if (value instanceof String) { StringBuilder builder = new StringBuilder(); builder.append('\"').append(value).append('\"'); return String.valueOf(builder); } else if (value instanceof Float) { StringBuilder builder = new StringBuilder(); builder.append(value).append('f'); return String.valueOf(builder); } else if (value instanceof Long) { StringBuilder builder = new StringBuilder(); builder.append(value).append('L'); return String.valueOf(builder); } else if (value instanceof Short) { StringBuilder builder = new StringBuilder(); builder.append("(short)").append(value); //$NON-NLS-1$ return String.valueOf(builder); } else if (value instanceof Byte) { StringBuilder builder = new StringBuilder(); builder.append("(byte)0x"); //$NON-NLS-1$ int intValue = ((Byte) value).byteValue(); String hexString = Integer.toHexString(intValue & 0xFF); if (hexString.length() < 2) { builder.append('0'); } builder.append(hexString); return String.valueOf(builder); } return String.valueOf(value); } /* (non-Javadoc) * @see javax.lang.model.util.Elements#getDocComment(javax.lang.model.element.Element) */ @Override public String getDocComment(Element e) { char[] unparsed = getUnparsedDocComment(e); return formatJavadoc(unparsed); } /** * Return the entire javadoc comment on e, including the comment characters and whitespace * @param e an Element of any sort, possibly with a javadoc comment. * @return a String, or null if the comment is not available */ private char[] getUnparsedDocComment(Element e) { Javadoc javadoc = null; ReferenceContext referenceContext = null; switch(e.getKind()) { case ANNOTATION_TYPE : case CLASS : case ENUM : case INTERFACE : TypeElementImpl typeElementImpl = (TypeElementImpl) e; ReferenceBinding referenceBinding = (ReferenceBinding)typeElementImpl._binding; if (referenceBinding instanceof SourceTypeBinding) { SourceTypeBinding sourceTypeBinding = (SourceTypeBinding) referenceBinding; referenceContext = sourceTypeBinding.scope.referenceContext; javadoc = ((TypeDeclaration) referenceContext).javadoc; } break; case PACKAGE : // might need to handle javadoc of package-info.java file PackageElementImpl packageElementImpl = (PackageElementImpl) e; PackageBinding packageBinding = (PackageBinding) packageElementImpl._binding; char[][] compoundName = CharOperation.arrayConcat(packageBinding.compoundName, TypeConstants.PACKAGE_INFO_NAME); ReferenceBinding type = this._env.getLookupEnvironment().getType(compoundName); if (type != null && type.isValidBinding() && (type instanceof SourceTypeBinding)) { SourceTypeBinding sourceTypeBinding = (SourceTypeBinding) type; referenceContext = sourceTypeBinding.scope.referenceContext; javadoc = ((TypeDeclaration) referenceContext).javadoc; } break; case CONSTRUCTOR : case METHOD : ExecutableElementImpl executableElementImpl = (ExecutableElementImpl) e; MethodBinding methodBinding = (MethodBinding) executableElementImpl._binding; AbstractMethodDeclaration sourceMethod = methodBinding.sourceMethod(); if (sourceMethod != null) { javadoc = sourceMethod.javadoc; referenceContext = sourceMethod; } break; case ENUM_CONSTANT : case FIELD : VariableElementImpl variableElementImpl = (VariableElementImpl) e; FieldBinding fieldBinding = (FieldBinding) variableElementImpl._binding; FieldDeclaration sourceField = fieldBinding.sourceField(); if (sourceField != null) { javadoc = sourceField.javadoc; if (fieldBinding.declaringClass instanceof SourceTypeBinding) { SourceTypeBinding sourceTypeBinding = (SourceTypeBinding) fieldBinding.declaringClass; referenceContext = sourceTypeBinding.scope.referenceContext; } } break; default: return null; } if (javadoc != null && referenceContext != null) { char[] contents = referenceContext.compilationResult().getCompilationUnit().getContents(); if (contents != null) { return CharOperation.subarray(contents, javadoc.sourceStart, javadoc.sourceEnd - 1); } } return null; } /** * Strip the comment characters from a javadoc comment. Assume the comment is already * missing its closing delimiter. * * Javac's behavior with regard to tab expansion and trimming of whitespace and * asterisks is bizarre and undocumented. We do our best here to emulate it. */ private static String formatJavadoc(char[] unparsed) { if (unparsed == null || unparsed.length < 5) { // delimiters take 5 chars return null; } String[] lines = new String(unparsed).split("\n"); //$NON-NLS-1$ Matcher delimiterMatcher = INITIAL_DELIMITER.matcher(lines[0]); if (!delimiterMatcher.find()) { return null; } int iOpener = delimiterMatcher.end(); lines[0] = lines[0].substring(iOpener); if (lines.length == 1) { // single-line comment. Should trim(), but javac doesn't. // we should however remove the starting whitespaces StringBuilder sb = new StringBuilder(); char[] chars = lines[0].toCharArray(); boolean startingWhitespaces = true; for (char c : chars) { if (Character.isWhitespace(c)) if (startingWhitespaces) { continue; } else { sb.append(c); } else { startingWhitespaces = false; sb.append(c); } } return sb.toString(); } // if the first line ends with spaces after the /** then we want to insert a line separator int firstLine = lines[0].trim().length() > 0 ? 0 : 1; // If the last line is now empty, skip it int lastLine = lines[lines.length - 1].trim().length() > 0 ? lines.length - 1 : lines.length - 2; StringBuilder sb = new StringBuilder(); if (lines[0].length() != 0 && firstLine == 1) { // insert a line separator only if the remaining chars on the line are whitespaces sb.append('\n'); } boolean preserveLineSeparator = lines[0].length() == 0; for (int line = firstLine; line <= lastLine; ++line) { char[] chars = lines[line].toCharArray(); int starsIndex = getStars(chars); int leadingWhitespaces = 0; boolean recordLeadingWhitespaces = true; for (int i = 0, max = chars.length; i < max; i++) { char c = chars[i]; switch(c) { case ' ' : if (starsIndex == -1) { if (recordLeadingWhitespaces) { leadingWhitespaces++; } else { sb.append(c); } } else if (i >= starsIndex) { sb.append(c); } break; default : // convert leadingwhitespaces to spaces recordLeadingWhitespaces = false; if (leadingWhitespaces != 0) { int numberOfTabs = leadingWhitespaces / 8; if (numberOfTabs != 0) { for (int j = 0, max2 = numberOfTabs; j < max2; j++) { sb.append(" "); //$NON-NLS-1$ } if ((leadingWhitespaces % 8) >= 1) { sb.append(' '); } } else if (line != 0) { // we don't want to preserve the leading spaces for the first line for (int j = 0, max2 = leadingWhitespaces; j < max2; j++) { sb.append(' '); } } leadingWhitespaces = 0; sb.append(c); } else if (c == '\t') { if (i >= starsIndex) { sb.append(c); } } else if (c != '*' || i > starsIndex) { sb.append(c); } } } // append a newline at the end of each line except the last, even if we skipped the last entirely int end = lines.length - 1; if (line < end) { sb.append('\n'); } else if (preserveLineSeparator && line == end) { sb.append('\n'); } } return sb.toString(); } /** * Returns the index of the last leading stars on this line, -1 if none. * * @param line the given line * @return the computed index */ private static int getStars(char[] line) { loop: for (int i = 0, max = line.length; i < max; i++) { char c = line[i]; if (!Character.isWhitespace(c)) { if (c == '*') { // only whitespaces before the first star // consume all stars and return the last index for (int j = i + 1; j < max; j++) { if (line[j] != '*') { return j; } } return max - 1; } // no need to continue break loop; } } return -1; } /** * @return all the annotation instance's explicitly set values, plus default values * for all the annotation members that are not explicitly set but that have * defaults. By comparison, {@link AnnotationMirror#getElementValues()} only * returns the explicitly set values. * @see javax.lang.model.util.Elements#getElementValuesWithDefaults(javax.lang.model.element.AnnotationMirror) */ @Override public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValuesWithDefaults( AnnotationMirror a) { return ((AnnotationMirrorImpl)a).getElementValuesWithDefaults(); } /* (non-Javadoc) * @see javax.lang.model.util.Elements#getName(java.lang.CharSequence) */ @Override public Name getName(CharSequence cs) { return new NameImpl(cs); } @Override public PackageElement getPackageElement(CharSequence name) { LookupEnvironment le = _env.getLookupEnvironment(); if (name.length() == 0) { return new PackageElementImpl(_env, le.defaultPackage); } char[] packageName = name.toString().toCharArray(); PackageBinding packageBinding = le.createPackage(CharOperation.splitOn('.', packageName)); if (packageBinding == null) { return null; } return new PackageElementImpl(_env, packageBinding); } @Override public PackageElement getPackageOf(Element type) { switch(type.getKind()) { case ANNOTATION_TYPE : case CLASS : case ENUM : case INTERFACE : TypeElementImpl typeElementImpl = (TypeElementImpl) type; ReferenceBinding referenceBinding = (ReferenceBinding)typeElementImpl._binding; return (PackageElement) _env.getFactory().newElement(referenceBinding.fPackage); case PACKAGE : return (PackageElement) type; case CONSTRUCTOR : case METHOD : ExecutableElementImpl executableElementImpl = (ExecutableElementImpl) type; MethodBinding methodBinding = (MethodBinding) executableElementImpl._binding; return (PackageElement) _env.getFactory().newElement(methodBinding.declaringClass.fPackage); case ENUM_CONSTANT : case FIELD : VariableElementImpl variableElementImpl = (VariableElementImpl) type; FieldBinding fieldBinding = (FieldBinding) variableElementImpl._binding; return (PackageElement) _env.getFactory().newElement(fieldBinding.declaringClass.fPackage); case PARAMETER : variableElementImpl = (VariableElementImpl) type; LocalVariableBinding localVariableBinding = (LocalVariableBinding) variableElementImpl._binding; return (PackageElement) _env.getFactory().newElement(localVariableBinding.declaringScope.classScope().referenceContext.binding.fPackage); case EXCEPTION_PARAMETER : case INSTANCE_INIT : case OTHER : case STATIC_INIT : case TYPE_PARAMETER : case LOCAL_VARIABLE : return null; } // unreachable return null; } /* (non-Javadoc) * @see javax.lang.model.util.Elements#getTypeElement(java.lang.CharSequence) */ @Override public TypeElement getTypeElement(CharSequence name) { LookupEnvironment le = _env.getLookupEnvironment(); final char[][] compoundName = CharOperation.splitOn('.', name.toString().toCharArray()); ReferenceBinding binding = le.getType(compoundName); // If we didn't find the binding, maybe it's a nested type; // try finding the top-level type and then working downwards. if (null == binding) { ReferenceBinding topLevelBinding = null; int topLevelSegments = compoundName.length; while (--topLevelSegments > 0) { char[][] topLevelName = new char[topLevelSegments][]; for (int i = 0; i < topLevelSegments; ++i) { topLevelName[i] = compoundName[i]; } topLevelBinding = le.getType(topLevelName); if (null != topLevelBinding) { break; } } if (null == topLevelBinding) { return null; } binding = topLevelBinding; for (int i = topLevelSegments; null != binding && i < compoundName.length; ++i) { binding = binding.getMemberType(compoundName[i]); } } if (null == binding) { return null; } return new TypeElementImpl(_env, binding, null); } /* (non-Javadoc) * Element A hides element B if: A and B are both fields, both nested types, or both methods; and * the enclosing element of B is a superclass or superinterface of the enclosing element of A. * See JLS 8.3 (for hiding of fields), 8.4.8.2 (hiding of class methods), and 8.5 (for hiding of member types). * @see javax.lang.model.util.Elements#hides(javax.lang.model.element.Element, javax.lang.model.element.Element) */ @Override public boolean hides(Element hider, Element hidden) { if (hidden == null) { // required by API spec throw new NullPointerException(); } return ((ElementImpl)hider).hides(hidden); } /* (non-Javadoc) * @see javax.lang.model.util.Elements#isDeprecated(javax.lang.model.element.Element) */ @Override public boolean isDeprecated(Element e) { if (!(e instanceof ElementImpl)) { return false; } return (((ElementImpl)e)._binding.getAnnotationTagBits() & TagBits.AnnotationDeprecated) != 0; } /* (non-Javadoc) * See JLS 8.4.8.1 for discussion of hiding of methods * @see javax.lang.model.util.Elements#overrides(javax.lang.model.element.ExecutableElement, javax.lang.model.element.ExecutableElement, javax.lang.model.element.TypeElement) */ @Override public boolean overrides(ExecutableElement overrider, ExecutableElement overridden, TypeElement type) { if (overridden == null || type == null) { throw new NullPointerException(); } return ((ExecutableElementImpl)overrider).overrides(overridden, type); } /* (non-Javadoc) * @see javax.lang.model.util.Elements#printElements(java.io.Writer, javax.lang.model.element.Element[]) */ @Override public void printElements(Writer w, Element... elements) { String lineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$ for (Element element : elements) { try { w.write(element.toString()); w.write(lineSeparator); } catch (IOException e) { // ignore } } try { w.flush(); } catch (IOException e) { // ignore } } public boolean isFunctionalInterface(TypeElement type) { if (type != null && type.getKind() == ElementKind.INTERFACE) { ReferenceBinding binding = (ReferenceBinding)((TypeElementImpl) type)._binding; if (binding instanceof SourceTypeBinding) { return binding.isFunctionalInterface(((SourceTypeBinding) binding).scope); } } return false; } }