/** * 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.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import java.util.ServiceLoader; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; import org.junit.After; import com.asakusafw.operator.description.ClassDescription; import com.asakusafw.operator.flowpart.FlowPartAnnotationProcessor; import com.asakusafw.operator.method.OperatorAnnotationProcessor; import com.asakusafw.operator.util.Logger; import com.asakusafw.utils.java.jsr199.testing.SafeProcessor; import com.asakusafw.utils.java.jsr199.testing.VolatileCompiler; import com.asakusafw.utils.java.jsr199.testing.VolatileJavaFile; import com.asakusafw.utils.java.model.syntax.ModelFactory; import com.asakusafw.utils.java.model.util.Models; /** * Test helper for operator compilers. */ public class OperatorCompilerTestRoot { static final Logger LOG = Logger.get(OperatorCompilerTestRoot.class); final ModelFactory f = Models.getModelFactory(); private final VolatileCompiler compiler = new VolatileCompiler(); private final List<JavaFileObject> sources = new ArrayList<>(); /** * operator drivers. */ public final List<OperatorDriver> operatorDrivers = new ArrayList<>(); /** * data model mirrors. */ public final List<DataModelMirrorRepository> dataModelMirrors = new ArrayList<>(); /** * Dumps generated source files. */ protected boolean dump = true; /** * Disposes compiler. * @throws Exception if failed. */ @After public void tearDown() throws Exception { compiler.close(); } /** * Loads a class with the specified class name. * @param loader target class loader * @param name class name * @return the loaded class */ protected Class<?> load(ClassLoader loader, String name) { try { return loader.loadClass(name); } catch (Exception e) { throw new AssertionError(e); } } /** * Creates an object with the specified class name. * @param loader target class loader * @param aClass the target class * @return the generated instance */ protected Object create(ClassLoader loader, ClassDescription aClass) { try { Class<?> loaded = loader.loadClass(aClass.getBinaryName()); return loaded.newInstance(); } catch (Exception e) { throw new AssertionError(e); } } /** * Returns the specified method. * @param aClass target class * @param name method name * @param parameterTypes parameter types * @return the specified method, or {@code null} if it does not exist */ protected Method method(Class<?> aClass, String name, Class<?>... parameterTypes) { try { return aClass.getMethod(name, parameterTypes); } catch (Exception e) { return null; } } /** * Returns the specified field. * @param aClass target class * @param name field name * @return the specified field, or {@code null} if it does not exist */ protected Field field(Class<?> aClass, String name) { try { return aClass.getField(name); } catch (Exception e) { return null; } } /** * Returns the result of method invocation. * @param object target object * @param name method name * @param arguments method arguments * @return invocation result */ protected Object invoke(Object object, String name, Object... arguments) { try { for (Method method : object.getClass().getMethods()) { if (method.getName().equals(name) && method.getParameterTypes().length == arguments.length) { return method.invoke(object, arguments); } } } catch (Exception e) { throw new AssertionError(e); } throw new AssertionError(name); } /** * Returns the content of target field. * @param object target object * @param name field name * @return content */ protected Object access(Object object, String name) { try { for (Field field : object.getClass().getFields()) { if (field.getName().equals(name)) { return field.get(object); } } } catch (Exception e) { throw new AssertionError(e); } throw new AssertionError(name); } /** * Add a new file content. * @param name target file name * @param contents target file content * @throws IllegalArgumentException if some parameters were {@code null} */ public void add(String name, String contents) { sources.add(new VolatileJavaFile(name, contents)); } /** * Adds a new file in {@code <class-name>.files/<name>.java.txt}. * @param name target file name prefix */ public void add(String name) { Class<?> aClass = getClass(); ClassDescription desc = new ClassDescription(name); String file = MessageFormat.format( "{0}.files/{1}.java.txt", aClass.getSimpleName(), desc.getInternalName()); StringBuilder buf = new StringBuilder(); try (InputStream in = aClass.getResourceAsStream(file)) { assertThat(file, in, not(nullValue())); try (Scanner s = new Scanner(in, "UTF-8")) { while (s.hasNextLine()) { String line = s.nextLine(); buf.append(line .replaceAll("\\$s\\b", desc.getSimpleName()) .replaceAll("\\$p\\b", desc.getPackageName())); buf.append("\n"); } } } catch (IOException e) { throw new AssertionError(e); } sources.add(new VolatileJavaFile(desc.getInternalName(), buf.toString())); } /** * Adds {@link DataModelMirrorRepository} objects from SPI. * @param loader target class loader */ public void addSpiDataModelMirrorRepositories(ClassLoader loader) { for (DataModelMirrorRepository repo : ServiceLoader.load(DataModelMirrorRepository.class, loader)) { dataModelMirrors.add(repo); } } /** * Adds an operator driver for next compilation. * @param driver added operator driver */ public void add(OperatorDriver driver) { operatorDrivers.add(driver); } /** * Adds a data model mirror repository for next compilation. * @param repository a data model mirror repository * @throws IllegalArgumentException if some parameters were {@code null} */ public void add(DataModelMirrorRepository repository) { dataModelMirrors.add(repository); } /** * Creates an operator annotation processor using current settings. * @return an operator annotation processor */ protected Processor operatorProcessor() { return new OperatorAnnotationProcessor() { @Override protected CompileEnvironment createCompileEnvironment(ProcessingEnvironment processingEnv) { return new CompileEnvironment(processingEnv, operatorDrivers, dataModelMirrors); } }; } /** * Creates an flow-part annotation processor using current settings. * @return an flow-part annotation processor */ protected Processor flowPartProcessor() { return new FlowPartAnnotationProcessor() { @Override protected CompileEnvironment createCompileEnvironment(ProcessingEnvironment processingEnv) { return new CompileEnvironment(processingEnv, operatorDrivers, dataModelMirrors); } }; } /** * Starts compilation using the specified annotation processor. * @param processor annotation processor * @return compilation result */ protected ClassLoader start(Processor processor) { SafeProcessor safe = new SafeProcessor(processor); compiler.addProcessor(safe); ClassLoader loader = compile(); safe.rethrow(); return loader; } /** * Starts compilation using the specified callback object. * @param callback target callback * @return compilation result */ protected ClassLoader start(Callback callback) { compiler.addProcessor(new DelegateProcessor(callback)); ClassLoader loader = compile(); callback.rethrow(); return loader; } /** * Requires fail for compilation using the specified annotation processor. * @param processor target annotation processor */ protected void error(Processor processor) { SafeProcessor safe = new SafeProcessor(processor); compiler.addProcessor(safe); List<Diagnostic<? extends JavaFileObject>> diagnostics = doCompile(); safe.rethrow(); assertThat(diagnostics.isEmpty(), is(false)); } /** * Requires fail for compilation using the specified callback object. * @param callback target callback object */ protected void error(Callback callback) { compiler.addProcessor(new DelegateProcessor(callback)); List<Diagnostic<? extends JavaFileObject>> diagnostics = doCompile(); callback.rethrow(); assertThat("must have errors", diagnostics.isEmpty(), is(false)); } private ClassLoader compile() { List<Diagnostic<? extends JavaFileObject>> diagnostics = doCompile(); boolean wrong = false; for (Diagnostic<?> d : diagnostics) { if (d.getKind() != Diagnostic.Kind.NOTE) { wrong = true; break; } } if (wrong) { for (JavaFileObject java : compiler.getSources()) { try { System.out.println("====" + java.getName()); System.out.println(java.getCharContent(true)); } catch (IOException e) { // ignore. } } for (Diagnostic<? extends JavaFileObject> d : diagnostics) { System.out.println("===="); System.out.println(d); } throw new AssertionError(diagnostics); } return compiler.getClassLoader(); } private List<Diagnostic<? extends JavaFileObject>> doCompile() { if (LOG.isDebugEnabled()) { for (JavaFileObject java : sources) { try { LOG.debug("==== {}", java.getName()); LOG.debug("{}", java.getCharContent(true)); } catch (IOException e) { // ignore. } } } compiler.addArguments("-Xlint:unchecked"); for (JavaFileObject java : sources) { compiler.addSource(java); } if (sources.isEmpty()) { compiler.addSource(new VolatileJavaFile("A", "public class A {}")); } List<Diagnostic<? extends JavaFileObject>> diagnostics = compiler.doCompile(); if (LOG.isDebugEnabled()) { for (JavaFileObject java : compiler.getSources()) { try { LOG.debug("==== {}", java.getName()); LOG.debug("{}", java.getCharContent(true)); } catch (IOException e) { // ignore. } } for (Diagnostic<? extends JavaFileObject> d : diagnostics) { LOG.debug("===="); LOG.debug("{}", d); } } return diagnostics; } }