/**
* 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.builder;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import com.asakusafw.vocabulary.flow.graph.FlowElementAttribute;
import com.asakusafw.vocabulary.flow.graph.FlowElementDescription;
import com.asakusafw.vocabulary.flow.graph.FlowElementPortDescription;
import com.asakusafw.vocabulary.flow.graph.OperatorDescription;
import com.asakusafw.vocabulary.flow.graph.PortDirection;
/**
* Builds operator internally.
* @since 0.9.0
*/
public class OperatorNodeBuilder extends FlowElementBuilder {
private final Class<? extends Annotation> annotationType;
private final Class<?> implementationClass;
private final Method method;
/**
* Creates a new instance for operator method.
* @param annotationType operator annotation type.
* @param operatorClass operator class
* @param implementationClass operator implementation class
* @param methodName operator method name
* @param methodParameterTypes operator method parameter types (erasure)
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public OperatorNodeBuilder(
Class<? extends Annotation> annotationType,
Class<?> operatorClass,
Class<?> implementationClass,
String methodName,
Class<?>... methodParameterTypes) {
Objects.requireNonNull(annotationType, "annotationType must not be null"); //$NON-NLS-1$
Objects.requireNonNull(operatorClass, "operatorClass must not be null"); //$NON-NLS-1$
Objects.requireNonNull(implementationClass, "implementationClass must not be null"); //$NON-NLS-1$
Objects.requireNonNull(methodName, "methodName must not be null"); //$NON-NLS-1$
Objects.requireNonNull(methodParameterTypes, "methodParameterTypes must not be null"); //$NON-NLS-1$
if (operatorClass.isAssignableFrom(implementationClass) == false) {
throw new IllegalArgumentException(MessageFormat.format(
"implementationClass ({0}) must be subclass of operatorClass ({1})",
implementationClass.getName(),
operatorClass.getName()));
}
this.annotationType = annotationType;
this.implementationClass = implementationClass;
try {
this.method = operatorClass.getMethod(methodName, methodParameterTypes);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(MessageFormat.format(
"Failed to detect operator method (class={0}, name={1}, parameters={2})",
operatorClass.getName(),
methodName,
Arrays.toString(methodParameterTypes)), e);
}
}
@Override
protected FlowElementDescription build(
List<PortInfo> inputPorts,
List<PortInfo> outputPorts,
List<DataInfo> arguments,
List<AttributeInfo> attributes) {
OperatorDescription.Builder builder = new OperatorDescription.Builder(annotationType);
builder.declare(method.getDeclaringClass(), implementationClass, method.getName());
for (Class<?> type : method.getParameterTypes()) {
builder.declareParameter(type);
}
for (PortInfo info : inputPorts) {
if (info.getExtern() != null) {
throw new IllegalArgumentException(MessageFormat.format(
"operator cannot accept external input: {0}#{1}({2})",
method.getDeclaringClass().getName(),
method.getName(),
info.getName()));
}
builder.addPort(new FlowElementPortDescription(
info.getName(),
info.getType(),
PortDirection.INPUT,
info.getKey() == null ? null : info.getKey().toShuffleKey(),
info.getAttributes()));
}
for (PortInfo info : outputPorts) {
if (info.getKey() != null) {
throw new IllegalArgumentException(MessageFormat.format(
"operator cannot accept shuffle key: {0}#{1}({2})",
method.getDeclaringClass().getName(),
method.getName(),
info.getName()));
}
if (info.getExtern() != null) {
throw new IllegalArgumentException(MessageFormat.format(
"operator cannot accept external output: {0}#{1}({2})",
method.getDeclaringClass().getName(),
method.getName(),
info.getName()));
}
builder.addPort(new FlowElementPortDescription(
info.getName(),
info.getType(),
PortDirection.OUTPUT,
null,
info.getAttributes()));
}
for (DataInfo info : arguments) {
Data data = info.getData();
switch (data.getKind()) {
case CONSTANT: {
Constant c = (Constant) data;
builder.addParameter(info.getName(), c.getType(), c.getValue());
break;
}
default:
throw new AssertionError(data);
}
}
for (AttributeInfo attribute : attributes) {
builder.addAttribute(attribute.getAdapter(FlowElementAttribute.class));
}
return builder.toDescription();
}
}