/*
Copyright 2015 Immutables Authors and Contributors
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.immutables.value.processor.meta;
import com.google.common.collect.Lists;
import java.lang.annotation.Annotation;
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.ElementKind;
import javax.lang.model.element.ElementVisitor;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVisitor;
/**
* Some annotation processors have {@code javax.lang.model} being implemented using relatively
* expensive conversions from internal model. When some properties are being queried again and
* again, annotation mirrors or enclosed elements are worth to store. Implementations wrappers cache
* some properties eagerly and some lazily.
*/
public final class CachingElements {
private CachingElements() {}
public static Element asCaching(Element element) {
if (element instanceof Caching) {
return element;
}
return new CachingElement(element);
}
public static TypeElement asCaching(TypeElement element) {
if (element instanceof Caching) {
return element;
}
return new CachingTypeElement(element);
}
@SuppressWarnings("unchecked")
public static <E extends Element> E getDelegate(E element) {
if (element instanceof Caching) {
return (E) ((Caching) element).delegate();
}
return element;
}
@SuppressWarnings("unchecked")
public static <E extends AnnotationMirror> E getDelegate(E element) {
if (element instanceof Caching) {
return (E) ((Caching) element).delegate();
}
return element;
}
public static PackageElement asCaching(PackageElement element) {
if (element instanceof Caching) {
return element;
}
return new CachingPackageElement(element);
}
public static ExecutableElement asCaching(ExecutableElement element) {
if (element instanceof Caching) {
return element;
}
return new CachingExecutableElement(element);
}
public static AnnotationMirror asCaching(AnnotationMirror mirror) {
if (mirror instanceof Caching) {
return mirror;
}
return new CachingAnnotationMirror(mirror);
}
public static boolean equals(Element left, Element right) {
return getDelegate(left).equals(getDelegate(right));
}
private static List<AnnotationMirror> asCaching(List<? extends AnnotationMirror> mirrors) {
List<AnnotationMirror> cachingMirrors = Lists.newArrayListWithCapacity(mirrors.size());
for (AnnotationMirror mirror : mirrors) {
cachingMirrors.add(new CachingAnnotationMirror(mirror));
}
return cachingMirrors;
}
private interface Caching {
Object delegate();
}
private static class CachingExecutableElement extends CachingElement implements ExecutableElement {
private final ExecutableElement delegate;
private final TypeMirror returnType;
// not volatile, it's ok to have some additional instances at race condition
private List<? extends VariableElement> parameters;
// not volatile, it's ok to have some additional instances at race condition
private List<? extends TypeParameterElement> typeParameters;
// not volatile, it's ok to have some additional instances at race condition
private AnnotationValue defaultValue;
CachingExecutableElement(ExecutableElement delegate) {
super(delegate);
this.delegate = delegate;
this.returnType = delegate.getReturnType();
}
@Override
public List<? extends VariableElement> getParameters() {
List<? extends VariableElement> ps = parameters;
if (ps == null) {
ps = delegate.getParameters();
parameters = ps;
}
return ps;
}
@Override
public List<? extends TypeParameterElement> getTypeParameters() {
List<? extends TypeParameterElement> tps = typeParameters;
if (tps == null) {
tps = delegate.getTypeParameters();
typeParameters = tps;
}
return tps;
}
@Override
public TypeMirror getReturnType() {
return returnType;
}
@Override
public List<? extends TypeMirror> getThrownTypes() {
return delegate.getThrownTypes();
}
@Override
public AnnotationValue getDefaultValue() {
AnnotationValue dv = defaultValue;
if (dv == null) {
dv = delegate.getDefaultValue();
defaultValue = dv;
}
return dv;
}
@Override
public boolean isVarArgs() {
return delegate.isVarArgs();
}
@SuppressWarnings("unused")
public boolean isDefault() {
throw throwInteroperabilityStub();
}
@SuppressWarnings("unused")
public TypeMirror getReceiverType() {
throw throwInteroperabilityStub();
}
}
private static class CachingPackageElement extends CachingElement implements PackageElement {
private final PackageElement delegate;
private final Name qualifiedName;
CachingPackageElement(PackageElement delegate) {
super(delegate);
this.delegate = delegate;
this.qualifiedName = delegate.getQualifiedName();
}
@Override
public Name getQualifiedName() {
return qualifiedName;
}
@Override
public boolean isUnnamed() {
return delegate.isUnnamed();
}
}
private static class CachingTypeElement extends CachingElement implements TypeElement {
private final TypeElement delegate;
private final Name qualifiedName;
// not volatile, it's ok to have some additional instances at race condition
private List<? extends TypeParameterElement> typeParameters;
CachingTypeElement(TypeElement delegate) {
super(delegate);
this.delegate = delegate;
this.qualifiedName = delegate.getQualifiedName();
}
@Override
public NestingKind getNestingKind() {
return delegate.getNestingKind();
}
@Override
public Name getQualifiedName() {
return qualifiedName;
}
@Override
public TypeMirror getSuperclass() {
return delegate.getSuperclass();
}
@Override
public List<? extends TypeMirror> getInterfaces() {
return delegate.getInterfaces();
}
@Override
public List<? extends TypeParameterElement> getTypeParameters() {
List<? extends TypeParameterElement> tps = typeParameters;
if (tps == null) {
tps = delegate.getTypeParameters();
typeParameters = tps;
}
return tps;
}
}
private static class CachingElement implements Element, Caching {
private final Element delegate;
private final ElementKind kind;
private final Name simpleName;
private final Set<Modifier> modifiers;
// not volatile, it's ok to have some additional instances at race condition
private Element enclosingElement;
// not volatile, it's ok to have some additional instances at race condition
private List<? extends Element> enclosedElements;
// not volatile, it's ok to have some additional instances at race condition
private List<? extends AnnotationMirror> annotationMirrors;
CachingElement(Element delegate) {
this.delegate = delegate;
this.kind = delegate.getKind();
this.simpleName = delegate.getSimpleName();
this.modifiers = delegate.getModifiers();
}
@Override
public Element delegate() {
return delegate;
}
@Override
public List<? extends AnnotationMirror> getAnnotationMirrors() {
List<? extends AnnotationMirror> ms = annotationMirrors;
if (ms == null) {
ms = asCaching(delegate.getAnnotationMirrors());
annotationMirrors = ms;
}
return ms;
}
@Override
public Set<Modifier> getModifiers() {
return modifiers;
}
@Override
public Name getSimpleName() {
return simpleName;
}
@Override
public Element getEnclosingElement() {
Element e = enclosingElement;
if (e == null) {
e = delegate.getEnclosingElement();
enclosingElement = e;
}
return e;
}
@Override
public List<? extends Element> getEnclosedElements() {
List<? extends Element> es = enclosedElements;
if (es == null) {
es = delegate.getEnclosedElements();
enclosedElements = es;
}
return es;
}
@Override
public TypeMirror asType() {
return delegate.asType();
}
@Override
public ElementKind getKind() {
return kind;
}
@Override
public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
return delegate.getAnnotation(annotationType);
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public <R, P> R accept(ElementVisitor<R, P> v, P p) {
return delegate.accept(v, p);
}
@SuppressWarnings("unused")
public <A extends Annotation> A[] getAnnotationsByType(Class<A> type) {
throw throwInteroperabilityStub();
}
@Override
public String toString() {
return delegate.toString();
}
}
private static class CachingAnnotationMirror implements AnnotationMirror, Caching {
private final AnnotationMirror delegate;
// not volatile, it's ok to have some additional instances at race condition
private Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues;
private final DeclaredType annotationType;
CachingAnnotationMirror(AnnotationMirror delegate) {
this.delegate = delegate;
this.annotationType = new CachingDeclaredType(delegate.getAnnotationType());
}
@Override
public DeclaredType getAnnotationType() {
return annotationType;
}
@Override
public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() {
Map<? extends ExecutableElement, ? extends AnnotationValue> vs = elementValues;
if (vs == null) {
vs = delegate.getElementValues();
elementValues = vs;
}
return vs;
}
@Override
public AnnotationMirror delegate() {
return delegate;
}
@Override
public String toString() {
return delegate.toString();
}
}
private static class CachingDeclaredType implements DeclaredType, Caching {
private final DeclaredType delegate;
private final TypeKind kind;
private final Element element;
CachingDeclaredType(DeclaredType delegate) {
this.delegate = delegate;
this.kind = delegate.getKind();
this.element = new CachingTypeElement((TypeElement) delegate.asElement());
}
@Override
public DeclaredType delegate() {
return delegate;
}
@Override
public Element asElement() {
return element;
}
@Override
public TypeKind getKind() {
return kind;
}
@Override
public <R, P> R accept(TypeVisitor<R, P> v, P p) {
return delegate.accept(v, p);
}
@Override
public TypeMirror getEnclosingType() {
return delegate.getEnclosingType();
}
@Override
public List<? extends TypeMirror> getTypeArguments() {
return delegate.getTypeArguments();
}
@SuppressWarnings("unused")
public <A extends Annotation> A getAnnotation(Class<A> type) {
throw throwInteroperabilityStub();
}
@SuppressWarnings("unused")
public <A extends Annotation> A[] getAnnotationsByType(Class<A> type) {
throw throwInteroperabilityStub();
}
@SuppressWarnings("unused")
public List<? extends AnnotationMirror> getAnnotationMirrors() {
throw throwInteroperabilityStub();
}
@Override
public String toString() {
return delegate.toString();
}
}
private static RuntimeException throwInteroperabilityStub() {
throw new UnsupportedOperationException("due Java7/Java8 interoperability");
}
}