/** * 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.builtin; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.util.ElementFilter; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; import com.asakusafw.operator.Callback; import com.asakusafw.operator.CompileEnvironment; import com.asakusafw.operator.CompileEnvironment.Support; import com.asakusafw.operator.MockDataModelMirrorRepository; import com.asakusafw.operator.OperatorCompilerTestRoot; import com.asakusafw.operator.OperatorDriver; import com.asakusafw.operator.WarningAction; import com.asakusafw.operator.description.ClassDescription; import com.asakusafw.operator.description.Descriptions; import com.asakusafw.operator.description.ObjectDescription; import com.asakusafw.operator.description.ValueDescription; import com.asakusafw.operator.method.OperatorMethodAnalyzer; import com.asakusafw.operator.model.OperatorClass; import com.asakusafw.operator.model.OperatorDescription.Node; import com.asakusafw.operator.model.OperatorElement; import com.asakusafw.vocabulary.attribute.ViewInfo; /** * Test helper for {@link OperatorDriver}s. */ public class OperatorDriverTestRoot extends OperatorCompilerTestRoot { /** * Target operator driver. */ protected final OperatorDriver driver; final List<String> dataModelNames; /** * Creates a new instance. * @param driver target driver * @throws IllegalArgumentException if some parameters were {@code null} */ public OperatorDriverTestRoot(OperatorDriver driver) { this.driver = driver; this.dataModelNames = new ArrayList<>(); } /** * Returns the default name. * @param annotationType the target operator annotation * @param elementName the target element name * @return the default name */ protected static String defaultName(Class<? extends Annotation> annotationType, String elementName) { try { Method element = annotationType.getMethod(elementName); return (String) element.getDefaultValue(); } catch (Exception e) { throw new AssertionError(e); } } /** * Compiles and invoke the action. * @param action target test action */ protected void compile(Action action) { prepare(action.className, action.supportClassNames); start(action); assertThat(action.performed, is(true)); } /** * Compiles and check violation exist. * @param className target class name * @param supportClassNames support class names * @throws IllegalArgumentException if some parameters were {@code null} */ protected void violate(String className, String... supportClassNames) { prepare(className, Arrays.asList(supportClassNames)); Action action = new Action(className) { @Override protected void perform(OperatorElement target) { return; } @Override protected CompileEnvironment createCompileEnvironment(ProcessingEnvironment processingEnv) { return super.createCompileEnvironment(processingEnv) .withWarningAction(WarningAction.REPORT) .withStrictOperatorParameterOrder(true); } }; action.expectError = true; error(action); assertThat(action.performed, is(true)); } private void prepare(String className, List<String> supportClassNames) { add(className); for (String name : supportClassNames) { add(name); } addModels(); } /** * Registers data model types. */ protected void addModels() { addDataModel("Model", "package com.example; public class Model { public int key; public String content; }"); addDataModel("Side", "package com.example; public class Side { public int id; }"); addDataModel("Proceeded", "package com.example; public class Proceeded { public int proceeded; }"); addDataModel("Projective", "package com.example; public class Projective { public String content; }"); } /** * Registers data model type. * @param simpleName target simple name (this will be qualified with {@code com.example}) */ protected final void addDataModel(String simpleName) { add("com/example/" + simpleName); dataModelNames.add(simpleName); } /** * Registers data model type. * @param simpleName target simple name (this will be qualified with {@code com.example}) * @param content target content */ protected final void addDataModel(String simpleName, String content) { add("com/example/" + simpleName, content); dataModelNames.add(simpleName); } /** * Returns a plain view info. * @return the matcher */ public ObjectDescription flatView() { return ObjectDescription.of( DslBuilder.TYPE_VIEW_INFO, DslBuilder.NAME_FLAT_VIEW_INFO_FACTORY); } /** * Returns a group view info. * @param terms the property terms * @return the matcher */ public ObjectDescription groupView(String... terms) { return ObjectDescription.of( DslBuilder.TYPE_VIEW_INFO, DslBuilder.NAME_GROUP_VIEW_INFO_FACTORY, Arrays.stream(terms).map(Descriptions::valueOf).collect(Collectors.toList())); } /** * Returns a matcher that tests whether or not the value represents a view info. * @return the matcher */ public Matcher<ValueDescription> isView() { return new BaseMatcher<ValueDescription>() { @Override public boolean matches(Object item) { ValueDescription v = (ValueDescription) item; return v.getValueType().equals(ClassDescription.of(ViewInfo.class)); } @Override public void describeTo(Description description) { description.appendText("is view"); } }; } /** * Test action. */ protected abstract class Action extends Callback { final String className; final List<String> supportClassNames; boolean performed; boolean expectError; /** * Creates a new instance. * @param className target class name * @param supportClassNames support class names */ protected Action(String className, String... supportClassNames) { this.className = className; this.supportClassNames = Arrays.asList(supportClassNames); } @Override protected CompileEnvironment createCompileEnvironment(ProcessingEnvironment processingEnv) { return new CompileEnvironment( processingEnv, Arrays.asList(driver), Arrays.asList(new MockDataModelMirrorRepository() .add("com.example", dataModelNames.toArray(new String[dataModelNames.size()])))); } @Override protected void test() { TypeElement element = env.findTypeElement(new ClassDescription(className)); assertThat(className, element, is(notNullValue())); while (element.getEnclosingElement().getKind() != ElementKind.PACKAGE) { element = (TypeElement) element.getEnclosingElement(); } if (round.getRootElements().contains(element)) { this.performed = true; testSpi(); OperatorMethodAnalyzer analyzer = new OperatorMethodAnalyzer(env); for (TypeElement annotation : annotatios) { if (env.findDriver(annotation) == null) { continue; } for (ExecutableElement method : ElementFilter.methodsIn(round.getElementsAnnotatedWith(annotation))) { analyzer.register(annotation, method); } } Collection<OperatorClass> resolved = analyzer.resolve(); if (expectError) { return; } if (resolved.size() == 1) { OperatorClass first = resolved.iterator().next(); assertThat(first.getDeclaration(), is(element)); for (OperatorElement op : first.getElements()) { assertThat(op.getDeclaration().toString(), op.getDescription(), is(notNullValue())); perform(op); } } else { perform(null); } } } private void testSpi() { CompileEnvironment spi = CompileEnvironment.newInstance( env.getProcessingEnvironment(), Support.OPERATOR_DRIVER); OperatorDriver loaded = spi.findDriver(spi.findTypeElement(driver.getAnnotationTypeName())); assertThat(className, loaded, is(notNullValue())); } /** * Performs the test. * @param target test target */ protected abstract void perform(OperatorElement target); /** * Converts node list into {@code name -> node} map. * @param nodes node list * @return converted map */ protected Map<String, Node> toMap(List<Node> nodes) { Map<String, Node> results = new HashMap<>(); for (Node node : nodes) { results.put(node.getName(), node); } return results; } } }