/**
* Copyright 2011-2014 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.trace;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.asakusafw.compiler.common.JavaName;
import com.asakusafw.runtime.trace.TraceDriver;
import com.asakusafw.trace.model.TraceSetting;
import com.asakusafw.trace.model.Tracepoint;
import com.asakusafw.trace.model.TraceSetting.Mode;
import com.asakusafw.trace.model.Tracepoint.PortKind;
import com.asakusafw.vocabulary.flow.graph.Connectivity;
import com.asakusafw.vocabulary.flow.graph.FlowElement;
import com.asakusafw.vocabulary.flow.graph.FlowElementDescription;
import com.asakusafw.vocabulary.flow.graph.FlowElementInput;
import com.asakusafw.vocabulary.flow.graph.FlowElementOutput;
import com.asakusafw.vocabulary.flow.graph.FlowElementPort;
import com.asakusafw.vocabulary.flow.graph.FlowElementResolver;
import com.asakusafw.vocabulary.flow.graph.FlowPartDescription;
import com.asakusafw.vocabulary.flow.graph.ObservationCount;
import com.asakusafw.vocabulary.flow.graph.OperatorDescription;
import com.asakusafw.vocabulary.flow.graph.PortConnection;
import com.asakusafw.vocabulary.operator.Trace;
/**
* Weaves trace operators into tracepoints.
* @since 0.5.1
*/
public class TracepointWeaver {
/**
* The input port name of trace operators.
*/
public static final String INPUT_PORT_NAME = "in";
/**
* The output port name of trace operators.
*/
public static final String OUTPUT_PORT_NAME = "out";
private static final String FLOWPART_FACTORY_NAME = "create";
static final Logger LOG = LoggerFactory.getLogger(TracepointWeaver.class);
private final Map<String, Map<Tracepoint, TraceSetting>> tracepointsByOperatorClass;
private final Map<Tracepoint, TraceSetting> rest;
private final AtomicInteger counter = new AtomicInteger();
/**
* Creates a new instance.
* @param settings target settings
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public TracepointWeaver(Collection<? extends TraceSetting> settings) {
if (settings == null) {
throw new IllegalArgumentException("settings must not be null"); //$NON-NLS-1$
}
HashMap<Tracepoint, TraceSetting> all = new HashMap<Tracepoint, TraceSetting>();
Map<String, Map<Tracepoint, TraceSetting>> map = new HashMap<String, Map<Tracepoint, TraceSetting>>();
for (TraceSetting setting : settings) {
all.put(setting.getTracepoint(), setting);
Map<Tracepoint, TraceSetting> entry = map.get(setting.getTracepoint().getOperatorClassName());
if (entry == null) {
entry = all;
map.put(setting.getTracepoint().getOperatorClassName(), entry);
}
entry.put(setting.getTracepoint(), setting);
}
this.tracepointsByOperatorClass = map;
this.rest = all;
}
/**
* Weaves tracepoints into {@link FlowElement}.
* @param element the target element
* @return {@code true} if the target element is modified, otherwise {@code false}
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public boolean edit(FlowElement element) {
if (element == null) {
throw new IllegalArgumentException("element must not be null"); //$NON-NLS-1$
}
FlowElementDescription description = element.getDescription();
switch (description.getKind()) {
case OPERATOR:
return edit(element, (OperatorDescription) description);
case FLOW_COMPONENT:
return edit(element, (FlowPartDescription) description);
default:
return false;
}
}
private boolean edit(FlowElement element, OperatorDescription description) {
String className = description.getDeclaration().getDeclaring().getName();
Map<Tracepoint, TraceSetting> settings = tracepointsByOperatorClass.get(className);
if (settings == null || settings.isEmpty()) {
return false;
}
return edit(element, settings, className, normalizeMethodName(description.getDeclaration().getName()));
}
private String normalizeMethodName(String name) {
return JavaName.of(name).toMemberName();
}
private boolean edit(FlowElement element, FlowPartDescription description) {
String className = description.getFlowGraph().getDescription().getName();
Map<Tracepoint, TraceSetting> settings = tracepointsByOperatorClass.get(className);
if (settings == null || settings.isEmpty()) {
return false;
}
return edit(element, settings, className, FLOWPART_FACTORY_NAME);
}
private boolean edit(
FlowElement element,
Map<Tracepoint, TraceSetting> settings,
String className, String methodName) {
boolean modified = false;
for (FlowElementInput port : element.getInputPorts()) {
Tracepoint point = new Tracepoint(className, methodName, PortKind.INPUT, port.getDescription().getName());
if (settings.containsKey(point)) {
edit(port, settings.get(point));
rest.remove(point);
modified = true;
}
}
for (FlowElementOutput port : element.getOutputPorts()) {
Tracepoint point = new Tracepoint(className, methodName, PortKind.OUTPUT, port.getDescription().getName());
if (settings.containsKey(point)) {
edit(port, settings.get(point));
rest.remove(point);
modified = true;
}
}
return modified;
}
private void edit(FlowElementInput port, TraceSetting setting) {
LOG.info("Weaving tracepoint ({}): {}", setting.getTracepoint(), port);
Collection<FlowElementOutput> opposites = port.getOpposites();
edit(setting, port, opposites, Collections.singleton(port));
}
private void edit(FlowElementOutput port, TraceSetting setting) {
LOG.info("Weaving tracepoint ({}): {}", setting.getTracepoint(), port);
Collection<FlowElementInput> opposites = port.getOpposites();
edit(setting, port, Collections.singleton(port), opposites);
}
private void edit(
TraceSetting setting,
FlowElementPort port,
Collection<FlowElementOutput> upstreams,
Collection<FlowElementInput> downstreams) {
assert port != null;
assert upstreams != null;
assert downstreams != null;
OperatorDescription.Builder builder = new OperatorDescription.Builder(Trace.class);
builder.declare(TraceDriver.class, TraceDriver.class, "trace");
builder.addInput(INPUT_PORT_NAME, port.getDescription().getDataType());
builder.addOutput(OUTPUT_PORT_NAME, port.getDescription().getDataType());
builder.addAttribute(new TraceSettingAttribute(setting, counter.incrementAndGet()));
builder.addAttribute(Connectivity.OPTIONAL);
if (setting.getMode() == Mode.STRICT) {
builder.addAttribute(ObservationCount.EXACTLY_ONCE);
port.disconnectAll();
FlowElementResolver resolver = builder.toResolver();
FlowElementInput input = resolver.getInput(INPUT_PORT_NAME);
for (FlowElementOutput upstream : upstreams) {
PortConnection.connect(upstream, input);
}
FlowElementOutput output = resolver.getOutput(OUTPUT_PORT_NAME);
for (FlowElementInput downstream : downstreams) {
PortConnection.connect(output, downstream);
}
} else if (setting.getMode() == Mode.IN_ORDER) {
builder.addAttribute(ObservationCount.AT_LEAST_ONCE);
port.disconnectAll();
FlowElementResolver resolver = builder.toResolver();
FlowElementInput input = resolver.getInput(INPUT_PORT_NAME);
for (FlowElementOutput upstream : upstreams) {
PortConnection.connect(upstream, input);
}
FlowElementOutput output = resolver.getOutput(OUTPUT_PORT_NAME);
for (FlowElementInput downstream : downstreams) {
PortConnection.connect(output, downstream);
}
} else if (setting.getMode() == Mode.OUT_OF_ORDER) {
builder.addAttribute(ObservationCount.AT_LEAST_ONCE);
FlowElementResolver resolver = builder.toResolver();
FlowElementInput input = resolver.getInput(INPUT_PORT_NAME);
for (FlowElementOutput upstream : upstreams) {
PortConnection.connect(upstream, input);
}
}
}
}