package com.sap.runlet.interpreter.expressions; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import behavioral.actions.Statement; import com.sap.runlet.abstractinterpreter.Interpreter; import com.sap.runlet.abstractinterpreter.objects.LogicallyEqualsWrapper; import com.sap.runlet.abstractinterpreter.objects.MultiValuedObject; import com.sap.runlet.abstractinterpreter.objects.RunletObject; import com.sap.runlet.interpreter.RunletInterpreter; import com.sap.runlet.interpreter.RunletStackFrame; import data.classes.Association; import data.classes.AssociationEnd; import data.classes.ClassTypeDefinition; import data.classes.NativeImpl; import data.classes.SapClass; import data.classes.SignatureImplementation; import data.classes.TypeDefinition; import dataaccess.analytics.DimensionDefinition; import dataaccess.analytics.GroupBy; import dataaccess.expressions.Expression; /** * A {@link GroupBy} expression computes the dimension expressions for each fact and groups those facts with equal * results for those dimension expressions. If a {@link GroupBy#getMapExpression() map expression} is provided, that * expression is evaluated for each group. Based on the required side effect-freeness of both, dimension expressions and * map expression, several of these computations can be executed in a parallel map/reduce style. * * @author Axel Uhl D043530 * */ public class GroupByInterpreter implements Interpreter<GroupBy, SapClass, TypeDefinition, ClassTypeDefinition, Association, AssociationEnd, Statement, Expression, SignatureImplementation, RunletStackFrame, NativeImpl, RunletInterpreter> { private GroupBy groupBy; public GroupByInterpreter(GroupBy groupBy) { this.groupBy = groupBy; } @Override public RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> evaluate( final RunletInterpreter interpreter) throws SecurityException, IllegalArgumentException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { final boolean factsOrdered = groupBy.getObject().getType().isOrdered(); final boolean factsUnique = groupBy.getObject().getType().isUnique(); Map<Map<DimensionDefinition, LogicallyEqualsWrapper<AssociationEnd, TypeDefinition, ClassTypeDefinition>>, Collection<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>>> groups = computeGroups(interpreter, factsOrdered, factsUnique); Collection<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> resultObjects; if (groupBy.getMapExpression() != null) { resultObjects = map(interpreter, groups); } else { resultObjects = RunletObject.createCollection(groupBy.getType().isOrdered(), groupBy.getType().isUnique()); TypeDefinition typeOfOneGroup = groupBy.getObject().getType(); for (Collection<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> group : groups.values()) { MultiValuedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> groupMultiObject = new MultiValuedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>( typeOfOneGroup, group, typeOfOneGroup.isOrdered(), typeOfOneGroup.isUnique()); resultObjects.add(groupMultiObject); } } MultiValuedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> result = new MultiValuedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>( groupBy.getType(), resultObjects, groupBy.getType().isOrdered(), groupBy.getType().isUnique()); return result; } private Collection<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> map( final RunletInterpreter interpreter, final Map<Map<DimensionDefinition, LogicallyEqualsWrapper<AssociationEnd, TypeDefinition, ClassTypeDefinition>>, Collection<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>>> groups) { Collection<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> unsynchronizedResultObjects = RunletObject .createCollection(groupBy.getType().isOrdered(), groupBy.getType().isUnique()); final Collection<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> resultObjects = Collections .synchronizedCollection(unsynchronizedResultObjects); Set<Thread> mappingThreads = new HashSet<Thread>(); final TypeDefinition typeOfOneGroup = groupBy.getGroupedFacts().getType(); for (final Map<DimensionDefinition, LogicallyEqualsWrapper<AssociationEnd, TypeDefinition, ClassTypeDefinition>> dimensions : groups.keySet()) { // map each group in parallel because map expression has to be side effect free Thread t = new Thread("GroupBy mapper for group with dimensions "+dimensions) { @Override public void run() { RunletInterpreter mapperInterpreter = interpreter.spawn(); RunletStackFrame frame = new RunletStackFrame(mapperInterpreter.getCallstack().peek()); // set all dimension iterators and groupedFacts iterator in new stack frame: for (DimensionDefinition dimension : groupBy.getDimensions()) { RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> dimensionValue = dimensions.get(dimension).getWrappedObject(); frame.enterValue(dimension.getIterator(), dimensionValue); } MultiValuedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> groupMultiObject = new MultiValuedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>( typeOfOneGroup, groups.get(dimensions), typeOfOneGroup.isOrdered(), typeOfOneGroup.isUnique()); frame.enterValue(groupBy.getGroupedFacts(), groupMultiObject); mapperInterpreter.push(frame); try { resultObjects.add(mapperInterpreter.evaluate(groupBy.getMapExpression())); mapperInterpreter.pop(); } catch (Exception e) { throw new RuntimeException(e); } } }; mappingThreads.add(t); t.start(); } for (Thread t : mappingThreads) { try { t.join(); } catch (InterruptedException e) { throw new RuntimeException("Didn't expect to get interrupted while waiting for mapping thread "+t, e); } } return resultObjects; } private Map<Map<DimensionDefinition, LogicallyEqualsWrapper<AssociationEnd, TypeDefinition, ClassTypeDefinition>>, Collection<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>>> computeGroups( final RunletInterpreter interpreter, final boolean factsOrdered, final boolean factsUnique) throws SecurityException, IllegalArgumentException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> facts = interpreter.evaluate(groupBy .getObject()); Set<Thread> groupingThreads = new HashSet<Thread>(); final Map<Map<DimensionDefinition, LogicallyEqualsWrapper<AssociationEnd, TypeDefinition, ClassTypeDefinition>>, Collection<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>>> groups = Collections.synchronizedMap(new HashMap<Map<DimensionDefinition, LogicallyEqualsWrapper<AssociationEnd, TypeDefinition, ClassTypeDefinition>>, Collection<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>>>()); for (final RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> fact : facts) { Thread t = new Thread("GroupBy grouper for fact "+fact) { @Override public void run() { Map<DimensionDefinition, LogicallyEqualsWrapper<AssociationEnd, TypeDefinition, ClassTypeDefinition>> valuesForDimension = Collections .synchronizedMap(new HashMap<DimensionDefinition, LogicallyEqualsWrapper<AssociationEnd, TypeDefinition, ClassTypeDefinition>>()); RunletInterpreter dimensionInterpreter = interpreter.spawn(); for (DimensionDefinition dimension : groupBy.getDimensions()) { RunletStackFrame frame = new RunletStackFrame(dimensionInterpreter.getCallstack().peek()); frame.enterValue(groupBy.getFact(), fact); dimensionInterpreter.push(frame); try { valuesForDimension.put(dimension, new LogicallyEqualsWrapper<AssociationEnd, TypeDefinition, ClassTypeDefinition>( dimensionInterpreter.evaluate(dimension.getExpression()))); dimensionInterpreter.pop(); } catch (Exception e) { throw new RuntimeException(e); } } Collection<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> group; synchronized(groups) { group = groups.get(valuesForDimension); if (group == null) { Collection<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> unsynchronizedGroup = RunletObject.createCollection(factsOrdered, factsUnique); group = Collections.synchronizedCollection(unsynchronizedGroup); groups.put(valuesForDimension, group); } } group.add(fact); } }; groupingThreads.add(t); t.start(); } for (Thread t : groupingThreads) { try { t.join(); } catch (InterruptedException e) { throw new RuntimeException("Didn't expect to get interrupted while waiting for grouping thread "+t, e); } } return groups; } }