/*
* Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
*
* 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.jboss.errai.processor;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import javax.annotation.processing.ProcessingEnvironment;
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.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
/**
* An indiscriminate dumping ground for static methods that help paper over the
* missing functionality in the Java 6 Annotation Processing API.
* <p>
* Do your worst! :-)
*
* @author jfuerth
*/
public class AnnotationProcessors {
/** Prevents instantiation. */
private AnnotationProcessors() {
}
public static boolean hasAnnotation(final Element target, final CharSequence annotationQualifiedName) {
return getAnnotation(target, annotationQualifiedName) != null;
}
public static AnnotationMirror getAnnotation(final Element target, final CharSequence annotationQualifiedName) {
return getAnnotation(target.getAnnotationMirrors(), annotationQualifiedName);
}
public static AnnotationMirror getAnnotation(final List<? extends AnnotationMirror> annotationMirrors, final CharSequence annotationQualifiedName) {
for (final AnnotationMirror am : annotationMirrors) {
final Name annotationClassName = ((TypeElement) am.getAnnotationType().asElement()).getQualifiedName();
if (annotationClassName.contentEquals(annotationQualifiedName)) {
return am;
}
}
return null;
}
/**
* Retrieves a parameter value from an annotation that targets the given
* element. The returned value does not take defaults into consideration.
*
* @param target
* The element targeted by an instance of the given annotation. Could
* be a class, field, method, or anything else.
* @param annotationQualifiedName
* The fully-qualified name of the annotation to retrieve the
* parameter value from.
* @param paramName
* the name of the annotation parameter to retrieve.
* @return the String value of the given annotation's parameter, or null if
* the parameter is not present on the annotation.
*/
static AnnotationValue getAnnotationParamValueWithoutDefaults(final Element target, final CharSequence annotationQualifiedName,
final CharSequence paramName) {
final AnnotationMirror templatedAnnotation = getAnnotation(target, annotationQualifiedName);
final Map<? extends ExecutableElement, ? extends AnnotationValue> annotationParams = templatedAnnotation
.getElementValues();
for (final Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> param : annotationParams.entrySet()) {
if (param.getKey().getSimpleName().contentEquals(paramName)) {
return param.getValue();
}
}
return null;
}
/**
* Retrieves a string parameter value from an annotation.
*
* @param elements
* Reference to the element utilities see
* {@link ProcessingEnvironment#getElementUtils()}.
* @param annotation
* The annotation to retrieve the parameter value from.
* @param paramName
* the name of the annotation parameter to retrieve.
* @return the String value of the given annotation's parameter, or null if
* the parameter is not present on the annotation.
*/
public static String extractAnnotationStringValue(final Elements elementUtils, final AnnotationMirror annotation,
final CharSequence paramName) {
final AnnotationValue av = extractAnnotationPropertyValue(elementUtils, annotation, paramName);
if (av != null && av.getValue() != null) {
return av.getValue().toString();
}
return null;
}
public static AnnotationValue extractAnnotationPropertyValue(final Elements elementUtils, final AnnotationMirror annotation,
final CharSequence annotationProperty) {
final Map<? extends ExecutableElement, ? extends AnnotationValue> annotationParams = elementUtils
.getElementValuesWithDefaults(annotation);
for (final Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> param : annotationParams.entrySet()) {
if (param.getKey().getSimpleName().contentEquals(annotationProperty)) {
return param.getValue();
}
}
return null;
}
static Optional<Element> getDeclaredField(final TypeElement classElement, final CharSequence fieldName) {
if (fieldName.charAt(0) == '\"') {
throw new IllegalArgumentException("given field name begins with invalid character: " + fieldName.charAt(0));
}
for (final Element field : ElementFilter.fieldsIn(classElement.getEnclosedElements())) {
if (field.getSimpleName().contentEquals(fieldName)) {
return Optional.of(field);
}
}
return Optional.empty();
}
static Optional<Element> getField(final TypeElement classElement, final CharSequence fieldName) {
final Optional<Element> oField = getDeclaredField(classElement, fieldName);
if (oField.isPresent()) {
return oField;
}
else {
final TypeMirror superclass = classElement.getSuperclass();
if (superclass instanceof DeclaredType) {
final TypeElement superclassElement = (TypeElement) ((DeclaredType) superclass).asElement();
return getField(superclassElement, fieldName);
}
else {
return Optional.empty();
}
}
}
public static TypeElement getEnclosingTypeElement(final Element element) {
Element currentElement = element;
while (currentElement != null && currentElement.getKind() != ElementKind.CLASS) {
currentElement = currentElement.getEnclosingElement();
}
if (currentElement == null) {
throw new RuntimeException("No enclosing class for " + element);
}
return (TypeElement) currentElement;
}
/**
* Returns the JavaBeans property name defined by the given method element, if
* it does in fact define a property name. The name is calculated by stripping
* off the prefix "is", "get", or "set" and then converting the new initial
* character to lowercase.
*
* @param el
* The method element to extract a property name from.
* @return the property name defined by the method according to JavaBeans
* convention, or null if the method does not define a JavaBeans
* property setter/getter.
*/
static String propertyNameOfMethod(final Element el) {
final Name methodName = el.getSimpleName();
String propertyName = null;
if (methodName.length() > 3 && "get".contentEquals(methodName.subSequence(0, 3))) {
final StringBuilder sb = new StringBuilder(methodName.length() - 3);
sb.append(Character.toLowerCase(methodName.charAt(3)));
sb.append(methodName.subSequence(4, methodName.length()));
propertyName = sb.toString();
}
else if (methodName.length() > 2 && "is".contentEquals(methodName.subSequence(0, 2))) {
final StringBuilder sb = new StringBuilder(methodName.length() - 2);
sb.append(Character.toLowerCase(methodName.charAt(2)));
sb.append(methodName.subSequence(3, methodName.length()));
propertyName = sb.toString();
}
else if (methodName.length() > 3 && "set".contentEquals(methodName.subSequence(0, 2))) {
final StringBuilder sb = new StringBuilder(methodName.length() - 3);
sb.append(Character.toLowerCase(methodName.charAt(3)));
sb.append(methodName.subSequence(4, methodName.length()));
propertyName = sb.toString();
}
return propertyName;
}
public static boolean isNativeJsType(final TypeMirror targetType, final Elements elements) {
final AnnotationMirror am = getAnnotation(((DeclaredType) targetType).asElement(), TypeNames.JS_TYPE);
final AnnotationValue isNativeValue = (am != null ? extractAnnotationPropertyValue(elements, am, "isNative") : null);
return isNativeValue != null && (Boolean) isNativeValue.getValue();
}
public static boolean isBrowserEvent(final TypeMirror targetType, final Elements elements) {
final AnnotationMirror am = getAnnotation(((DeclaredType) targetType).asElement(), TypeNames.BROWSER_EVENT);
return am != null;
}
public static boolean isElementWrapper(final TypeMirror targetType, final Elements elements) {
final AnnotationMirror am = getAnnotation(((DeclaredType) targetType).asElement(), TypeNames.NATIVE_ELEMENT);
return am != null;
}
public static boolean isTemplated(final TypeMirror targetType, final Elements elements) {
final AnnotationMirror am = getAnnotation(((DeclaredType) targetType).asElement(), TypeNames.TEMPLATED);
return am != null;
}
public static Optional<TypeMirror> findSuperType(final TypeMirror targetType, final TypeMirror superType, final Types types) {
final TypeMirror erasedSuperType = types.erasure(superType);
return getAllSuperTypes(targetType, types)
.filter(t -> types.isSameType(types.erasure(t), erasedSuperType))
.findFirst();
}
public static Optional<TypeMirror> resolveSingleTypeArgumentForGenericSuperType(final TypeMirror targetType,
final TypeMirror superTypeWithParameter, final Types types) {
return findSuperType(targetType, superTypeWithParameter, types)
.filter(t -> t instanceof DeclaredType)
.map(t -> ((DeclaredType) t).getTypeArguments())
.filter(typeArgs -> typeArgs.size() == 1)
.map(typeArgs -> typeArgs.get(0));
}
public static Stream<TypeMirror> getAllSuperTypes(final TypeMirror type, final Types types) {
return Stream.concat(Stream.of(type), types.directSupertypes(type).stream().flatMap(t -> getAllSuperTypes(t, types)));
}
}