/**
* 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.reflect.Constructor;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.asakusafw.vocabulary.flow.FlowDescription;
import com.asakusafw.vocabulary.flow.graph.FlowElementDescription;
import com.asakusafw.vocabulary.flow.graph.FlowIn;
import com.asakusafw.vocabulary.flow.graph.FlowOut;
import com.asakusafw.vocabulary.flow.graph.FlowPartDescription;
/**
* Builds fragment graph node internally.
* @since 0.9.0
*/
public class FlowNodeBuilder extends FlowElementBuilder {
private static final Object INVALID_ARGUMENT = new Object() {
@Override
public String toString() {
return "INVALID"; //$NON-NLS-1$
}
};
private final Constructor<? extends FlowDescription> constructor;
/**
* Creates a new instance for operator method.
* @param flowClass flow class
* @param parameterTypes flow-part constructor parameter types
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public FlowNodeBuilder(Class<? extends FlowDescription> flowClass, Class<?>... parameterTypes) {
try {
this.constructor = flowClass.getConstructor(parameterTypes);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(MessageFormat.format(
"Failed to detect flow description constructor (class={0}, parameters={1})",
flowClass.getName(),
Arrays.toString(parameterTypes)), e);
}
}
/**
* Returns the target constructor.
* @return the constructor
*/
public Constructor<? extends FlowDescription> getConstructor() {
return constructor;
}
@Override
protected FlowElementDescription build(
List<PortInfo> inputPorts,
List<PortInfo> outputPorts,
List<DataInfo> arguments,
List<AttributeInfo> attributes) {
FlowPartDescription.Builder builder = new FlowPartDescription.Builder(constructor.getDeclaringClass());
Map<String, FlowIn<?>> inputMap = new LinkedHashMap<>();
Map<String, FlowOut<?>> outputMap = new LinkedHashMap<>();
Map<String, Object> dataMap = new LinkedHashMap<>();
for (PortInfo info : inputPorts) {
if (info.getKey() != null) {
throw new IllegalArgumentException(MessageFormat.format(
"flow-part cannot accept shuffle key: {0}({1})",
constructor.getDeclaringClass().getName(),
info.getName()));
}
if (info.getExtern() != null) {
throw new IllegalArgumentException(MessageFormat.format(
"flow-part cannot accept external input: {0}({1})",
constructor.getDeclaringClass().getName(),
info.getName()));
}
FlowIn<?> port = builder.addInput(info.getName(), info.getType());
inputMap.put(info.getName(), port);
}
for (PortInfo info : outputPorts) {
if (info.getKey() != null) {
throw new IllegalArgumentException(MessageFormat.format(
"flow-part cannot accept shuffle key: {0}({1})",
constructor.getDeclaringClass().getName(),
info.getName()));
}
if (info.getExtern() != null) {
throw new IllegalArgumentException(MessageFormat.format(
"flow-part cannot accept external output: {0}({1})",
constructor.getDeclaringClass().getName(),
info.getName()));
}
FlowOut<?> port = builder.addOutput(info.getName(), info.getType());
outputMap.put(info.getName(), port);
}
for (DataInfo info : arguments) {
Data data = info.getData();
Object value;
switch (data.getKind()) {
case CONSTANT: {
Constant c = (Constant) data;
value = c.getValue();
builder.addParameter(info.getName(), c.getType(), value);
break;
}
default:
throw new AssertionError(data);
}
dataMap.put(info.getName(), value);
}
if (attributes.isEmpty() == false) {
throw new IllegalArgumentException(MessageFormat.format(
"flow-part cannot accept attributes: {0}({1})",
constructor.getDeclaringClass().getName(),
attributes));
}
Object[] constructorArguments = computeConstructoArguments(
inputPorts, outputPorts, arguments,
inputMap, outputMap, dataMap);
try {
FlowDescription instance = constructor.newInstance(constructorArguments);
instance.start();
} catch (Exception e) {
throw new IllegalStateException(MessageFormat.format(
"error occurred while processing flow-part: {0}",
constructor.getDeclaringClass().getName()), e);
}
return builder.toDescription();
}
private Object[] computeConstructoArguments(
List<PortInfo> inputPorts,
List<PortInfo> outputPorts,
List<DataInfo> arguments,
Map<String, FlowIn<?>> inputMap,
Map<String, FlowOut<?>> outputMap,
Map<String, Object> dataMap) {
Object[] results = new Object[constructor.getParameterTypes().length];
Arrays.fill(results, INVALID_ARGUMENT);
for (PortInfo info : inputPorts) {
put("input", results, info, inputMap);
}
for (PortInfo info : outputPorts) {
put("output", results, info, outputMap);
}
for (DataInfo info : arguments) {
put("data", results, info, dataMap);
}
for (int index = 0; index < results.length; index++) {
if (results[index] == INVALID_ARGUMENT) {
throw new IllegalStateException(MessageFormat.format(
"flow-part constructor argument is not completed: {0}(@{1})",
constructor.getDeclaringClass().getName(),
index));
}
}
return results;
}
private void put(String label, Object[] results, EdgeInfo<?> info, Map<String, ?> valueMap) {
Integer index = info.getParameterIndex();
if (index == null) {
throw new IllegalArgumentException(MessageFormat.format(
"flow-part {0} must have parameter index information: {1}({2})",
label,
constructor.getDeclaringClass().getName(),
info.getName()));
}
if (results[index] != INVALID_ARGUMENT) {
throw new IllegalStateException(MessageFormat.format(
"flow-part constructor argument is duplicated: {0}(@{1}={2})",
constructor.getDeclaringClass().getName(),
index,
info.getName()));
}
if (valueMap.containsKey(info.getName()) == false) {
throw new IllegalStateException(MessageFormat.format(
"flow-part constructor argument is unknown: {0}(@{1}={2})",
constructor.getDeclaringClass().getName(),
index,
info.getName()));
}
Object value = valueMap.get(info.getName());
results[index] = value;
}
}