/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.sesame.graph;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
import com.opengamma.sesame.cache.Cacheable;
import com.opengamma.sesame.config.EngineUtils;
import com.opengamma.sesame.engine.ComponentMap;
import com.opengamma.sesame.engine.FunctionService;
import com.opengamma.util.ArgumentChecker;
/**
* Builds function objects from the {@link FunctionModelNode} instances representing them in the function model.
*/
public final class FunctionBuilder implements FunctionIdProvider {
private static final Logger s_logger = LoggerFactory.getLogger(FunctionBuilder.class);
/** Produces IDs for functions that are unique across the system. */
private static final AtomicInteger s_functionIds = new AtomicInteger();
/** Whether identical nodes should share IDs and therefore share cache values. */
private final boolean _enableCacheSharing;
/**
* Map of function identities to function IDs.
* <p>
* The keys are the identity hash codes of the function instances and therefore identify a specific
* function instance. The values are generated function IDs. The idea is that two functions with
* identical nodes are logically identical and will share an ID. That ID is used in the cache key so
* identical functions running in different views can share data.
*/
private final Map<Integer, FunctionId> _functionsToIds = new ConcurrentHashMap<>();
/**
* Map of model nodes to function IDs.
* <p>
* When a new function is created this map is checked to see if a function has previously been created from an
* identical node. If it has then the two functions are identical and can share values in the cache.
* They are allocated the same ID so their cache keys are equal for the same set of arguments.
*/
private final Map<FunctionModelNode, FunctionId> _nodesToFunctionIds = new HashMap<>();
/**
* Creates a new function builder which builds functions that can share cached values between views.
* <p>
* For caching to work the service {@link FunctionService#CACHING} must also be included in the set of services
* when creating the view.
*/
public FunctionBuilder() {
_enableCacheSharing = true;
}
/**
* Creates a new function builder.
*
* @param enableCacheSharing whether identical nodes should share IDs and therefore share cache values between
* different views. For caching to work the service {@link FunctionService#CACHING} must also be
* included in the set of services when creating the view.
*/
public FunctionBuilder(boolean enableCacheSharing) {
_enableCacheSharing = enableCacheSharing;
}
synchronized Object create(FunctionModelNode node, ComponentMap componentMap) {
checkValid(node);
// TODO detect cycles in the graph
// TODO cache this info if it proves expensive to do it over and over for the same classes
boolean cacheable =
node instanceof InterfaceNode &&
(EngineUtils.hasMethodAnnotation(((InterfaceNode) node).getImplementationType(), Cacheable.class) ||
(EngineUtils.hasMethodAnnotation(((InterfaceNode) node).getType(), Cacheable.class)));
List<Object> dependencies = Lists.newArrayListWithCapacity(node.getDependencies().size());
for (FunctionModelNode dependentNode : node.getDependencies()) {
dependencies.add(create(dependentNode, componentMap));
}
Object function = node.create(componentMap, dependencies, this);
if (cacheable) {
FunctionId existingFunctionId = _nodesToFunctionIds.get(node);
if (_enableCacheSharing && existingFunctionId != null) {
s_logger.debug("Using existing function ID {} for node {}", existingFunctionId, node.prettyPrint(false));
_functionsToIds.put(System.identityHashCode(function), existingFunctionId);
} else {
FunctionId newFunctionId = FunctionId.of(s_functionIds.getAndIncrement());
s_logger.debug("Creating new function ID {} for node {}", newFunctionId, node.prettyPrint(false));
_functionsToIds.put(System.identityHashCode(function), newFunctionId);
_nodesToFunctionIds.put(node, newFunctionId);
}
}
return function;
}
private static void checkValid(FunctionModelNode node) {
if (!node.isValid()) {
throw new GraphBuildException("Can't build functions from an invalid graph\n" + node.prettyPrint(false) + "\n",
node.getExceptions());
}
}
@Override
public FunctionId getFunctionId(Object obj) {
ArgumentChecker.notNull(obj, "obj");
FunctionId id = _functionsToIds.get(System.identityHashCode(obj));
if (id == null) {
throw new IllegalArgumentException("No ID for function " + obj);
}
return id;
}
}