org.richfaces.cdk.apt.AptSourceUtils Maven / Gradle / Ivy
The newest version!
package org.richfaces.cdk.apt;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
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.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import org.richfaces.cdk.CdkException;
import org.richfaces.cdk.Logger;
import org.richfaces.cdk.model.ClassName;
import org.richfaces.cdk.model.FacesId;
import org.richfaces.cdk.util.PropertyUtils;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
/**
*
* Implementation to use in annotation processor.
*
*
* @author [email protected]
*
*/
public class AptSourceUtils implements SourceUtils {
private static final String IS = "is";
private static final int IS_LENGTH = IS.length();
private static final String GET = "get";
private static final String SET = "set";
private static final int SET_LENGTH = SET.length();
private static final int GET_LENGTH = GET.length();
private static final ImmutableSet HIDDEN_PROPERTIES = ImmutableSet.of("eventNames", "defaultEventName",
"clientBehaviors", "family", "class");
private final ProcessingEnvironment processingEnv;
private Map> beanPropertyCache = Maps.newHashMap();
@Inject
private Logger log;
/**
*
*
*
* @param processingEnv
*/
public AptSourceUtils(ProcessingEnvironment processingEnv) {
this.processingEnv = processingEnv;
}
/**
*
* Get all fields and bean properties that are annotated with given annotation.
*
*
* @param annotation
* @param type
* @return
*/
public Set getBeanPropertiesAnnotatedWith(Class extends Annotation> annotation, TypeElement type) {
Set properties = new HashSet();
Map beanProperties = getBeanProperties(type);
for (BeanProperty beanProperty : beanProperties.values()) {
if (beanProperty.isAnnotationPresent(annotation)) {
properties.add(beanProperty);
}
}
return properties;
}
public Set getAbstractBeanProperties(TypeElement type) {
Set properties = new HashSet();
Map beanProperties = getBeanProperties(type);
for (BeanProperty beanProperty : beanProperties.values()) {
if (!beanProperty.isExists()) {
properties.add(beanProperty);
}
}
return properties;
}
@Override
public BeanProperty getBeanProperty(ClassName type, final String name) {
return getBeanProperty(asTypeElement(type), name);
}
@Override
public BeanProperty getBeanProperty(TypeElement type, final String name) {
Map beanProperties = getBeanProperties(type);
if (beanProperties.containsKey(name)) {
return beanProperties.get(name);
} else {
return new DummyPropertyImpl(name);
}
}
/**
*
* Utility method to get all bean properties, similar to Introspector
*
*
* @param type
* @return
*/
Map getBeanProperties(TypeElement type) {
if (null == type) {
return Collections.emptyMap();
}
Name qName = type.getQualifiedName();
Map result = beanPropertyCache.get(qName);
if (result != null) {
return result;
}
result = Maps.newHashMap();
List extends Element> members = this.processingEnv.getElementUtils().getAllMembers(type);
// extract all getters/setters.
for (Element element : members) {
if (ElementKind.METHOD.equals(element.getKind())) {
ExecutableElement method = (ExecutableElement) element;
processMethod(type, result, method);
}
}
beanPropertyCache.put(qName, result);
return result;
}
private void processMethod(TypeElement type, Map result, ExecutableElement method) {
if (isPublicNonStatic(method)) {
if (isGetter(method)) {
processBeanPropertyAccessor(type, result, method, false);
} else if (isSetter(method)) {
processBeanPropertyAccessor(type, result, method, true);
}
}
}
private void processBeanPropertyAccessor(TypeElement type, Map result, ExecutableElement method,
boolean setter) {
String propertyName = getPropertyName(method);
if (!HIDDEN_PROPERTIES.contains(propertyName)) {
ClassName propertyType = asClassDescription(setter ? method.getParameters().get(0).asType() : method
.getReturnType());
if (result.containsKey(propertyName)) {
// Merge property with existed one.
AptBeanProperty beanProperty = result.get(propertyName);
checkPropertyType(type, propertyName, propertyType, beanProperty);
if (null != (setter ? beanProperty.setter : beanProperty.getter)) {
log.debug("Two " + (setter ? "setter" : "getter") + " methods for the same bean property " + propertyName
+ " in the class " + type.getQualifiedName());
if (!method.getModifiers().contains(Modifier.ABSTRACT)) {
beanProperty.setAccessMethod(method, setter);
}
} else {
beanProperty.setAccessMethod(method, setter);
}
} else {
AptBeanProperty beanProperty = new AptBeanProperty(propertyName);
beanProperty.setAccessMethod(method, setter);
beanProperty.type = propertyType;
result.put(propertyName, beanProperty);
}
}
}
private String getPropertyName(ExecutableElement method) {
return PropertyUtils.methodToName(method.getSimpleName().toString());
}
private void checkPropertyType(TypeElement type, String propertyName, ClassName propertyType, AptBeanProperty beanProperty) {
if (!propertyType.equals(beanProperty.type)) {
log.warn("Unambiguious type for bean property " + propertyName + " in the class " + type.getQualifiedName());
}
}
private boolean isAbstract(ExecutableElement method) {
return method.getModifiers().contains(Modifier.ABSTRACT);
}
private boolean isPublicNonStatic(ExecutableElement method) {
Set modifiers = method.getModifiers();
return modifiers.contains(Modifier.PUBLIC) && !modifiers.contains(Modifier.STATIC);
}
private boolean isGetter(ExecutableElement e) {
String methodName = e.getSimpleName().toString();
return (isGetterName(methodName) || isBooleanGetterName(methodName)) && 0 == e.getParameters().size();
}
private boolean isGetterName(String methodName) {
return methodName.startsWith(GET) && methodName.length() > GET_LENGTH
&& Character.isUpperCase(methodName.charAt(GET_LENGTH));
}
private boolean isBooleanGetterName(String methodName) {
return methodName.startsWith(IS) && methodName.length() > IS_LENGTH
&& Character.isUpperCase(methodName.charAt(IS_LENGTH));
}
private boolean isSetter(ExecutableElement e) {
String methodName = e.getSimpleName().toString();
return isSetterName(methodName) && 1 == e.getParameters().size() && !e.isVarArgs()
&& TypeKind.VOID.equals(e.getReturnType().getKind());
}
private boolean isSetterName(String methodName) {
return methodName.startsWith(SET) && methodName.length() > SET_LENGTH
&& Character.isUpperCase(methodName.charAt(SET_LENGTH));
}
private ClassName asClassDescription(TypeMirror type) {
return new ClassName(type.toString());
}
public String getDocComment(Element componentElement) {
return this.processingEnv.getElementUtils().getDocComment(componentElement);
}
@Override
public AnnotationMirror getAnnotationMirror(Element element, Class extends Annotation> annotationType) {
List extends AnnotationMirror> annotationMirrors = processingEnv.getElementUtils().getAllAnnotationMirrors(element);
TypeMirror annotationTypeMirror = processingEnv.getElementUtils().getTypeElement(annotationType.getName()).asType();
for (AnnotationMirror annotationMirror : annotationMirrors) {
if (processingEnv.getTypeUtils().isSameType(annotationTypeMirror, annotationMirror.getAnnotationType())) {
return annotationMirror;
}
}
return null;
}
@Override
public boolean isAnnotationPresent(Element element, Class extends Annotation> annotationType) {
return null != element.getAnnotation(annotationType);
}
@Override
public boolean isAnnotationPropertyPresent(AnnotationMirror annotation, final String propertyName) {
return Iterables.any(getAnnotationValuesMap(annotation).entrySet(), new AnnotationAttributePredicate(propertyName));
}
@Override
public boolean isDefaultValue(AnnotationMirror annotation, String propertyName) {
Map.Entry extends ExecutableElement, ? extends AnnotationValue> attributeEntry = findAnnotationProperty(annotation,
propertyName);
return !annotation.getElementValues().containsKey(attributeEntry.getKey());
}
@Override
public T getAnnotationValue(AnnotationMirror annotation, String propertyName, Class expectedType) {
Map.Entry extends ExecutableElement, ? extends AnnotationValue> attributeEntry = findAnnotationProperty(annotation,
propertyName);
AnnotationValue annotationValue = attributeEntry.getValue();
return convertAnnotationValue(expectedType, annotationValue);
}
@SuppressWarnings("unchecked")
private T convertAnnotationValue(Class expectedType, AnnotationValue annotationValue) {
if (Enum.class.isAssignableFrom(expectedType)) {
VariableElement variable = (VariableElement) annotationValue.getValue();
return (T) Enum.valueOf((Class extends Enum>) expectedType, variable.getSimpleName().toString());
} else if (ClassName.class.equals(expectedType)) {
Object value = annotationValue.getValue();
return (T) ClassName.get(value.toString());
} else if (FacesId.class.equals(expectedType)) {
String value = (String) annotationValue.getValue();
return (T) FacesId.parseId(value);
} else if (AnnotationMirror.class.isAssignableFrom(expectedType)) {
AnnotationMirror value = (AnnotationMirror) annotationValue.getValue();
return (T) value;
} else {
return (T) annotationValue.getValue();
}
}
@SuppressWarnings("unchecked")
@Override
public Iterable getAnnotationValues(AnnotationMirror annotation, String propertyName, Class expectedType) {
Map.Entry extends ExecutableElement, ? extends AnnotationValue> attributeEntry = findAnnotationProperty(annotation,
propertyName);
List extends AnnotationValue> annotationValues = (List extends AnnotationValue>) attributeEntry.getValue()
.getValue();
List values = Lists.newArrayList();
for (AnnotationValue annotationValue : annotationValues) {
values.add(convertAnnotationValue(expectedType, annotationValue));
}
return values;
}
private Entry extends ExecutableElement, ? extends AnnotationValue> findAnnotationProperty(AnnotationMirror annotation,
final String propertyName) {
try {
return Iterables
.find(getAnnotationValuesMap(annotation).entrySet(), new AnnotationAttributePredicate(propertyName));
} catch (NoSuchElementException e) {
throw new CdkException("Attribute " + propertyName + " not found for annotation "
+ annotation.getAnnotationType().toString());
}
}
private Map extends ExecutableElement, ? extends AnnotationValue> getAnnotationValuesMap(AnnotationMirror annotation) {
return processingEnv.getElementUtils().getElementValuesWithDefaults(annotation);
}
/**
*
* Set model property to the corresponding annotation attribute, if annotation attribute set to non-default value.
*
*
* @param model Model object.
* @param annotation annotation to copy property from.
* @param modelProperty bean attribute name in the model and annotation.
*/
@Override
public void setModelProperty(Object model, AnnotationMirror annotation, String modelProperty) {
setModelProperty(model, annotation, modelProperty, modelProperty);
}
/**
*
* Set model property to the corresponding annotation attribute, if annotation attribute set to non-default value.
*
*
* @param model Model object.
* @param annotation annotation to copy property from.
* @param modelProperty bean attribute name in model.
* @param annotationAttribute annotation attribute name.
*/
@Override
public void setModelProperty(Object model, AnnotationMirror annotation, String modelProperty, String annotationAttribute) {
if (!isDefaultValue(annotation, annotationAttribute)) {
PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(model, modelProperty);
PropertyUtils.setPropertyValue(model, modelProperty,
getAnnotationValue(annotation, annotationAttribute, propertyDescriptor.getPropertyType()));
}
}
public Object getConstant(TypeElement componentElement, String name) {
List fieldsIn = ElementFilter.fieldsIn(this.processingEnv.getElementUtils().getAllMembers(
componentElement));
Object value = null;
for (VariableElement field : fieldsIn) {
Set modifiers = field.getModifiers();
if (modifiers.contains(Modifier.FINAL) && modifiers.contains(Modifier.STATIC)
&& field.getSimpleName().toString().equals(name)) {
value = field.getConstantValue();
}
}
return value;
}
public void visitSupertypes(TypeElement type, SuperTypeVisitor visitor) {
visitSupertypes(type.asType(), visitor);
}
private void visitSupertypes(TypeMirror type, SuperTypeVisitor visitor) {
List extends TypeMirror> supertypes = this.processingEnv.getTypeUtils().directSupertypes(type);
for (TypeMirror typeMirror : supertypes) {
visitSupertypes(typeMirror, visitor);
}
visitor.visit(type);
}
private TypeElement asTypeElement(ClassName type) {
return processingEnv.getElementUtils().getTypeElement(type.toString());
}
public boolean isClassExists(ClassName type) {
return null != asTypeElement(type);
}
@Override
public TypeElement asTypeElement(TypeMirror mirror) {
if (TypeKind.DECLARED.equals(mirror.getKind())) {
return (TypeElement) processingEnv.getTypeUtils().asElement(mirror);
} else {
return null;
}
}
/**
*
*
*
* @author [email protected]
*
*/
private static final class AnnotationAttributePredicate implements
Predicate> {
private final String propertyName;
private AnnotationAttributePredicate(String propertyName) {
this.propertyName = propertyName;
}
@Override
public boolean apply(Entry extends ExecutableElement, ? extends AnnotationValue> input) {
return this.propertyName.equals(input.getKey().getSimpleName().toString());
}
}
/**
*
*
*
* @author [email protected]
*
*/
protected final class AptBeanProperty implements BeanProperty {
private ExecutableElement getter;
private ExecutableElement setter;
private final String name;
private ClassName type;
/**
*
*
*
* @param name
*/
public AptBeanProperty(String name) {
this.name = name;
}
void setAccessMethod(ExecutableElement method, boolean setter) {
if (setter) {
this.setter = method;
} else {
this.getter = method;
}
}
/**
*
*
*
* @return the name
*/
public String getName() {
return name;
}
/**
*
* Get JavaDoc comment of appropriate bean property element.
*
*
* @return
*/
public String getDocComment() {
String comment = getMethodComment(getter);
if (null == comment) {
comment = getMethodComment(setter);
}
return comment;
}
private String getMethodComment(ExecutableElement method) {
if (null != method) {
return processingEnv.getElementUtils().getDocComment(method);
} else {
return null;
}
}
public ClassName getType() {
return type;
}
/**
*
*
*
* @return the exists
*/
public boolean isExists() {
return !(isAbstract(getter) || isAbstract(setter));
}
private boolean isAbstract(ExecutableElement method) {
return null != method && method.getModifiers().contains(Modifier.ABSTRACT);
}
public AnnotationMirror getAnnotationMirror(Class extends Annotation> annotationType) {
if (isAnnotationPresent(getter, annotationType)) {
return AptSourceUtils.this.getAnnotationMirror(getter, annotationType);
} else if (isAnnotationPresent(setter, annotationType)) {
return AptSourceUtils.this.getAnnotationMirror(setter, annotationType);
}
return null;
}
@Override
public boolean isAnnotationPresent(Class extends Annotation> annotationType) {
return isAnnotationPresent(getter, annotationType) || isAnnotationPresent(setter, annotationType);
}
@Override
public T getAnnotation(Class annotationType) {
if (isAnnotationPresent(getter, annotationType)) {
return getter.getAnnotation(annotationType);
} else if (isAnnotationPresent(setter, annotationType)) {
return setter.getAnnotation(annotationType);
}
return null;
}
private boolean isAnnotationPresent(ExecutableElement method, Class annotationType) {
return null != method && null != method.getAnnotation(annotationType);
}
@Override
public ACCESS_TYPE getAccessType() {
if (null != getter && null != setter) {
return ACCESS_TYPE.readWrite;
} else if (null == setter) {
return ACCESS_TYPE.readOnly;
}
return ACCESS_TYPE.writeOnly;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
AptBeanProperty other = (AptBeanProperty) obj;
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
return true;
}
@Override
public String toString() {
return name + "[" + getType() + "]";
}
}
}