/** * 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.method; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.ElementFilter; import org.junit.Test; import com.asakusafw.operator.Callback; import com.asakusafw.operator.CompileEnvironment; import com.asakusafw.operator.Constants; import com.asakusafw.operator.MockSource; import com.asakusafw.operator.OperatorCompilerTestRoot; import com.asakusafw.operator.StringDataModelMirrorRepository; import com.asakusafw.operator.description.ClassDescription; import com.asakusafw.operator.description.Descriptions; import com.asakusafw.operator.description.EnumConstantDescription; import com.asakusafw.operator.model.KeyMirror; import com.asakusafw.operator.model.OperatorClass; import com.asakusafw.operator.model.OperatorDescription; import com.asakusafw.operator.model.OperatorDescription.Document; import com.asakusafw.operator.model.OperatorDescription.MethodReference; import com.asakusafw.operator.model.OperatorDescription.Node; import com.asakusafw.operator.model.OperatorDescription.Node.Kind; import com.asakusafw.operator.model.OperatorDescription.ParameterReference; import com.asakusafw.operator.model.OperatorDescription.Reference; import com.asakusafw.operator.model.OperatorDescription.ReferenceDocument; import com.asakusafw.operator.model.OperatorDescription.ReturnReference; import com.asakusafw.operator.model.OperatorDescription.SpecialReference; import com.asakusafw.operator.model.OperatorDescription.TextDocument; import com.asakusafw.operator.model.OperatorElement; import com.asakusafw.operator.util.AnnotationHelper; import com.asakusafw.operator.util.ElementHelper; import com.asakusafw.vocabulary.flow.Source; import com.asakusafw.vocabulary.flow.graph.FlowElement; import com.asakusafw.vocabulary.flow.graph.FlowElementOutput; import com.asakusafw.vocabulary.flow.graph.FlowElementPortDescription; import com.asakusafw.vocabulary.flow.graph.OperatorDescription.Parameter; import com.asakusafw.vocabulary.flow.graph.OperatorHelper; import com.asakusafw.vocabulary.flow.graph.PortConnection; /** * Test for {@link OperatorFactoryEmitter}. */ public class OperatorFactoryEmitterTest extends OperatorCompilerTestRoot { /** * simple case. */ @Test public void simple() { Object factory = compile(new Action("com.example.Simple", "method") { @Override protected OperatorDescription analyze(ExecutableElement element) { Document document = new TextDocument("Hello, world!"); List<Node> parameters = new ArrayList<>(); List<Node> outputs = new ArrayList<>(); return new OperatorDescription(document, parameters, outputs); } }); Object node = invoke(factory, "method"); assertThat(node.getClass().getName().replace('$', '.'), is("com.example.SimpleFactory.Method")); } /** * w/ input. */ @Test public void input() { Object factory = compile(new Action("com.example.WithParameter", "method") { @Override protected OperatorDescription analyze(ExecutableElement element) { List<Node> parameters = new ArrayList<>(); parameters.add(new Node( Kind.INPUT, "in", new ReferenceDocument(new ParameterReference(0)), env.findDeclaredType(Descriptions.classOf(String.class)), new ParameterReference(0))); List<Node> outputs = new ArrayList<>(); return new OperatorDescription(new ReferenceDocument(new MethodReference()), parameters, outputs); } }); MockSource<String> source = MockSource.of(String.class); invoke(factory, "method", source); FlowElement info = getOppositeNode(source); assertThat(info.getInputPorts().size(), is(1)); assertThat(info.getOutputPorts().size(), is(0)); assertThat(getParameters(info).size(), is(0)); FlowElementPortDescription port = info.getInputPorts().get(0).getDescription(); assertThat(port.getName(), is("in")); assertThat(port.getDataType(), is((Object) String.class)); } /** * w/ output. */ @Test public void output() { Object factory = compile(new Action("com.example.WithParameter", "method") { @Override protected OperatorDescription analyze(ExecutableElement element) { List<Node> parameters = new ArrayList<>(); List<Node> outputs = new ArrayList<>(); outputs.add(new Node( Kind.OUTPUT, "out", new ReferenceDocument(new ParameterReference(0)), env.findDeclaredType(Descriptions.classOf(String.class)), new ParameterReference(0))); return new OperatorDescription(new ReferenceDocument(new MethodReference()), parameters, outputs); } }); Object node = invoke(factory, "method"); assertThat(field(node.getClass(), "out"), is(notNullValue())); Object accessed = access(node, "out"); assertThat(accessed, is(instanceOf(Source.class))); FlowElement info = getNode((Source<?>) accessed); assertThat(info.getInputPorts().size(), is(0)); assertThat(info.getOutputPorts().size(), is(1)); assertThat(getParameters(info).size(), is(0)); FlowElementPortDescription port = info.getOutputPorts().get(0).getDescription(); assertThat(port.getName(), is("out")); assertThat(port.getDataType(), is((Object) String.class)); } /** * w/ argument. */ @Test public void argument() { Object factory = compile(new Action("com.example.WithParameter", "method") { @Override protected OperatorDescription analyze(ExecutableElement element) { List<Node> parameters = new ArrayList<>(); parameters.add(new Node( Kind.DATA, "arg", new ReferenceDocument(new ParameterReference(0)), env.findDeclaredType(Descriptions.classOf(String.class)), new ParameterReference(0))); List<Node> outputs = new ArrayList<>(); return new OperatorDescription(new ReferenceDocument(new MethodReference()), parameters, outputs); } }); invoke(factory, "method", "Hello, world!"); } /** * w/ type parameters. */ @Test public void projective() { Object factory = compile(new Action("com.example.WithTypeParameter", "method") { @Override protected OperatorDescription analyze(ExecutableElement element) { List<Node> parameters = new ArrayList<>(); parameters.add(new Node( Kind.INPUT, "in", new ReferenceDocument(new ParameterReference(0)), element.getTypeParameters().get(0).asType(), new ParameterReference(0))); parameters.add(new Node( Kind.DATA, "arg", new ReferenceDocument(new ParameterReference(1)), env.findDeclaredType(Descriptions.classOf(String.class)), new ParameterReference(1))); List<Node> outputs = new ArrayList<>(); outputs.add(new Node( Kind.OUTPUT, "out", new ReferenceDocument(new ReturnReference()), element.getTypeParameters().get(0).asType(), new ReturnReference())); return new OperatorDescription(new ReferenceDocument(new MethodReference()), parameters, outputs); } }); MockSource<String> source = MockSource.of(String.class); invoke(factory, "method", source, "Hello, world!"); FlowElement info = getOppositeNode(source); assertThat(info.getInputPorts().size(), is(1)); assertThat(info.getOutputPorts().size(), is(1)); assertThat(getParameters(info).size(), is(1)); } /** * w/ attribute. */ @Test public void attribute() { Object factory = compile(new Action("com.example.WithParameter", "method") { @Override protected OperatorDescription analyze(ExecutableElement element) { Document document = new TextDocument("Hello, world!"); List<Node> parameters = new ArrayList<>(); parameters.add(new Node( Kind.INPUT, "in", new ReferenceDocument(new ParameterReference(0)), env.findDeclaredType(Descriptions.classOf(String.class)), new ParameterReference(0))); List<Node> outputs = new ArrayList<>(); List<EnumConstantDescription> attributes = new ArrayList<>(); attributes.add(EnumConstantDescription.of(MockAttribute.OK)); return new OperatorDescription(document, parameters, outputs, attributes); } }); MockSource<String> source = MockSource.of(String.class); invoke(factory, "method", source); FlowElement info = getOppositeNode(source); assertThat(info.getInputPorts().size(), is(1)); assertThat(info.getOutputPorts().size(), is(0)); assertThat(getParameters(info).size(), is(0)); assertThat(info.getAttribute(MockAttribute.class), is(MockAttribute.OK)); } /** * w/ key. */ @Test public void key() { add(new StringDataModelMirrorRepository()); Object factory = compile(new Action("com.example.WithKey", "method") { @Override protected OperatorDescription analyze(ExecutableElement element) { VariableElement param = element.getParameters().get(0); AnnotationMirror mirror = param.getAnnotationMirrors().get(0); List<Node> parameters = new ArrayList<>(); parameters.add(new Node( Kind.INPUT, "in", new ReferenceDocument(new ParameterReference(0)), env.findDeclaredType(Descriptions.classOf(String.class)), new ParameterReference(0)) .withKey(KeyMirror.parse(env, mirror, param, env.findDataModel(param.asType())))); List<Node> outputs = new ArrayList<>(); return new OperatorDescription(new ReferenceDocument(new MethodReference()), parameters, outputs); } }); MockSource<String> source = MockSource.of(String.class); invoke(factory, "method", source); FlowElement info = getOppositeNode(source); assertThat(info.getInputPorts().size(), is(1)); assertThat(info.getOutputPorts().size(), is(0)); assertThat(getParameters(info).size(), is(0)); FlowElementPortDescription port = info.getInputPorts().get(0).getDescription(); assertThat(port.getName(), is("in")); assertThat(port.getDataType(), is((Object) String.class)); } /** * w/ support. */ @Test public void support() { Object factory = compile(new Action("com.example.WithSupport", "method") { @Override protected OperatorDescription analyze(ExecutableElement element) { Document document = new TextDocument("Hello, world!"); List<Node> parameters = new ArrayList<>(); parameters.add(new Node( Kind.INPUT, "in", new ReferenceDocument(new ParameterReference(0)), env.findDeclaredType(Descriptions.classOf(String.class)), new ParameterReference(0))); List<Node> outputs = new ArrayList<>(); List<EnumConstantDescription> attributes = new ArrayList<>(); ExecutableElement support = null; for (ExecutableElement m : ElementFilter.methodsIn(element.getEnclosingElement().getEnclosedElements())) { if (m.getSimpleName().contentEquals("support")) { support = m; break; } } assertThat(support, is(notNullValue())); return new OperatorDescription(document, parameters, outputs, attributes) .withSupport(support); } }); MockSource<String> source = MockSource.of(String.class); invoke(factory, "method", source); FlowElement info = getOppositeNode(source); assertThat(info.getInputPorts().size(), is(1)); assertThat(info.getOutputPorts().size(), is(0)); assertThat(getParameters(info).size(), is(0)); OperatorHelper support = info.getAttribute(OperatorHelper.class); assertThat(support, is(notNullValue())); assertThat(support.getName(), is("support")); assertThat(support.getParameterTypes(), contains((Object) int.class)); } /** * w/ external reference. */ @Test public void external() { Object factory = compile(new Action("com.example.WithVariable", "method") { @Override protected OperatorDescription analyze(ExecutableElement element) { TypeElement type = (TypeElement) element.getEnclosingElement(); VariableElement var = type.getEnclosedElements().stream() .filter(e -> e.getKind() == ElementKind.FIELD) .map(VariableElement.class::cast) .filter(e -> e.getSimpleName().contentEquals("VAR")) .findAny() .get(); List<Node> parameters = new ArrayList<>(); parameters.add(new Node( Kind.INPUT, "in", Document.external(var), env.findDeclaredType(Descriptions.classOf(String.class)), new SpecialReference("SPECIAL"))); List<Node> outputs = new ArrayList<>(); return new OperatorDescription(new ReferenceDocument(new MethodReference()), parameters, outputs); } }); MockSource<String> source = MockSource.of(String.class); invoke(factory, "method", source); FlowElement info = getOppositeNode(source); assertThat(info.getInputPorts().size(), is(1)); assertThat(info.getOutputPorts().size(), is(0)); assertThat(getParameters(info).size(), is(0)); FlowElementPortDescription port = info.getInputPorts().get(0).getDescription(); assertThat(port.getName(), is("in")); assertThat(port.getDataType(), is((Object) String.class)); } /** * w/ special reference. */ @Test public void special() { Object factory = compile(new Action("com.example.Simple", "method") { @Override protected OperatorDescription analyze(ExecutableElement element) { List<Node> parameters = new ArrayList<>(); parameters.add(new Node( Kind.INPUT, "in", Document.reference(Reference.special("SPECIAL")), env.findDeclaredType(Descriptions.classOf(String.class)), new SpecialReference("SPECIAL"))); List<Node> outputs = new ArrayList<>(); return new OperatorDescription(new ReferenceDocument(new MethodReference()), parameters, outputs); } }); MockSource<String> source = MockSource.of(String.class); invoke(factory, "method", source); FlowElement info = getOppositeNode(source); assertThat(info.getInputPorts().size(), is(1)); assertThat(info.getOutputPorts().size(), is(0)); assertThat(getParameters(info).size(), is(0)); FlowElementPortDescription port = info.getInputPorts().get(0).getDescription(); assertThat(port.getName(), is("in")); assertThat(port.getDataType(), is((Object) String.class)); } /** * input w/ attribute. */ @Test public void input_attribute() { Object factory = compile(new Action("com.example.WithParameter", "method") { @Override protected OperatorDescription analyze(ExecutableElement element) { List<Node> parameters = new ArrayList<>(); parameters.add(new Node( Kind.INPUT, "in", new ReferenceDocument(new ParameterReference(0)), env.findDeclaredType(Descriptions.classOf(String.class)), new ParameterReference(0)).withAttribute(EnumConstantDescription.of(MockAttribute.OK))); List<Node> outputs = new ArrayList<>(); return new OperatorDescription(new ReferenceDocument(new MethodReference()), parameters, outputs); } }); MockSource<String> source = MockSource.of(String.class); invoke(factory, "method", source); FlowElement info = getOppositeNode(source); assertThat(info.getInputPorts().size(), is(1)); assertThat(info.getOutputPorts().size(), is(0)); assertThat(getParameters(info).size(), is(0)); FlowElementPortDescription port = info.getInputPorts().get(0).getDescription(); assertThat(port.getName(), is("in")); assertThat(port.getDataType(), is((Object) String.class)); assertThat(port.getAttribute(MockAttribute.class), is(MockAttribute.OK)); } /** * output w/ attribute. */ @Test public void output_attribute() { Object factory = compile(new Action("com.example.WithParameter", "method") { @Override protected OperatorDescription analyze(ExecutableElement element) { List<Node> parameters = new ArrayList<>(); List<Node> outputs = new ArrayList<>(); outputs.add(new Node( Kind.OUTPUT, "out", new ReferenceDocument(new ParameterReference(0)), env.findDeclaredType(Descriptions.classOf(String.class)), new ParameterReference(0)).withAttribute(EnumConstantDescription.of(MockAttribute.OK))); return new OperatorDescription(new ReferenceDocument(new MethodReference()), parameters, outputs); } }); Object node = invoke(factory, "method"); assertThat(field(node.getClass(), "out"), is(notNullValue())); Object accessed = access(node, "out"); assertThat(accessed, is(instanceOf(Source.class))); FlowElement info = getNode((Source<?>) accessed); assertThat(info.getInputPorts().size(), is(0)); assertThat(info.getOutputPorts().size(), is(1)); assertThat(getParameters(info).size(), is(0)); FlowElementPortDescription port = info.getOutputPorts().get(0).getDescription(); assertThat(port.getName(), is("out")); assertThat(port.getDataType(), is((Object) String.class)); assertThat(port.getAttribute(MockAttribute.class), is(MockAttribute.OK)); } private FlowElement getOppositeNode(Source<?> source) { FlowElementOutput output = source.toOutputPort(); assertThat(output.getConnected().isEmpty(), is(false)); for (PortConnection connection : output.getConnected()) { return connection.getDownstream().getOwner(); } throw new AssertionError(source); } private FlowElement getNode(Source<?> source) { FlowElementOutput output = source.toOutputPort(); return output.getOwner(); } private List<Parameter> getParameters(FlowElement info) { return ((com.asakusafw.vocabulary.flow.graph.OperatorDescription) info.getDescription()).getParameters(); } private Object compile(Action action) { add(action.className); add("com.example.Mock"); ClassLoader classLoader = start(action); assertThat(action.performed, is(true)); ClassDescription implClass = Constants.getFactoryClass(action.className); return create(classLoader, implClass); } private abstract class Action extends Callback { final String className; final Set<String> methodNames; boolean performed; Action(String className, String... methodNames) { this.className = className; this.methodNames = new HashSet<>(); Collections.addAll(this.methodNames, methodNames); } @Override protected CompileEnvironment createCompileEnvironment(ProcessingEnvironment processingEnv) { return new CompileEnvironment(processingEnv, operatorDrivers, dataModelMirrors); } @Override protected void test() { TypeElement element = env.findTypeElement(new ClassDescription(className)); if (round.getRootElements().contains(element)) { TypeElement annotationType = env.findTypeElement(new ClassDescription("com.example.Mock")); this.performed = true; List<OperatorElement> elems = new ArrayList<>(); for (ExecutableElement e : ElementFilter.methodsIn(element.getEnclosedElements())) { AnnotationMirror annotation = AnnotationHelper.findAnnotation(env, annotationType, e); if (methodNames.contains(e.getSimpleName().toString())) { OperatorDescription desc = analyze(e); ElementHelper.validate(env, e, desc); elems.add(new OperatorElement(annotation, e, desc)); } } OperatorClass analyzed = new OperatorClass(element, elems); new OperatorFactoryEmitter(env).emit(analyzed); new OperatorImplementationEmitter(env).emit(analyzed); } } protected abstract OperatorDescription analyze(ExecutableElement element); } }