net.karneim.pojobuilder.analysis.DirectivesFactory Maven / Gradle / Ivy
Show all versions of pojobuilder Show documentation
package net.karneim.pojobuilder.analysis;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import net.karneim.pojobuilder.GeneratePojoBuilder;
public class DirectivesFactory {
private final Elements elements;
private final Types types;
private final JavaModelAnalyzerUtil javaModelAnalyzerUtil;
public DirectivesFactory(Elements elements, Types types,
JavaModelAnalyzerUtil javaModelAnalyzerUtil) {
this.elements = elements;
this.types = types;
this.javaModelAnalyzerUtil = javaModelAnalyzerUtil;
}
/**
* Scans the annotation hierarchy of all annotations on the given (annotated) element for the
* presence of a {@link GeneratePojoBuilder} annotation and returns a {@link Directives} object
* populated with the aggregation of the specified {@link GeneratePojoBuilder} element values.
*
* Please note that element values can be overridden top-down. This means, that element values
* that appear further up in the source code will be overridden by element values further down.
*
* @param annotatedEl the annotated element
* @param orginatingElements this out parameter will be filled with all elements that have (or
* could have) contributed to the {@link Directives} throughout the scan process.
* @return a {@link Directives} object populated with the effective annotation element values
*/
public Directives getDirectives(Element annotatedEl, Set orginatingElements) {
List hierarchy = findAnnotationHierarchy(annotatedEl);
List relevantHierarchy = filterByType(hierarchy, GeneratePojoBuilder.class);
Map valueMap = getValueMap(relevantHierarchy);
fillOrginatingElements(orginatingElements, hierarchy);
Directives result = new Directives(valueMap);
validate(annotatedEl, result);
return result;
}
private List filterByType(List hierarchy,
Class class1) {
TypeMirror generatePojoBuilderAnno =
elements.getTypeElement(class1.getName()).asType();
List result = new ArrayList();
for (HierarchyElement el : hierarchy) {
if (types.isSameType(el.annotation.getAnnotationType(), generatePojoBuilderAnno)) {
result.add(el);
}
}
return result;
}
private void fillOrginatingElements(Set result, List hierarchy) {
for (HierarchyElement hierarchyElement : hierarchy) {
result.add(javaModelAnalyzerUtil.getCompilationUnit(hierarchyElement.annotatedEl));
}
}
private void validate(Element annotatedEl, Directives directives) {
if (directives.isGenerateBuilderProperties()
&& Void.class.getName().equals(directives.getBuilderInterfaceName())) {
String message =
String
.format("Value for \"withBuilderInterface\" not specified. When setting \"withBuilderProperties\" to true, you must also specifiy a valid builder interface.");
throw new InvalidElementException(message, annotatedEl);
}
}
private static class HierarchyElement {
Element annotatedEl;
AnnotationMirror annotation;
public HierarchyElement(Element annotatedEl, AnnotationMirror annotation) {
super();
this.annotatedEl = annotatedEl;
this.annotation = annotation;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((annotatedEl == null) ? 0 : annotatedEl.hashCode());
result = prime * result + ((annotation == null) ? 0 : annotation.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
HierarchyElement other = (HierarchyElement) obj;
if (annotatedEl == null) {
if (other.annotatedEl != null)
return false;
} else if (!annotatedEl.equals(other.annotatedEl))
return false;
if (annotation == null) {
if (other.annotation != null)
return false;
} else if (!annotation.equals(other.annotation))
return false;
return true;
}
@Override
public String toString() {
return "HierarchyElement [annotatedEl=" + annotatedEl + ", annotation=" + annotation + "]";
}
}
private List findAnnotationHierarchy(Element annotatedEl) {
Set visitedElements = new HashSet();
List result = new LinkedList();
findAnnotationHierarchy(annotatedEl, result, visitedElements);
return result;
}
private void findAnnotationHierarchy(Element annotatedEl, List result,
Set visitedElements) {
if (!visitedElements.add(annotatedEl)) {
return;
}
List extends AnnotationMirror> annos = annotatedEl.getAnnotationMirrors();
for (AnnotationMirror anno : annos) {
Element el = anno.getAnnotationType().asElement();
findAnnotationHierarchy(el, result, visitedElements);
HierarchyElement hElem = new HierarchyElement(annotatedEl, anno);
result.add(hElem);
}
}
private boolean isGeneratePojoBuilderAnnotation(AnnotationMirror anno) {
TypeElement generatePojoBuilderTypeEl =
elements.getTypeElement(GeneratePojoBuilder.class.getName());
return types.isSameType(anno.getAnnotationType(), generatePojoBuilderTypeEl.asType());
}
private Map getValueMap(List hierarchy) {
Map result = null;
for (HierarchyElement el : hierarchy) {
if (isGeneratePojoBuilderAnnotation(el.annotation)) {
if (result == null) {
result = getValueMap(elements.getElementValuesWithDefaults(el.annotation));
} else {
result.putAll(getValueMap(el.annotation.getElementValues()));
}
}
}
return result;
}
private Map getValueMap(
Map extends ExecutableElement, ? extends AnnotationValue> values) {
Map result = new HashMap();
for (Map.Entry extends ExecutableElement, ? extends AnnotationValue> entry : values
.entrySet()) {
String name = entry.getKey().getSimpleName().toString();
Object value = entry.getValue().getValue();
if (value instanceof TypeMirror) {
TypeMirror tm = (TypeMirror) value;
if (tm.getKind() == TypeKind.DECLARED) {
DeclaredType declType = (DeclaredType) tm;
TypeElement elem = (TypeElement) declType.asElement();
result.put(name, elem.getQualifiedName().toString());
} else {
// TODO could this really happen?
result.put(name, String.valueOf(value));
}
} else {
result.put(name, value);
}
}
return result;
}
}