/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* 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 com.asakusafw.operator;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.IOError;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import com.asakusafw.operator.description.ClassDescription;
import com.asakusafw.operator.description.EnumConstantDescription;
import com.asakusafw.operator.model.OperatorElement;
/**
* Callback from {@link DelegateProcessor}.
*/
public abstract class Callback {
private RuntimeException runtimeException;
private Error error;
/**
* The current compiling environment.
*/
public CompileEnvironment env;
/**
* The type utilities.
*/
public Types types;
/**
* The element utilities.
*/
public Elements elements;
/**
* The current rounding environment.
*/
public RoundEnvironment round;
/**
* Target annotations.
*/
public Set<? extends TypeElement> annotatios;
private final boolean oneshot;
private int count = 0;
/**
* Creates a new instance.
*/
public Callback() {
this(true);
}
/**
* Creates a new instance.
* @param oneshot one shot execution
*/
public Callback(boolean oneshot) {
this.oneshot = oneshot;
}
/**
* Runs {@link #test()} method.
* @param pEnv processing environment
* @param rEnv rounding environment
* @param annotations target annotations
*/
public void run(ProcessingEnvironment pEnv, RoundEnvironment rEnv, Set<? extends TypeElement> annotations) {
this.env = createCompileEnvironment(pEnv);
this.round = rEnv;
this.types = pEnv.getTypeUtils();
this.elements = pEnv.getElementUtils();
this.annotatios = annotations;
try {
if (count++ == 0 || oneshot == false) {
test();
}
} catch (IOException e) {
this.error = new IOError(e);
} catch (RuntimeException e) {
this.runtimeException = e;
} catch (Error e) {
this.error = e;
}
}
/**
* Creates a compile environment for this processing (for testing).
* @param processingEnv current processing environment
* @return created environment
*/
protected CompileEnvironment createCompileEnvironment(ProcessingEnvironment processingEnv) {
return CompileEnvironment.newInstance(
processingEnv,
CompileEnvironment.Support.DATA_MODEL_REPOSITORY,
CompileEnvironment.Support.OPERATOR_DRIVER);
}
/**
* Throws exceptions/errors which are thrown in {@link #test()}.
*/
public void rethrow() {
if (runtimeException != null) {
throw runtimeException;
} else if (error != null) {
throw error;
}
}
/**
* Performs the test.
* @throws IOException if compilation was failed
*/
protected abstract void test() throws IOException;
/**
* Returns the declared type.
* @param aClass target raw type
* @param arguments type arguments
* @return the declared type
*/
public DeclaredType getType(Class<?> aClass, TypeMirror...arguments) {
String typeName = aClass.getName();
return getType(typeName, arguments);
}
/**
* Returns the declared type.
* @param typeName target type name
* @param arguments type arguments
* @return the declared type
*/
public DeclaredType getType(String typeName, TypeMirror... arguments) {
TypeElement type = elements.getTypeElement(typeName);
assertThat(typeName, type, not(nullValue()));
if (arguments.length == 0) {
return (DeclaredType) types.erasure(type.asType());
} else {
return types.getDeclaredType(type, arguments);
}
}
/**
* Returns the type parameter.
* @param typeName target type name
* @param name parameter name
* @return the type variable
*/
public TypeVariable getTypeVariable(String typeName, String name) {
return getTypeVariable(elements.getTypeElement(typeName), name);
}
/**
* Returns the type parameter.
* @param element type parameter owner
* @param name parameter name
* @return the type variable
*/
public TypeVariable getTypeVariable(TypeElement element, String name) {
return getTypeParameters(name, element.getTypeParameters());
}
/**
* Returns the type parameter.
* @param element type parameter owner
* @param name parameter name
* @return the type variable
*/
public TypeVariable getTypeVariable(ExecutableElement element, String name) {
return getTypeParameters(name, element.getTypeParameters());
}
private TypeVariable getTypeParameters(String name, List<? extends TypeParameterElement> typeParameters) {
for (TypeParameterElement param : typeParameters) {
if (param.getSimpleName().contentEquals(name)) {
return (TypeVariable) param.asType();
}
}
throw new AssertionError(name);
}
/**
* Returns the matcher that compares to the specified type.
* @param aClass target raw type
* @param arguments type arguments
* @return the matcher
*/
public Matcher<TypeMirror> sameType(Class<?> aClass, TypeMirror...arguments) {
return sameType(getType(aClass, arguments));
}
/**
* Returns the matcher that compares to the specified type.
* @param typeName target type name
* @param arguments type arguments
* @return the matcher
*/
public Matcher<TypeMirror> sameType(String typeName, TypeMirror...arguments) {
return sameType(getType(typeName, arguments));
}
/**
* Returns the matcher that compares to the specified type.
* @param type target type
* @return the matcher
*/
public Matcher<TypeMirror> sameType(TypeMirror type) {
return new BaseMatcher<TypeMirror>() {
@Override
public boolean matches(Object arg) {
return env.getProcessingEnvironment().getTypeUtils().isSameType((TypeMirror) arg, type);
}
@Override
public void describeTo(Description desc) {
desc.appendValue(type);
}
};
}
/**
* Returns the matcher that compares to the specified type kind.
* @param kind target type kind
* @return the matcher
*/
public Matcher<TypeMirror> kindOf(TypeKind kind) {
return new BaseMatcher<TypeMirror>() {
@Override
public boolean matches(Object arg) {
return ((TypeMirror) arg).getKind() == kind;
}
@Override
public void describeTo(Description desc) {
desc.appendValue(kind);
}
};
}
/**
* Returns a matcher that tests whether or not the operator has the specified attribute.
* @param attributeType the attribute type
* @return the matcher
*/
public Matcher<OperatorElement> hasAttribute(Class<? extends Enum<?>> attributeType) {
ClassDescription type = ClassDescription.of(attributeType);
return new BaseMatcher<OperatorElement>() {
@Override
public boolean matches(Object item) {
return ((OperatorElement) item).getDescription().getAttributes().stream()
.filter(EnumConstantDescription.class::isInstance)
.map(EnumConstantDescription.class::cast)
.map(EnumConstantDescription::getDeclaringClass)
.anyMatch(Predicate.isEqual(type));
}
@Override
public void describeTo(Description description) {
description.appendText("has attribute ").appendValue(type.getClassName());
}
};
}
/**
* Returns a matcher that tests whether or not the operator has the specified attribute.
* @param attribute the attribute
* @return the matcher
*/
public Matcher<OperatorElement> hasAttribute(Enum<?> attribute) {
EnumConstantDescription desc = EnumConstantDescription.of(attribute);
return new BaseMatcher<OperatorElement>() {
@Override
public boolean matches(Object item) {
return ((OperatorElement) item).getDescription().getAttributes().contains(desc);
}
@Override
public void describeTo(Description description) {
description.appendText("has attribute ").appendValue(desc);
}
};
}
}