/**
* 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.vocabulary.flow.graph;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import com.asakusafw.vocabulary.flow.Source;
/**
* A description of user/code operator.
* @since 0.1.0
* @version 0.9.1
*/
public class OperatorDescription implements FlowElementDescription {
private final FlowElementDescription origin;
private final Declaration declaration;
private final List<FlowElementPortDescription> inputPorts;
private final List<FlowElementPortDescription> outputPorts;
private final List<FlowResourceDescription> resources;
private final List<Parameter> parameters;
private final Map<Class<? extends FlowElementAttribute>, FlowElementAttribute> attributes;
private String name;
/**
* Creates a new instance.
* @param declaration information of the operator declaration
* @param inputPorts information of the input ports
* @param outputPorts information of the output ports
* @param resources information of the external resources
* @param parameters information of the user parameters
* @param attributes the attributes
* @throws IllegalArgumentException if some parameters are {@code null}
*/
public OperatorDescription(
Declaration declaration,
List<FlowElementPortDescription> inputPorts,
List<FlowElementPortDescription> outputPorts,
List<FlowResourceDescription> resources,
List<Parameter> parameters,
List<FlowElementAttribute> attributes) {
this(null, declaration, inputPorts, outputPorts, resources, parameters, attributes);
}
/**
* Creates a new instance.
* @param origin the original description (nullable)
* @param declaration information of the operator declaration
* @param inputPorts information of the input ports
* @param outputPorts information of the output ports
* @param resources information of the external resources
* @param parameters information of the user parameters
* @param attributes the attributes
* @throws IllegalArgumentException if some parameters are {@code null}
* @since 0.5.1
*/
public OperatorDescription(
FlowElementDescription origin,
Declaration declaration,
List<FlowElementPortDescription> inputPorts,
List<FlowElementPortDescription> outputPorts,
List<FlowResourceDescription> resources,
List<Parameter> parameters,
List<FlowElementAttribute> attributes) {
if (declaration == null) {
throw new IllegalArgumentException("declaration must not be null"); //$NON-NLS-1$
}
if (inputPorts == null) {
throw new IllegalArgumentException("inputPorts must not be null"); //$NON-NLS-1$
}
if (outputPorts == null) {
throw new IllegalArgumentException("outputPorts must not be null"); //$NON-NLS-1$
}
if (resources == null) {
throw new IllegalArgumentException("resources must not be null"); //$NON-NLS-1$
}
if (parameters == null) {
throw new IllegalArgumentException("parameters must not be null"); //$NON-NLS-1$
}
if (attributes == null) {
throw new IllegalArgumentException("attributes must not be null"); //$NON-NLS-1$
}
this.origin = origin == null ? this : origin;
this.declaration = declaration;
this.inputPorts = Collections.unmodifiableList(new ArrayList<>(inputPorts));
this.outputPorts = Collections.unmodifiableList(new ArrayList<>(outputPorts));
this.resources = Collections.unmodifiableList(new ArrayList<>(resources));
this.parameters = Collections.unmodifiableList(new ArrayList<>(parameters));
this.attributes = new HashMap<>();
for (FlowElementAttribute attribute : attributes) {
this.attributes.put(attribute.getDeclaringClass(), attribute);
}
}
@Override
public FlowElementKind getKind() {
return FlowElementKind.OPERATOR;
}
@Override
public FlowElementDescription getOrigin() {
return origin;
}
/**
* Returns information of the operator declaration.
* @return information of the operator declaration
*/
public Declaration getDeclaration() {
return declaration;
}
@Override
public String getName() {
if (name == null) {
return MessageFormat.format(
"{0}.{1}", //$NON-NLS-1$
declaration.getDeclaring().getSimpleName(),
declaration.getName());
}
return name;
}
@Override
public void setName(String name) {
if (name == null) {
throw new IllegalArgumentException("name must not be null"); //$NON-NLS-1$
}
this.name = name;
}
@Override
public List<FlowElementPortDescription> getInputPorts() {
return inputPorts;
}
@Override
public List<FlowElementPortDescription> getOutputPorts() {
return outputPorts;
}
@Override
public List<FlowResourceDescription> getResources() {
return resources;
}
/**
*
* Returns information of the user parameters.
* @return information of the user parameters
*/
public List<Parameter> getParameters() {
return parameters;
}
@Override
public Set<? extends Class<? extends FlowElementAttribute>> getAttributeTypes() {
return attributes.keySet();
}
@Override
public <T extends FlowElementAttribute> T getAttribute(Class<T> attributeClass) {
if (attributeClass == null) {
throw new IllegalArgumentException("attributeClass must not be null"); //$NON-NLS-1$
}
Object attribute = attributes.get(attributeClass);
return attributeClass.cast(attribute);
}
/**
* Returns the attributes of the operator.
* @return the attributes
*/
public Set<FlowElementAttribute> getAttributes() {
return new HashSet<>(attributes.values());
}
@Override
public String toString() {
return MessageFormat.format(
"{0}#{1}(@{2})", //$NON-NLS-1$
getDeclaration().getDeclaring().getSimpleName(),
getDeclaration().getName(),
getDeclaration().getAnnotationType().getSimpleName());
}
/**
* Represents a declaration of operator.
*/
public static class Declaration {
private final Class<? extends Annotation> annotationType;
private final Class<?> declaring;
private final Class<?> implementing;
private final String name;
private final List<Class<?>> parameterTypes;
/**
* Creates a new instance.
* @param annotationType the operator annotation type
* @param declaring the declaring class (a.k.a. operator class)
* @param implementing the implementation class
* @param name the operator method name
* @param parameterTypes the erased parameter types of the operator method
* @throws IllegalArgumentException if some parameters are {@code null}
*/
public Declaration(
Class<? extends Annotation> annotationType,
Class<?> declaring,
Class<?> implementing,
String name,
List<Class<?>> parameterTypes) {
if (annotationType == null) {
throw new IllegalArgumentException("annotationType must not be null"); //$NON-NLS-1$
}
if (declaring == null) {
throw new IllegalArgumentException("declaring must not be null"); //$NON-NLS-1$
}
if (implementing == null) {
throw new IllegalArgumentException("implementing must not be null"); //$NON-NLS-1$
}
if (name == null) {
throw new IllegalArgumentException("name must not be null"); //$NON-NLS-1$
}
if (parameterTypes == null) {
throw new IllegalArgumentException("parameterTypes must not be null"); //$NON-NLS-1$
}
this.annotationType = annotationType;
this.declaring = declaring;
this.implementing = implementing;
this.name = name;
this.parameterTypes = parameterTypes;
}
/**
* Returns the operator annotation type.
* @return the operator annotation type
*/
public Class<? extends Annotation> getAnnotationType() {
return annotationType;
}
/**
* Returns the declaring class.
* @return the declaring class
*/
public Class<?> getDeclaring() {
return declaring;
}
/**
* Returns the implementation class.
* @return the implementation class
*/
public Class<?> getImplementing() {
return implementing;
}
/**
* Returns the operator method name.
* @return the operator method name
*/
public String getName() {
return name;
}
/**
* Returns the erased parameter types of the operator method.
* @return the parameter types
*/
public List<Class<?>> getParameterTypes() {
return parameterTypes;
}
/**
* Returns the reflective object of the target operator method.
* @return the reflective object, or {@code null} if there is no such a corresponding method
*/
public Method toMethod() {
Class<?>[] params = parameterTypes.toArray(new Class<?>[parameterTypes.size()]);
try {
return declaring.getMethod(name, params);
} catch (Exception e) {
return null;
}
}
@Override
public String toString() {
return MessageFormat.format(
"{0}#{1}({2})", //$NON-NLS-1$
declaring.getName(),
name,
parameterTypes);
}
}
/**
* Represents a user parameter.
*/
public static class Parameter {
private final String name;
private final Type type;
private final Object value;
/**
* Creates a new instance.
* @param name the parameter name
* @param type the parameter type
* @param value the parameter value ({@code nullable})
* @throws IllegalArgumentException if some parameters are {@code null}
*/
public Parameter(String name, Type type, Object value) {
if (name == null) {
throw new IllegalArgumentException("name must not be null"); //$NON-NLS-1$
}
if (type == null) {
throw new IllegalArgumentException("type must not be null"); //$NON-NLS-1$
}
this.name = name;
this.type = type;
this.value = value;
}
/**
* Returns the parameter name.
* @return the parameter name
*/
public String getName() {
return name;
}
/**
* Returns the parameter type.
* @return the parameter type
*/
public Type getType() {
return type;
}
/**
* Returns the parameter value.
* @return the parameter value, or {@code null} if the target value is just {@code null}
*/
public Object getValue() {
return value;
}
@Override
public String toString() {
return MessageFormat.format(
"{0}[{1}]={2}", //$NON-NLS-1$
getName(),
getType(),
getValue());
}
}
/**
* A builder for building {@link OperatorDescription}.
* @since 0.1.0
* @version 0.5.1
*/
public static class Builder {
private FlowElementDescription origin;
private final Class<? extends Annotation> annotationType;
private Class<?> declaring;
private Class<?> implementing;
private String name;
private final List<Class<?>> parameterTypes;
private final List<FlowElementPortDescription> inputPorts = new ArrayList<>();
private final List<FlowElementPortDescription> outputPorts = new ArrayList<>();
private final List<FlowResourceDescription> resources = new ArrayList<>();
private final List<Parameter> parameters = new ArrayList<>();
private final List<FlowElementAttribute> attributes = new ArrayList<>();
/**
* Creates a new instance.
* @param annotationType the operator annotation type
* @throws IllegalArgumentException if the parameter is {@code null}
*/
public Builder(Class<? extends Annotation> annotationType) {
if (annotationType == null) {
throw new IllegalArgumentException("annotationType must not be null"); //$NON-NLS-1$
}
this.annotationType = annotationType;
this.parameterTypes = new ArrayList<>();
}
/**
* Sets information of operator method declaration.
* Note that, clients should use {@link #declareParameter(Class)} to add method parameters.
* @param operatorClass the operator class
* @param implementorClass the implementation class
* @param methodName the operator method name
* @return this
* @throws IllegalArgumentException if some parameters are {@code null}
*/
public Builder declare(Class<?> operatorClass, Class<?> implementorClass, String methodName) {
if (operatorClass == null) {
throw new IllegalArgumentException("operatorClass must not be null"); //$NON-NLS-1$
}
if (implementorClass == null) {
throw new IllegalArgumentException("implementorClass must not be null"); //$NON-NLS-1$
}
if (methodName == null) {
throw new IllegalArgumentException("methodName must not be null"); //$NON-NLS-1$
}
if (this.declaring != null) {
throw new IllegalStateException();
}
this.declaring = operatorClass;
this.implementing = implementorClass;
this.name = methodName;
return this;
}
/**
* Sets the original description for the building description.
* @param origin the original description, or {@code null} if the building one is the origin
*/
public void setOrigin(FlowElementDescription origin) {
this.origin = origin;
}
/**
* Adds a parameter of the target operator method.
* @param parameterType the erased parameter type
* @return this
* @throws IllegalArgumentException if the parameter is {@code null}
*/
public Builder declareParameter(Class<?> parameterType) {
if (parameterType == null) {
throw new IllegalArgumentException("parameterType must not be null"); //$NON-NLS-1$
}
this.parameterTypes.add(parameterType);
return this;
}
/**
* Adds a new port of the target operator.
* @param description the port description
* @return this
* @since 0.9.1
*/
public Builder addPort(FlowElementPortDescription description) {
Objects.requireNonNull(description);
switch (description.getDirection()) {
case INPUT:
inputPorts.add(description);
break;
case OUTPUT:
outputPorts.add(description);
break;
default:
throw new AssertionError(description);
}
return this;
}
/**
* Adds a new input port of the target operator.
* @param portName the port name
* @param dataType the data type
* @return this
* @throws IllegalArgumentException if some parameters are {@code null}
*/
public Builder addInput(String portName, Type dataType) {
if (portName == null) {
throw new IllegalArgumentException("portName must not be null"); //$NON-NLS-1$
}
if (dataType == null) {
throw new IllegalArgumentException("dataType must not be null"); //$NON-NLS-1$
}
inputPorts.add(new FlowElementPortDescription(
portName,
dataType,
PortDirection.INPUT));
return this;
}
/**
* Adds a new input port of the target operator.
* @param portName the port name
* @param typeReference a source that has the data type as same to the creating port
* @return this
* @throws IllegalArgumentException if some parameters are {@code null}
*/
public Builder addInput(String portName, Source<?> typeReference) {
if (portName == null) {
throw new IllegalArgumentException("portName must not be null"); //$NON-NLS-1$
}
if (typeReference == null) {
throw new IllegalArgumentException("typeReference must not be null"); //$NON-NLS-1$
}
return addInput(
portName,
typeReference.toOutputPort().getDescription().getDataType());
}
/**
* Adds a new input port of the target operator.
* @param portName the port name
* @param dataType the data type
* @param key information of the shuffle operation
* @return this
* @throws IllegalArgumentException if some parameters are {@code null}
*/
public Builder addInput(String portName, Type dataType, ShuffleKey key) {
if (portName == null) {
throw new IllegalArgumentException("portName must not be null"); //$NON-NLS-1$
}
if (dataType == null) {
throw new IllegalArgumentException("typeReference must not be null"); //$NON-NLS-1$
}
if (key == null) {
throw new IllegalArgumentException("key must not be null"); //$NON-NLS-1$
}
inputPorts.add(new FlowElementPortDescription(
portName,
dataType,
key));
return this;
}
/**
* Adds a new input port of the target operator.
* @param portName the port name
* @param typeReference a source that has the data type as same to the creating port
* @param key information of the shuffle operation
* @return this
* @throws IllegalArgumentException if some parameters are {@code null}
*/
public Builder addInput(String portName, Source<?> typeReference, ShuffleKey key) {
if (portName == null) {
throw new IllegalArgumentException("portName must not be null"); //$NON-NLS-1$
}
if (typeReference == null) {
throw new IllegalArgumentException("typeReference must not be null"); //$NON-NLS-1$
}
if (key == null) {
throw new IllegalArgumentException("key must not be null"); //$NON-NLS-1$
}
return addInput(portName, typeReference.toOutputPort().getDescription().getDataType(), key);
}
/**
* Adds a new output port of the target operator.
* @param portName the port name
* @param dataType the data type
* @return this
* @throws IllegalArgumentException if some parameters are {@code null}
*/
public Builder addOutput(String portName, Type dataType) {
if (portName == null) {
throw new IllegalArgumentException("portName must not be null"); //$NON-NLS-1$
}
if (dataType == null) {
throw new IllegalArgumentException("dataType must not be null"); //$NON-NLS-1$
}
outputPorts.add(new FlowElementPortDescription(
portName,
dataType,
PortDirection.OUTPUT));
return this;
}
/**
* Adds a new output port of the target operator.
* @param portName the port name
* @param typeReference a source that has the data type as same to the creating port
* @return this
* @throws IllegalArgumentException if some parameters are {@code null}
*/
public Builder addOutput(String portName, Source<?> typeReference) {
if (portName == null) {
throw new IllegalArgumentException("portName must not be null"); //$NON-NLS-1$
}
if (typeReference == null) {
throw new IllegalArgumentException("typeReference must not be null"); //$NON-NLS-1$
}
return addOutput(portName, typeReference.toOutputPort().getDescription().getDataType());
}
/**
* Adds a new external resource.
* @param resource the external resource
* @return this
* @throws IllegalArgumentException if the parameter is {@code null}
*/
public Builder addResource(FlowResourceDescription resource) {
if (resource == null) {
throw new IllegalArgumentException("resource must not be null"); //$NON-NLS-1$
}
resources.add(resource);
return this;
}
/**
* Adds a new user parameter.
* @param parameterName the parameter name
* @param parameterType the parameter value
* @param argument the actual parameter argument, or {@code null} if the argument is just {@code null}
* @return this
* @throws IllegalArgumentException if some parameters are {@code null}
*/
public Builder addParameter(String parameterName, Type parameterType, Object argument) {
if (parameterName == null) {
throw new IllegalArgumentException("parameterName must not be null"); //$NON-NLS-1$
}
if (parameterType == null) {
throw new IllegalArgumentException("parameterType must not be null"); //$NON-NLS-1$
}
parameters.add(new Parameter(parameterName, parameterType, argument));
return this;
}
/**
* Adds an attribute.
* @param attribute the attribute
* @return this
* @throws IllegalArgumentException if the parameter is {@code null}
*/
public Builder addAttribute(FlowElementAttribute attribute) {
if (attribute == null) {
throw new IllegalArgumentException("attribute must not be null"); //$NON-NLS-1$
}
attributes.add(attribute);
return this;
}
/**
* Creates a new description object from the previously information.
* @return the created description
*/
public OperatorDescription toDescription() {
return new OperatorDescription(
origin,
new Declaration(
annotationType,
declaring,
implementing,
name,
parameterTypes),
inputPorts,
outputPorts,
resources,
parameters,
attributes);
}
/**
* Creates a new {@link FlowElementResolver} object from the previously information.
* @return the created object
*/
public FlowElementResolver toResolver() {
return new FlowElementResolver(toDescription());
}
}
}