/** * 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.flow; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import com.asakusafw.compiler.common.NameGenerator; import com.asakusafw.compiler.common.Precondition; import com.asakusafw.runtime.core.Result; import com.asakusafw.runtime.flow.ArrayListBuffer; import com.asakusafw.runtime.flow.FileMapListBuffer; import com.asakusafw.runtime.flow.ListBuffer; import com.asakusafw.utils.java.model.syntax.Expression; import com.asakusafw.utils.java.model.syntax.FieldDeclaration; import com.asakusafw.utils.java.model.syntax.ModelFactory; import com.asakusafw.utils.java.model.syntax.SimpleName; import com.asakusafw.utils.java.model.syntax.Statement; import com.asakusafw.utils.java.model.syntax.Type; import com.asakusafw.utils.java.model.util.AttributeBuilder; import com.asakusafw.utils.java.model.util.ExpressionBuilder; import com.asakusafw.utils.java.model.util.ImportBuilder; import com.asakusafw.utils.java.model.util.Models; import com.asakusafw.utils.java.model.util.TypeBuilder; import com.asakusafw.vocabulary.flow.graph.FlowElementAttribute; import com.asakusafw.vocabulary.flow.graph.FlowElementAttributeProvider; import com.asakusafw.vocabulary.flow.graph.FlowElementDescription; import com.asakusafw.vocabulary.flow.graph.FlowElementPortDescription; import com.asakusafw.vocabulary.flow.graph.FlowResourceDescription; import com.asakusafw.vocabulary.flow.graph.OperatorDescription; import com.asakusafw.vocabulary.flow.processor.InputBuffer; /** * An abstract super interface for processing flow elements. * Developers should not inherit this interface directly, and inherit {@link AbstractFlowElementProcessor} instead. */ public interface FlowElementProcessor extends FlowCompilingEnvironment.Initializable { /** * A method name of {@link Result#add(Object)}. */ String RESULT_METHOD_NAME = "add"; //$NON-NLS-1$ /** * Returns the kind of this processor. * @return the processor kind */ FlowElementProcessor.Kind getKind(); /** * Returns the target operator annotation type of this processor. * @return the target operator annotation type */ Class<? extends Annotation> getTargetAnnotationType(); /** * The abstract implementation of context objects for {@link FlowElementProcessor}. * @since 0.1.0 * @version 0.9.1 */ abstract class AbstractProcessorContext implements FlowElementAttributeProvider { /** * The current environment. */ protected final FlowCompilingEnvironment environment; private final FlowElementAttributeProvider element; /** * The Java DOM factory. */ protected final ModelFactory factory; /** * The import declaration builder. */ protected final ImportBuilder importer; /** * The unique name generator. */ protected final NameGenerator names; /** * The target operator description. */ protected final OperatorDescription description; /** * The mapping between external resources and their Java expressions. */ protected final Map<FlowResourceDescription, Expression> resources; /** * The generated fields. */ protected final List<FieldDeclaration> generatedFields; /** * Creates a new instance. * @param environment the current context * @param element the target element * @param importer the import declaration builder * @param names the unique name generator * @param desc the target operator description * @param resources the mapping between external resources and their Java expressions * @throws IllegalArgumentException if the parameters are {@code null} */ public AbstractProcessorContext( FlowCompilingEnvironment environment, FlowElementAttributeProvider element, ImportBuilder importer, NameGenerator names, OperatorDescription desc, Map<FlowResourceDescription, Expression> resources) { Precondition.checkMustNotBeNull(environment, "environment"); //$NON-NLS-1$ Precondition.checkMustNotBeNull(importer, "importer"); //$NON-NLS-1$ Precondition.checkMustNotBeNull(element, "element"); //$NON-NLS-1$ Precondition.checkMustNotBeNull(names, "names"); //$NON-NLS-1$ Precondition.checkMustNotBeNull(desc, "desc"); //$NON-NLS-1$ Precondition.checkMustNotBeNull(resources, "resources"); //$NON-NLS-1$ this.environment = environment; this.element = element; this.factory = environment.getModelFactory(); this.importer = importer; this.names = names; this.description = desc; this.resources = resources; this.generatedFields = new ArrayList<>(); } /** * Returns the target operator description. * @return the target operator description */ public OperatorDescription getOperatorDescription() { return description; } @Override public Set<? extends Class<? extends FlowElementAttribute>> getAttributeTypes() { return element.getAttributeTypes(); } @Override public <T extends FlowElementAttribute> T getAttribute(Class<T> attributeClass) { return element.getAttribute(attributeClass); } /** * Returns the port description of the target operator. * @param portNumber the target port number * @return the port description * @throws IllegalArgumentException the target port is not found */ public FlowElementPortDescription getInputPort(int portNumber) { if (portNumber < 0 || portNumber >= description.getInputPorts().size()) { throw new IllegalArgumentException("invalid port number"); //$NON-NLS-1$ } return description.getInputPorts().get(portNumber); } /** * Returns the port description of the target operator. * @param portNumber the target port number * @return the port description * @throws IllegalArgumentException the target port is not found */ public FlowElementPortDescription getOutputPort(int portNumber) { if (portNumber < 0 || portNumber >= description.getOutputPorts().size()) { throw new IllegalArgumentException("invalid port number"); //$NON-NLS-1$ } return description.getOutputPorts().get(portNumber); } /** * Returns the external resource description of the target operator. * @param resourceNumber the target resource number * @return the resource description * @throws IllegalArgumentException the target resource is not found */ public FlowResourceDescription getResourceDescription(int resourceNumber) { if (resourceNumber < 0 || resourceNumber >= description.getResources().size()) { throw new IllegalArgumentException("invalid resource number"); //$NON-NLS-1$ } FlowResourceDescription resource = description.getResources().get(resourceNumber); return resource; } /** * Returns the Java expression to access to the target external resource. * @param resource the target resource description * @return the expression * @throws IllegalArgumentException if the target parameter is {@code null} */ public Expression getResource(FlowResourceDescription resource) { Precondition.checkMustNotBeNull(resource, "resource"); //$NON-NLS-1$ Expression expression = resources.get(resource); assert expression != null; return expression; } /** * Returns the Java DOM factory. * @return the Java DOM factory */ public ModelFactory getModelFactory() { return factory; } private Expression addField(Type type, String name, Expression init) { assert type != null; assert name != null; SimpleName fieldName = createName(name); FieldDeclaration field = factory.newFieldDeclaration( null, new AttributeBuilder(factory) .Private() .toAttributes(), type, fieldName, init); generatedFields.add(field); return factory.newFieldAccessExpression( factory.newThis(), fieldName); } /** * Returns the generated fields. * @return the generated fields */ public List<FieldDeclaration> getGeneratedFields() { return generatedFields; } /** * Returns a new unique name. * @param hint the name hint * @return the unique name * @throws IllegalArgumentException if the parameter is {@code null} */ public SimpleName createName(String hint) { Precondition.checkMustNotBeNull(hint, "hint"); //$NON-NLS-1$ return names.create(hint); } /** * Returns an expression which creates a new instance of the target operator implementation class. * @return the generated expression */ public Expression createImplementation() { Class<?> implementing = description.getDeclaration().getImplementing(); Type type = convert(implementing); return addField(type, "op", new TypeBuilder(factory, type) //$NON-NLS-1$ .newObject() .toExpression()); } /** * Returns an expression which accesses a new field. * @param type the target type * @param name a name hint for the target field * @return an expression to access the created field * @throws IllegalArgumentException if the parameters are {@code null} */ public Expression createField(java.lang.reflect.Type type, String name) { return createField(type, name, null); } /** * Returns an expression which accesses a new field. * @param type the target type * @param name a name hint for the target field * @param init field initialization expression (nullable) * @return an expression to access the created field * @throws IllegalArgumentException if some parameters were {@code null} * @since 0.5.1 */ public Expression createField(java.lang.reflect.Type type, String name, Expression init) { Precondition.checkMustNotBeNull(type, "type"); //$NON-NLS-1$ Precondition.checkMustNotBeNull(name, "name"); //$NON-NLS-1$ return addField( importer.toType(type), name, init); } /** * Returns a new data model object mirror. * @param type the data model type * @return the generated expression * @throws IllegalArgumentException if the parameters are {@code null} */ public DataObjectMirror createModelCache(java.lang.reflect.Type type) { Precondition.checkMustNotBeNull(type, "type"); //$NON-NLS-1$ DataClass data = environment.getDataClasses().load(type); if (data == null) { environment.error( Messages.getString("FlowElementProcessor.errorMissingDataClass"), //$NON-NLS-1$ type); data = new DataClass.Unresolved(factory, type); } Type domType = importer.toType(type); Expression cache = addField( domType, "cache", //$NON-NLS-1$ data.createNewInstance(domType)); return new DataObjectMirror(factory, cache, data); } /** * Returns a new {@link ListBuffer} object mirror. * @param type the element type of the {@link ListBuffer} * @param bufferKind the input buffer kind * @return the generated expression * @throws IllegalArgumentException if the parameters are {@code null} */ public ListBufferMirror createListBuffer(java.lang.reflect.Type type, InputBuffer bufferKind) { Precondition.checkMustNotBeNull(type, "type"); //$NON-NLS-1$ Precondition.checkMustNotBeNull(bufferKind, "bufferKind"); //$NON-NLS-1$ Type elementType = importer.toType(type); Class<?> bufferType = inputBufferTypeFromKind(bufferKind); Type listType = importer.resolve(factory.newParameterizedType( Models.toType(factory, bufferType), Collections.singletonList(elementType))); Expression list = addField( listType, "list", //$NON-NLS-1$ new TypeBuilder(factory, listType) .newObject() .toExpression()); DataClass component = environment.getDataClasses().load(type); if (component == null) { environment.error( Messages.getString("FlowElementProcessor.errorMissingDataClass"), //$NON-NLS-1$ type); component = new DataClass.Unresolved(factory, type); } return new ListBufferMirror(factory, list, component, elementType); } private Class<?> inputBufferTypeFromKind(InputBuffer kind) { assert kind != null; switch (kind) { case EXPAND: return ArrayListBuffer.class; case ESCAPE: return FileMapListBuffer.class; default: throw new AssertionError(kind); } } /** * Returns an imported type for the specified one. * @param type the target type * @return the imported type * @throws IllegalArgumentException if the parameter is {@code null} */ public Type convert(java.lang.reflect.Type type) { Precondition.checkMustNotBeNull(type, "type"); //$NON-NLS-1$ return importer.toType(type); } /** * Returns an imported type for the specified one. * @param type the target type * @return the imported type * @throws IllegalArgumentException if the parameter is {@code null} */ public Type simplify(Type type) { Precondition.checkMustNotBeNull(type, "type"); //$NON-NLS-1$ return importer.resolve(type); } } /** * A mirror of data model objects. */ class DataObjectMirror { private final Expression object; private final DataClass dataClass; /** * Creates a new instance. * @param factory the Java DOM factory * @param object an expression of the target data model object * @param dataClass the data model class * @throws IllegalArgumentException if the parameters are {@code null} */ public DataObjectMirror(ModelFactory factory, Expression object, DataClass dataClass) { Precondition.checkMustNotBeNull(factory, "factory"); //$NON-NLS-1$ Precondition.checkMustNotBeNull(object, "object"); //$NON-NLS-1$ Precondition.checkMustNotBeNull(dataClass, "dataClass"); //$NON-NLS-1$ this.object = object; this.dataClass = dataClass; } /** * Returns an expression of the target data model object. * @return an expression of the target data model object */ public Expression get() { return object; } /** * Returns a statement which copies the contents of the specified object into this mirror. * @param value an expression which accesses the target object * @return the generated statement * @throws IllegalArgumentException the parameter is {@code null} */ public Statement createSet(Expression value) { Precondition.checkMustNotBeNull(value, "value"); //$NON-NLS-1$ return dataClass.assign(object, value); } /** * Returns a statement which resets the contents of this mirror. * @return the generated statement */ public Statement createReset() { return dataClass.reset(object); } } /** * A mirror of a {@link Result} object. */ class ResultMirror { private final ModelFactory factory; private final Expression object; /** * Creates a new instance. * @param factory the Java DOM factory * @param object an expression of the target {@link Result} object * @throws IllegalArgumentException if the parameters are {@code null} */ public ResultMirror(ModelFactory factory, Expression object) { Precondition.checkMustNotBeNull(factory, "factory"); //$NON-NLS-1$ Precondition.checkMustNotBeNull(object, "object"); //$NON-NLS-1$ this.factory = factory; this.object = object; } /** * Returns an expression of the target {@link Result} object. * @return an expression of the target object */ public Expression get() { return object; } /** * Returns a statement which adds a value into this. * @param value an expression of the target value * @return the generated statement * @throws IllegalArgumentException if the parameter is {@code null} */ public Statement createAdd(Expression value) { Precondition.checkMustNotBeNull(value, "value"); //$NON-NLS-1$ return new ExpressionBuilder(factory, object) .method(RESULT_METHOD_NAME, value) .toStatement(); } } /** * A mirror of {@link ListBuffer}. */ class ListBufferMirror { private static final String BEGIN = "begin"; //$NON-NLS-1$ private static final String ADVANCE = "advance"; //$NON-NLS-1$ private static final String END = "end"; //$NON-NLS-1$ private static final String EXPAND = "expand"; //$NON-NLS-1$ private static final String IS_EXPAND_REQUIRED = "isExpandRequired"; //$NON-NLS-1$ private static final String SHRINK = "shrink"; //$NON-NLS-1$ private final ModelFactory factory; private final Expression object; private final DataClass dataClass; private final Type elementType; /** * Creates a new instance. * @param factory the Java DOM factory * @param object an expression of the target {@link ListBuffer} object * @param dataClass the element type * @param elementType the element type * @throws IllegalArgumentException if the parameters are {@code null} */ public ListBufferMirror(ModelFactory factory, Expression object, DataClass dataClass, Type elementType) { Precondition.checkMustNotBeNull(factory, "factory"); //$NON-NLS-1$ Precondition.checkMustNotBeNull(object, "object"); //$NON-NLS-1$ Precondition.checkMustNotBeNull(dataClass, "dataClass"); //$NON-NLS-1$ this.factory = factory; this.object = object; this.dataClass = dataClass; this.elementType = elementType; } /** * Returns an expression of the target {@link ListBuffer} object. * @return the expression of the target object */ public Expression get() { return object; } /** * Returns a statement which initializes this object. * @return the generated statement * @see ListBuffer#begin() */ public Statement createBegin() { return new ExpressionBuilder(factory, object) .method(BEGIN) .toStatement(); } /** * Returns a statement which adds a copy of data model object into this object. * @param value an expression of the target data model object * @return the generated statement * @throws IllegalArgumentException if the parameter is {@code null} * @see ListBuffer#advance() * @see ListBuffer#expand(Object) * @see ListBuffer#isExpandRequired() */ public Statement createAdvance(Expression value) { Precondition.checkMustNotBeNull(value, "value"); //$NON-NLS-1$ List<Statement> thenBlock = Arrays.asList(new Statement[] { new ExpressionBuilder(factory, object) .method(EXPAND, dataClass.createNewInstance(elementType)) .toStatement(), dataClass.assign( new ExpressionBuilder(factory, object) .method(ADVANCE) .toExpression(), value), }); List<Statement> elseBlock = Arrays.asList(new Statement[] { dataClass.assign( new ExpressionBuilder(factory, object) .method(ADVANCE) .toExpression(), value), }); return factory.newIfStatement( new ExpressionBuilder(factory, object) .method(IS_EXPAND_REQUIRED) .toExpression(), factory.newBlock(thenBlock), factory.newBlock(elseBlock)); } /** * Returns a statement which finalizes this object. * @return the generated statement * @see ListBuffer#end() */ public Statement createEnd() { return new ExpressionBuilder(factory, object) .method(END) .toStatement(); } /** * Returns a statement which disposes this object. * @return the generated statement * @see ListBuffer#shrink() */ public Statement createShrink() { return new ExpressionBuilder(factory, object) .method(SHRINK) .toStatement(); } } /** * An abstract super interface of repository of {@link FlowElementProcessor}. */ interface Repository extends FlowCompilingEnvironment.Initializable { /** * Returns a processor for the empty operators. * @return the processor */ LinePartProcessor getEmptyProcessor(); /** * Returns a {@link FlowElementProcessor} for processing the target operator. * @param description the target element description * @return the corresponded processor, or {@code null} if there is no available processors * @throws IllegalArgumentException if the parameter is {@code null} */ FlowElementProcessor findProcessor(FlowElementDescription description); /** * Returns a {@link FlowElementProcessor} for processing the target extract-like operator. * @param description the target element description * @return the corresponded processor, or {@code null} if there is no available processors * @throws IllegalArgumentException if the parameter is {@code null} */ LineProcessor findLineProcessor(FlowElementDescription description); /** * Returns a {@link FlowElementProcessor} for processing the target co-group like operator. * @param description the target element description * @return the corresponded processor, or {@code null} if there is no available processors * @throws IllegalArgumentException if the parameter is {@code null} */ RendezvousProcessor findRendezvousProcessor(FlowElementDescription description); } /** * Represents a kind of {@link FlowElementProcessor}. */ enum Kind { /** * {@link LinePartProcessor}. */ LINE_PART, /** * {@link LineEndProcessor}. */ LINE_END, /** * {@link RendezvousProcessor}. */ RENDEZVOUS, } }