/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.sesame.trace; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Objects; import org.threeten.bp.Duration; import com.google.common.base.Function; import com.google.common.base.Throwables; import com.google.common.collect.Lists; import com.opengamma.sesame.engine.TraceType; /** * Graph representing the calls made to calculate a single result. */ class CallGraphBuilder { /** * The type of trace to be done. */ private final TraceType _traceType; /** * The invoked method. */ private final Method _method; /** * The method arguments. */ private final Object[] _args; /** * The child call graphs. */ private final List<CallGraphBuilder> _callGraphBuilders = new ArrayList<>(); /** * The return value. */ private Object _returnValue; /** * Execution time of the call. */ private Duration _duration; /** * The throwable. */ private Throwable _throwable; /** * Creates an instance. * @param method the invoked method, not null * @param args the method arguments, not null */ CallGraphBuilder(TraceType traceType, Method method, Object... args) { _traceType = traceType; _method = method; _args = args; } //------------------------------------------------------------------------- void called(CallGraphBuilder callGraphBuilder) { _callGraphBuilders.add(callGraphBuilder); } void returned(Object returnValue, Duration duration) { _returnValue = returnValue; _duration = duration; } void threw(Throwable throwable, Duration duration) { _throwable = throwable; _duration = duration; } List<CallGraphBuilder> calls() { return _callGraphBuilders; } public CallGraph createTrace() { return createTrace(this, _traceType); } private static CallGraph createTrace(CallGraphBuilder callGraphBuilder, TraceType traceType) { List<CallGraph> calls = Lists.newArrayListWithCapacity(callGraphBuilder._callGraphBuilders.size()); for (CallGraphBuilder childCallGraphBuilder : callGraphBuilder._callGraphBuilders) { calls.add(createTrace(childCallGraphBuilder, traceType)); } List<Object> args = callGraphBuilder._args == null ? Collections.emptyList() : Arrays.asList(callGraphBuilder._args); Class<?> throwableClass; String errorMessage; String stackTrace; if (callGraphBuilder._throwable == null || traceType == TraceType.TIMINGS_ONLY) { throwableClass = null; errorMessage = null; stackTrace = null; } else { throwableClass = callGraphBuilder._throwable.getClass(); errorMessage = callGraphBuilder._throwable.getMessage(); stackTrace = Throwables.getStackTraceAsString(callGraphBuilder._throwable); } return new CallGraph(callGraphBuilder._method.getDeclaringClass(), callGraphBuilder._method.getName(), Arrays.asList(callGraphBuilder._method.getParameterTypes()), convertArguments(args, traceType), convertReturnValue(callGraphBuilder._returnValue, traceType), throwableClass, errorMessage, stackTrace, calls, callGraphBuilder._duration); } private static Object convertReturnValue(Object returnValue, TraceType traceType) { switch (traceType) { case FULL: return returnValue; case FULL_AS_STRING: return String.valueOf(returnValue); default: return null; } } private static List<Object> convertArguments(List<Object> args, TraceType traceType) { switch (traceType) { case FULL: return args; case FULL_AS_STRING: return Lists.transform( args, new Function<Object, Object>() { @Override public Object apply(Object input) { return String.valueOf(input); } }); default: // Unusually we want null here to indicate we // are not capturing information return null; } } //------------------------------------------------------------------------- /** * Provides a pretty-printed version of the call graph as a string. * * @return a string representation of the call graph, not null */ public String prettyPrint() { return prettyPrint(new StringBuilder(), this, "", "").toString(); } private static StringBuilder prettyPrint(StringBuilder builder, CallGraphBuilder callGraphBuilder, String indent, String childIndent) { builder.append('\n').append(indent).append(callGraphBuilder.toString()); for (Iterator<CallGraphBuilder> itr = callGraphBuilder.calls().iterator(); itr.hasNext(); ) { CallGraphBuilder next = itr.next(); String newIndent; String newChildIndent; boolean isFinalChild = !itr.hasNext(); if (!isFinalChild) { newIndent = childIndent + " |--"; newChildIndent = childIndent + " | "; } else { newIndent = childIndent + " `--"; newChildIndent = childIndent + " "; } // these are unicode characters for box drawing /* if (!isFinalChild) { newIndent = childIndent + " \u251c\u2500\u2500"; newChildIndent = childIndent + " \u2502 "; } else { newIndent = childIndent + " \u2514\u2500\u2500"; newChildIndent = childIndent + " "; } */ prettyPrint(builder, next, newIndent, newChildIndent); } return builder; } //------------------------------------------------------------------------- @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } final CallGraphBuilder other = (CallGraphBuilder) obj; return Objects.equals(this._method, other._method) && Arrays.deepEquals(this._args, other._args) && Objects.equals(this._callGraphBuilders, other._callGraphBuilders) && Objects.equals(this._returnValue, other._returnValue) && Objects.equals(this._throwable, other._throwable); } @Override public int hashCode() { return Objects.hash(_method, Arrays.deepHashCode(_args), _callGraphBuilders, _returnValue, _throwable); } @Override public String toString() { return _method.getDeclaringClass().getSimpleName() + "." + _method.getName() + "()" + (_throwable == null ? " -> " + _returnValue : " threw " + _throwable) + (_args == null ? "" : ", args: " + Arrays.deepToString(_args)); } }