/**
* 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.compiler.operator;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.Processor;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.After;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.asakusafw.utils.collections.Sets;
import com.asakusafw.utils.graph.Graph;
import com.asakusafw.utils.graph.Graphs;
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;
import com.asakusafw.vocabulary.flow.Source;
import com.asakusafw.vocabulary.flow.graph.FlowElement;
import com.asakusafw.vocabulary.flow.graph.FlowElementInput;
import com.asakusafw.vocabulary.flow.graph.FlowElementOutput;
import com.asakusafw.vocabulary.flow.graph.FlowIn;
import com.asakusafw.vocabulary.flow.graph.PortConnection;
import com.asakusafw.vocabulary.flow.testing.MockIn;
/**
* A test root for operator compilers.
*/
public class OperatorCompilerTestRoot {
static final Logger LOG = LoggerFactory.getLogger(OperatorCompilerTestRoot.class);
ModelFactory f = Models.getModelFactory();
private final VolatileCompiler compiler = new VolatileCompiler();
private final List<JavaFileObject> sources = new ArrayList<>();
/**
* Disposes the internal compiler.
* @throws Exception if exception was occurred
*/
@After
public void tearDown() throws Exception {
compiler.close();
}
/**
* Loads class and returns the instance of the loaded class.
* @param loader the class loader
* @param name the class name
* @return the created instance
*/
protected Object create(ClassLoader loader, String name) {
try {
Class<?> loaded = loader.loadClass(name);
return loaded.newInstance();
} catch (Exception e) {
throw new AssertionError(e);
}
}
/**
* Invokes the target method.
* @param object the target object
* @param name the method name
* @param arguments the method arguments
* @return the invocation result
*/
protected Object invoke(Object object, String name, Object... arguments) {
try {
for (Method method : object.getClass().getMethods()) {
if (method.getName().equals(name)) {
return method.invoke(object, arguments);
}
}
} catch (Exception e) {
throw new AssertionError(e);
}
throw new AssertionError(name);
}
/**
* Returns the field value.
* @param object the target object
* @param name the field name
* @return the field value
*/
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);
}
/**
* Returns the field value as operator output.
* @param dataType the data type
* @param object the target object
* @param name the field name
* @param <T> the data type
* @return the field value
*/
@SuppressWarnings("unchecked")
protected <T> Source<T> output(Class<T> dataType, Object object, String name) {
try {
for (Field field : object.getClass().getFields()) {
if (field.getName().equals(name)) {
return (Source<T>) field.get(object);
}
}
} catch (Exception e) {
throw new AssertionError(e);
}
throw new AssertionError(name);
}
/**
* Returns a matcher for comparing to a set of strings.
* @param names the strings
* @return the matcher
*/
protected Matcher<? super Set<String>> isJust(String... names) {
return Matchers.is(Sets.from(names));
}
/**
* Returns the reachable elements from the specified inputs.
* @param inputs the inputs
* @return the graph
*/
protected Graph<String> toGraph(MockIn<?>...inputs) {
FlowElement[] elements = new FlowElement[inputs.length];
for (int i = 0; i < inputs.length; i++) {
elements[i] = inputs[i].toElement();
}
return toGraph(elements);
}
/**
* Returns the reachable elements from the specified inputs.
* @param inputs the inputs
* @return the graph
*/
protected Graph<String> toGraph(List<FlowIn<?>> inputs) {
FlowElement[] elements = new FlowElement[inputs.size()];
for (int i = 0, n = inputs.size(); i < n; i++) {
elements[i] = inputs.get(i).getFlowElement();
}
return toGraph(elements);
}
/**
* Returns the reachable elements from the specified elements.
* @param startingElements the starting elements
* @return the graph
*/
protected Graph<String> toGraph(FlowElement... startingElements) {
Set<String> saw = new HashSet<>();
LinkedList<FlowElement> work = new LinkedList<>();
for (FlowElement elem : startingElements) {
work.add(elem);
}
Graph<String> graph = Graphs.newInstance();
while (work.isEmpty() == false) {
FlowElement elem = work.removeFirst();
String self = elem.getDescription().getName();
if (saw.contains(self)) {
continue;
}
saw.add(self);
for (FlowElementInput input : elem.getInputPorts()) {
for (PortConnection conn : input.getConnected()) {
work.add(conn.getUpstream().getOwner());
}
}
for (FlowElementOutput output : elem.getOutputPorts()) {
for (PortConnection conn : output.getConnected()) {
FlowElement opposite = conn.getDownstream().getOwner();
work.add(opposite);
String dest = opposite.getDescription().getName();
graph.addEdge(self, dest);
}
}
}
return graph;
}
/**
* Adds a source file to be compiled.
* @param name the target class name
*/
protected void add(String name) {
Class<?> aClass = getClass();
String file = MessageFormat.format(
"{0}.files/{1}.java.txt",
aClass.getSimpleName(),
name.replace('.', '/'));
StringBuilder buf = new StringBuilder();
try (InputStream in = aClass.getResourceAsStream(file)) {
assertThat(file, in, not(nullValue()));
Reader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
while (true) {
int c = reader.read();
if (c == -1) {
break;
}
buf.append((char) c);
}
} catch (IOException e) {
throw new AssertionError(e);
}
sources.add(new VolatileJavaFile(
name.replace('.', '/'),
buf.toString()));
}
/**
* Compiles and returns the class loader for loading compilation results.
* @param processor the annotation processor
* @return the class loader
*/
protected ClassLoader start(Processor processor) {
SafeProcessor safe = new SafeProcessor(processor);
compiler.addProcessor(safe);
ClassLoader loader = start();
safe.rethrow();
return loader;
}
/**
* Compiles and returns the class loader for loading compilation results.
* @param callback the callback object
* @return the class loader
*/
protected ClassLoader start(Callback callback) {
compiler.addProcessor(new DelegateProcessor(callback));
ClassLoader loader = start();
callback.rethrow();
return loader;
}
/**
* Compiles and returns the class loader for loading compilation results.
* @param procs the operator processors
* @return the class loader
*/
protected ClassLoader start(OperatorProcessor... procs) {
return start(new OperatorCompiler() {
@Override
protected Iterable<OperatorProcessor> findOperatorProcessors(OperatorCompilingEnvironment env) {
return Arrays.asList(procs);
}
});
}
/**
* Compiles and check if compilation errors are occurred.
* @param procs the operator processors
*/
protected void error(OperatorProcessor... procs) {
SafeProcessor proc = new SafeProcessor(new OperatorCompiler() {
@Override
protected Iterable<OperatorProcessor> findOperatorProcessors(OperatorCompilingEnvironment env) {
return Arrays.asList(procs);
}
});
compiler.addProcessor(proc);
List<Diagnostic<? extends JavaFileObject>> diagnostics = doCompile();
proc.rethrow();
assertThat(diagnostics, not(hasSize(0)));
}
/**
* Compiles and check if compilation errors are occurred.
* @param callback the callback object
*/
protected void error(Callback callback) {
compiler.addProcessor(new DelegateProcessor(callback));
List<Diagnostic<? extends JavaFileObject>> diagnostics = doCompile();
callback.rethrow();
assertThat(diagnostics, not(hasSize(0)));
}
private ClassLoader start() {
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;
}
}