javapoet: MethodSpec.Builder.returns() generate error - java

I would like to generate a class from an interface.
My interface is:
#Annotation
interface Object {
String getName();
}
I would expect this:
public final class ObjectGenerated implements Object {
public String getName() {
return "my code";
}
}
The class processor is:
#AutoService(Processor.class)
#SupportedSourceVersion(SourceVersion.RELEASE_8)
public class Processor extends AbstractProcessor {
#Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
for (Element element : roundEnvironment.getElementsAnnotatedWith(Annotation.class)) {
if (element.getKind() != ElementKind.INTERFACE) {
messager.printMessage(Diagnostic.Kind.ERROR, "Can be applied to class.");
return false;
}
generateClass(element);
}
return true;
}
private void generateClass(Element element) {
try {
String className = element.getSimpleName().toString();
String pack = processingEnv.getElementUtils().getPackageOf(element).toString();
String fileName = className + "Generated";
TypeSpec.Builder classBuilder = TypeSpec
.classBuilder(fileName)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addSuperinterface(ClassName.get(element.asType()));
for (Element elementMethod : element.getEnclosedElements()) {
MethodSpec intentMethod = MethodSpec
.methodBuilder(elementMethod.getSimpleName().toString())
.addModifiers(Modifier.PUBLIC)
.addStatement("return $S", "my code")
.returns(ClassName.get(elementMethod.asType())) //this generate error
.build();
classBuilder.addMethod(intentMethod);
}
JavaFile.builder(pack, classBuilder.build()).build().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public Set<String> getSupportedAnnotationTypes() {
return ImmutableSet.of(Annotation.class.getCanonicalName());
}
#Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
The error is:
java.lang.IllegalArgumentException: Unexpected type mirror: ()java.lang.String
It's possible that I did something wrong.
How can I get the "Type" from the interface in my case is String and use it in the MethodSpec.returns ?
Thanks.

Cast your Element to ExecutableElement and use its getReturnType()
In particular:
for (Element elementMethod : element.getEnclosedElements()) {
if (elementMethod.getKind() != ElementKind.METHOD) {
//skip non-method elements like final fields
continue;
}
ExecutableElement method = (ExecutableElement)elementMethod; //cast
MethodSpec intentMethod = MethodSpec
.methodBuilder(method.getSimpleName().toString())
.addModifiers(Modifier.PUBLIC)
.addStatement("return $S", "my code")
.returns(ClassName.get(method.getReturnType())) //should be ok now
.build();
classBuilder.addMethod(intentMethod);
}

Related

Java find specific class in groovy script

I have a java jar file contain interface named IOperator like this :
public interface IOperator {
String execute(Map<String, String> parameters);
boolean isSuccess();
}
now in groovy script (in single file):
public class ClassA {
}
public class ClassB {
}
public class classC implements IOperator {
#Override
String execute(Map<String, String> parameters) {
ClassA classA = new ClassA();
ClassB classB = new ClassB();
return null
}
#Override
boolean isSuccess() {
return false
}
}
Is there any way scan this groovy script to find that specific class implemented IOperator and invoke execute method ?
Note: I want do it in Java code.
Finally i found answer :
public class OperatorManager {
public static void run(File scriptFile) {
try {
GroovyScriptEngineImpl engine = (GroovyScriptEngineImpl) new GroovyScriptEngineFactory().getScriptEngine();
GroovyClassLoader classLoader = new GroovyClassLoader();
classLoader.parseClass(new GroovyCodeSource(scriptFile));
engine.setClassLoader(classLoader);
Class<?>[] loadedClasses = engine.getClassLoader().getLoadedClasses();
Class<?> operatorImplClass = Arrays.stream(loadedClasses)
.filter(IOperator.class::isAssignableFrom)
.findFirst().orElse(null);
if (operatorImplClass != null) {
String result = invokeMethod(operatorImplClass);
System.out.println(result);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static String invokeMethod(Class<?> item) {
try {
Object instance = item.getConstructor().newInstance();
Method execute = item.getDeclaredMethod("execute", Map.class);
return (String) execute.invoke(instance, new Object[]{null});
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

mapstruct wrapper type and generics

I am trying to map JsonNullable<List<ChildRequestTO> to Nullable<List<ChildRequestDO>> (see full code below) with mapstruct 1.4.2.Final and I am facing the following error: error: Nullable<List<ChildRequestDO>> does not have an accessible constructor. If I add a constructor for Nullable like
public Nullable(T value) {
this.value = value;
this.isPresent = true;
}
then I get the following error error: Unmapped target property: "value". Mapping from property "JsonNullable<List<ChildRequestTO>> products" to "Nullable<List<ChildRequestDO>> products".
How do I map complex wrapped types in a generic way?
The following mapping code (part of ChildRequestMapper class and applied in ObjectRequestMapper) solves the problem but I want to solve it in a more generic way:
#Named("mappingHelper")
default Nullable<List<ChildRequestDO>> customMapToDOs(JsonNullable<List<ChildRequestTO>> input) {
if (JsonNullable.undefined().equals(input)) {
return Nullable.undefined();
}
if (input.get() == null) {
return Nullable.of(null);
}
var output= input.get()
.stream()
.map(this::mapToDO)
.collect(Collectors.toList());
return Nullable.of(output);
}
Changing the NullableMapper to the code below does not work/compile because I do not know how to tell mapstruct to look for the appropriate mapper to map from T to X.
public static <T, X> Nullable<X> jsonNullableToNullable(JsonNullable<T> jsonNullable) {
if (jsonNullable.isPresent()) {
return Nullable.of(jsonNullable.get());
}
return Nullable.undefined();
}
Full code:
#Mapper(
unmappedTargetPolicy = ReportingPolicy.ERROR,
uses = {ChildRequestMapper.class, NullableMapper.class}
)
public interface ObjectRequestMapper {
#Mapping(target = "slots", source = "slots", qualifiedByName = "mapToSlotDOs")
ModifyObjectRequestDO mapToDO(ModifyObjectRequestTO input);
}
#Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface ChildRequestMapper {
ChildRequestDO mapToDO(ChildRequestTO input);
}
public class NullableMapper {
public static <T> Nullable<T> jsonNullableToNullable(JsonNullable<T> jsonNullable) {
if (jsonNullable.isPresent()) {
return Nullable.of(jsonNullable.get());
}
return Nullable.undefined();
}
}
public class ModifyObjectRequestTO {
private JsonNullable<String> name = JsonNullable.undefined();
private JsonNullable<List<ChildRequestTO>> children = JsonNullable.undefined();
}
public class ModifyObjectRequestDO {
private Nullable<String> name = Nullable.undefined();
private Nullable<List<ChildRequestDO>> children = Nullable.undefined();
}
public class Nullable<T> {
private static final Nullable<?> UNDEFINED = new Nullable<>(null, false);
private final T value;
private final boolean isPresent;
private Nullable(T value, boolean isPresent) {
this.value = value;
this.isPresent = isPresent;
}
public static <T> Nullable<T> undefined() {
#SuppressWarnings("unchecked")
Nullable<T> t = (Nullable<T>) UNDEFINED;
return t;
}
public static <T> Nullable<T> of(T value) {
return new Nullable<T>(value, true);
}
public T get() {
if (!isPresent) {
throw new NoSuchElementException("Value is undefined");
}
return value;
}
public boolean isPresent() {
return isPresent;
}
}

Java. Factory creation should return Generic class

Could someone please help.
I would like to create a Factory, which would return specific class.
Having problem on the line - filterFactory.getFilter(myColumn.getType()).setMin(5);
There is an error: Cannot resolve method 'setMin' in 'Object'
public enum Columns {
Name(ColumnType.String),
Age(ColumnType.Numeric);
private final ColumnType type;
Columns(ColumnType type) {
this.type = type;
}
public ColumnType getType() {
return type;
}
}
public enum ColumnType {
String,
Numeric
}
public class NumericFilter extends ColumnFilter {
public void setMin(int min) {
System.out.println("min is set" + min);
}
}
public class StringFilter extends ColumnFilter {
public void setFilter(String filter) {
System.out.println("filter is set to:" + filter);
}
}
public class ColumnFilterFactory {
public <T> T getFilter(ColumnType type) {
if (type == null) {
return null;
}
if (type == ColumnType.String) {
return (T) new StringFilter();
} else if (type == ColumnType.Numeric) {
return (T) new NumericFilter();
}
return null;
}
}
public class BasicTest {
public static void main(String[] args) {
Columns myColumn = Columns.Age;
ColumnFilterFactory filterFactory = new ColumnFilterFactory();
filterFactory.getFilter(myColumn.getType()).setMin(5);
}
}
There's no way the compiler can know what type the factory is going to return, so you need to give it a little help. For example by using an intermediate variable:
NumericFilter nf = filterFactory.getFilter(myColumn.getType());
nf.setMin(5);

Getting information from Annotation processor at runtime

I have a working annotation processor that gathers information of the annotated classes. Everything is there during compilation. But I would like to have access to those results during runtime.
#SupportedSourceVersion(SourceVersion.RELEASE_8)
#AutoService(Processor.class)
public class TestProcessor extends AbstractProcessor {
private final static List<TestInfo> tests = new ArrayList<>();
#Override
public Set getSupportedAnnotationTypes() {
return new LinkedHashSet() {
{
add(Annotation.class.getCanonicalName());
}
};
}
#Override
public boolean process(final Set<? extends TypeElement> annotations,
final RoundEnvironment env) {
System.out.println("Processing!");
if (!env.processingOver()) {
Set<? extends Element> rootE = env.getRootElements();
for (Element e : rootE) {
if (e.getKind() == ElementKind.CLASS) {
TestInfo t = new TestInfo(e.asType().toString());
for (Element se : e.getEnclosedElements()) {
if (se.getKind() == ElementKind.METHOD) {
t.addMethod(se.getSimpleName().toString());
}
}
getTests().add(t);
}
}
getTests().forEach(ti -> {
System.out.println(ti);
});
}
return false;
}
public static TypeElement findEnclosingTypeElement(Element e) {
while (e != null && !(e instanceof TypeElement)) {
e = e.getEnclosingElement();
}
return TypeElement.class.cast(e);
}
/**
* #return the tests
*/
public static List<TestInfo> getTests() {
return tests;
}
}
Is there a way to retrieve the results at runtime? TestProcessor.getTests returns an empty list.
Here's the TestInfo class fyi:
public class TestInfo {
private final String name;
private final List<String> methods = new ArrayList<>();
public TestInfo(String name) {
this.name = name;
}
public void addMethod(String m) {
getMethods().add(m);
}
/**
* #return the name
*/
public String getName() {
return name;
}
/**
* #return the methods
*/
public List<String> getMethods() {
return methods;
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(name).append(methods.toString());
return sb.toString();
}
}
Update: The annotation is marked with retention runtime.
The annotation-processing is in compile time. So you can't get the information in the runtime.
A direct way is to write the information as a resource file in compile time and read it at runtime.
Here is my example:
The annotation:
#Retention(SOURCE)
#Target(TYPE)
public #interface Anno {
}
The processor:
#Override
public boolean processActual(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
return false;
}
try {
write(roundEnv);
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
private void write(RoundEnvironment roundEnv) throws IOException, UnsupportedEncodingException {
Filer filer = processingEnv.getFiler();
FileObject resource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", "TestInfo");
OutputStream output = resource.openOutputStream();
PrintStream writer = new PrintStream(output, false, "UTF-8");
roundEnv.getElementsAnnotatedWith(Anno.class)
.stream()
.filter(e -> e.getKind() == ElementKind.CLASS)
.map(e -> e.asType().toString())
.forEach(writer::println);
writer.flush();
}
And the user code:
#Anno
public class Q48177784 {
public static final List<Class<?>> CLASSES;
static {
try {
URL resource = Q48177784.class.getClassLoader().getResource("TestInfo");
CLASSES = Files.readAllLines(Paths.get(resource.toURI()))
.stream()
.map(s -> {
try {
return Class.forName(s);
} catch (ClassNotFoundException e) {
throw new Error(e);
}
})
.collect(Collectors.toList());
} catch (Exception e) {
throw new Error(e);
}
}
public static void main(String[] args) {
System.out.println(CLASSES);
}
}
After build with processor, run the main method:
[class xdean.stackoverflow.Q48177784]
For your case, the only thing you should do is serialize/deserialize your TestInfo
Check out #RetentionPolicy. I think you want to set it to RUNTIME.

ParameterizedTest with a name in Eclipse Testrunner

When you run a JUnit 4 ParameterizedTest with the Eclipse TestRunner, the graphical representation is rather dumb: for each test you have a node called [0], [1], etc.
Is it possible give the tests [0], [1], etc. explicit names? Implementing a toString method for the tests does not seem to help.
(This is a follow-up question to JUnit test with dynamic number of tests.)
I think there's nothing built in in jUnit 4 to do this.
I've implemented a solution. I've built my own Parameterized class based on the existing one:
public class MyParameterized extends TestClassRunner {
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public static #interface Parameters {
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public static #interface Name {
}
public static Collection<Object[]> eachOne(Object... params) {
List<Object[]> results = new ArrayList<Object[]>();
for (Object param : params)
results.add(new Object[] { param });
return results;
}
// TODO: single-class this extension
private static class TestClassRunnerForParameters extends TestClassMethodsRunner {
private final Object[] fParameters;
private final Class<?> fTestClass;
private Object instance;
private final int fParameterSetNumber;
private final Constructor<?> fConstructor;
private TestClassRunnerForParameters(Class<?> klass, Object[] parameters, int i) throws Exception {
super(klass);
fTestClass = klass;
fParameters = parameters;
fParameterSetNumber = i;
fConstructor = getOnlyConstructor();
instance = fConstructor.newInstance(fParameters);
}
#Override
protected Object createTest() throws Exception {
return instance;
}
#Override
protected String getName() {
String name = null;
try {
Method m = getNameMethod();
if (m != null)
name = (String) m.invoke(instance);
} catch (Exception e) {
}
return String.format("[%s]", (name == null ? fParameterSetNumber : name));
}
#Override
protected String testName(final Method method) {
String name = null;
try {
Method m = getNameMethod();
if (m != null)
name = (String) m.invoke(instance);
} catch (Exception e) {
}
return String.format("%s[%s]", method.getName(), (name == null ? fParameterSetNumber : name));
}
private Constructor<?> getOnlyConstructor() {
Constructor<?>[] constructors = getTestClass().getConstructors();
assertEquals(1, constructors.length);
return constructors[0];
}
private Method getNameMethod() throws Exception {
for (Method each : fTestClass.getMethods()) {
if (Modifier.isPublic((each.getModifiers()))) {
Annotation[] annotations = each.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType() == Name.class) {
if (each.getReturnType().equals(String.class))
return each;
else
throw new Exception("Name annotated method doesn't return an object of type String.");
}
}
}
}
return null;
}
}
// TODO: I think this now eagerly reads parameters, which was never the
// point.
public static class RunAllParameterMethods extends CompositeRunner {
private final Class<?> fKlass;
public RunAllParameterMethods(Class<?> klass) throws Exception {
super(klass.getName());
fKlass = klass;
int i = 0;
for (final Object each : getParametersList()) {
if (each instanceof Object[])
super.add(new TestClassRunnerForParameters(klass, (Object[]) each, i++));
else
throw new Exception(String.format("%s.%s() must return a Collection of arrays.", fKlass.getName(), getParametersMethod().getName()));
}
}
private Collection<?> getParametersList() throws IllegalAccessException, InvocationTargetException, Exception {
return (Collection<?>) getParametersMethod().invoke(null);
}
private Method getParametersMethod() throws Exception {
for (Method each : fKlass.getMethods()) {
if (Modifier.isStatic(each.getModifiers())) {
Annotation[] annotations = each.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType() == Parameters.class)
return each;
}
}
}
throw new Exception("No public static parameters method on class " + getName());
}
}
public MyParameterized(final Class<?> klass) throws Exception {
super(klass, new RunAllParameterMethods(klass));
}
#Override
protected void validate(MethodValidator methodValidator) {
methodValidator.validateStaticMethods();
methodValidator.validateInstanceMethods();
}
}
To be used like:
#RunWith(MyParameterized.class)
public class ParameterizedTest {
private File file;
public ParameterizedTest(File file) {
this.file = file;
}
#Test
public void test1() throws Exception {}
#Test
public void test2() throws Exception {}
#Name
public String getName() {
return "coolFile:" + file.getName();
}
#Parameters
public static Collection<Object[]> data() {
// load the files as you want
Object[] fileArg1 = new Object[] { new File("path1") };
Object[] fileArg2 = new Object[] { new File("path2") };
Collection<Object[]> data = new ArrayList<Object[]>();
data.add(fileArg1);
data.add(fileArg2);
return data;
}
}
This implies that I instantiate the test class earlier. I hope this won't cause any errors ... I guess I should test the tests :)
JUnit4 now allows specifying a name attribute to the Parameterized annotation, such that you can specify a naming pattern from the index and toString methods of the arguments. E.g.:
#Parameters(name = "{index}: fib({0})={1}")
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
{ 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
}
A code-less though not that comfortable solution is to pass enough context information to identify the test in assert messages. You will still see just testXY[0] failed but the detailed message tells you which one was that.
assertEquals("Not the expected decision for the senator " + this.currentSenatorName + " and the law " + this.votedLaw,
expectedVote, actualVote);
If you use JUnitParams library (as I have described here), the parameterized tests will have their stringified parameters as their own default test names.
Moreover, you can see in their samples, that JUnitParams also allows you to have a custom test name by using #TestCaseName:
#Test
#Parameters({ "1,1", "2,2", "3,6" })
#TestCaseName("factorial({0}) = {1}")
public void custom_names_for_test_case(int argument, int result) { }
#Test
#Parameters({ "value1, value2", "value3, value4" })
#TestCaseName("[{index}] {method}: {params}")
public void predefined_macro_for_test_case_name(String param1, String param2) { }
There's no hint that this feature is or will be implemented. I would request this feature because it's nice to have.

Categories

Resources