/*********************************************************************
Copyright 2014 the Flapi authors
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 unquietcode.tools.flapi.graph.processors;
import com.sun.codemodel.*;
import unquietcode.tools.flapi.Constants;
import unquietcode.tools.flapi.generator.AbstractGenerator;
import unquietcode.tools.flapi.generator.GeneratorContext;
import unquietcode.tools.flapi.graph.GenericVisitor;
import unquietcode.tools.flapi.graph.TransitionVisitor;
import unquietcode.tools.flapi.graph.components.LateralTransition;
import unquietcode.tools.flapi.graph.components.StateClass;
import unquietcode.tools.flapi.graph.components.TerminalTransition;
import unquietcode.tools.flapi.graph.components.Transition;
import unquietcode.tools.flapi.java.JavaType;
import unquietcode.tools.flapi.runtime.ChainInfo;
import unquietcode.tools.flapi.runtime.MethodInfo;
import unquietcode.tools.flapi.runtime.Tracked;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
public class GraphProcessor extends AbstractGenerator implements GenericVisitor<StateClass> {
private Set<StateClass> seen = Collections.newSetFromMap(new IdentityHashMap<StateClass, Boolean>());
public GraphProcessor(GeneratorContext context) {
super(context);
}
public JCodeModel generate(StateClass startingClass) {
visit(startingClass);
return ctx.model;
}
@Override
public void visit(StateClass state) {
if (seen.contains(state)) {
return;
} else {
seen.add(state);
}
// create the interface class
JDefinedClass iBuilder = BUILDER_INTERFACE_STRATEGY.createStrongType(ctx, state);
for (Transition transition : state.getTransitions()) {
// add methods to interface
ReturnTypeProcessor rt = new ReturnTypeProcessor(ctx);
JType returnType = rt.computeReturnType(transition);
JMethod _method = addMethod(iBuilder, returnType, JMod.NONE, transition);
final JAnnotationUse infoAnnotation = _method.annotate(MethodInfo.class);
infoAnnotation.param("type", transition.getType());
transition.accept(new TransitionVisitor.$() {
public void visit(LateralTransition transition) {
if (!transition.getStateChain().isEmpty()) {
JClass next = BUILDER_OR_MARKER_INTERFACE_STRATEGY.createWeakType(ctx, transition.getSibling());
infoAnnotation.param("next", next);
}
}
});
// store the type information for the state chain
if (!transition.getStateChain().isEmpty()) {
final List<Integer> chainParameterPositions = transition.getChainParameterPositions();
final Function<Integer, Integer> positionFunction;
// if no specific parameters, start after the last regular parameter
if (transition.getChainParameterPositions().isEmpty()) {
final int offset = transition.getMethodSignature().parameterCount();
positionFunction = (Integer idx) -> {
return offset + idx;
};
// otherwise, use the provided position
} else {
positionFunction = (Integer idx) -> {
return chainParameterPositions.get(idx);
};
}
List<StateClass> stateChain = transition.getStateChain();
JAnnotationArrayMember chain = infoAnnotation.paramArray("chainInfo");
for (int i=0; i < stateChain.size(); i++) {
StateClass sc = stateChain.get(i);
JClass type = BUILDER_OR_MARKER_INTERFACE_STRATEGY.createWeakType(ctx, sc);
final int position = positionFunction.apply(i);
JAnnotationUse chainInfo = chain.annotate(ChainInfo.class);
chainInfo.param("type", type);
chainInfo.param("position", position);
}
}
// if it's an atLeast method, requiring tracking
if (transition.info().getMinOccurrences() > 0) {
String key = transition.info().keyString();
key = key.substring(0, key.length()-2); // removes the t/f triggered portion
_method.annotate(Tracked.class)
.param("atLeast", transition.info().getMinOccurrences())
.param("key", key)
;
}
// add the helper method to helper interface,
// but only if there isn't an existing helper
if (transition.getOwner().getHelperClass() == null) {
addHelperCall(transition);
}
// continue to the next states
transition.acceptForTraversal(this);
}
}
private void addHelperCall(Transition transition) {
if (ctx.helperMethods.seen(transition)) { return; }
// get a return value if present
final AtomicReference<JType> helperReturnType = new AtomicReference<JType>();
transition.accept(new TransitionVisitor.$() {
public @Override void visit(TerminalTransition transition) {
JavaType type = transition.getReturnType() == null || transition.info().isImplicit()
? JavaType.from(Void.class)
: transition.getReturnType();
// Set the return type unless it is (V)oid, in which case as a convenience we
// set the return to (v)oid (done elsewhere), which is also done on the
// *Helper interfaces.
if (!type.isVoid()) {
helperReturnType.set(getType(type));
}
}
});
// add the helper method to the helper interface
JType helperReturnType1 = helperReturnType.get();
JDefinedClass iHelper = HELPER_INTERFACE_STRATEGY.createStrongType(ctx, transition.getOwner());
JType methodCallType = helperReturnType1 == null ? ctx.model.VOID : helperReturnType1;
JMethod _method = addHelperMethod(iHelper, methodCallType, JMod.NONE, transition);
for (int i=0; i < transition.getStateChain().size(); ++i) {
JClass type = HELPER_INTERFACE_STRATEGY.createWeakType(ctx, transition.getStateChain().get(i));
_method.param(ref(AtomicReference.class).narrow(type), Constants.HELPER_VALUE_NAME+(i+1));
}
}
}