/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* GemGraphGenerator.java
* Created: 15-Mar-2004
* By: Rick Cameron
*/
package org.openquark.samples.bam;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.openquark.cal.compiler.CodeAnalyser;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.SourceModel;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.compiler.TypeChecker.TypeCheckInfo;
import org.openquark.cal.module.Cal.Collections.CAL_List;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.services.BasicCALServices;
import org.openquark.gems.client.CodeGem;
import org.openquark.gems.client.CollectorGem;
import org.openquark.gems.client.FunctionalAgentGem;
import org.openquark.gems.client.Gem;
import org.openquark.gems.client.GemGraph;
import org.openquark.gems.client.ReflectorGem;
import org.openquark.samples.bam.model.ActionDescription;
import org.openquark.samples.bam.model.BoundGemDescription;
import org.openquark.samples.bam.model.InputBinding;
import org.openquark.samples.bam.model.MetricDescription;
import org.openquark.samples.bam.model.MonitorJobDescription;
import org.openquark.samples.bam.model.TriggerDescription;
import org.openquark.samples.bam.model.MessageSourceDescription.MessagePropertyDescription;
import org.openquark.util.DurationLogger;
/**
* The GemGraphGenerator is used to assemble and compile the gem graph based
* on a job description.
*/
class GemGraphGenerator {
private final BasicCALServices calServices;
private final MonitorJobDescription jobDescription;
//the names of gems used to 'zip' 0, 1, ... lists together
private final QualifiedName[] zippingGems = {
CAL_List.Functions.repeat, //constant value - repeat
CAL_List.Functions.map,
CAL_List.Functions.zipWith,
CAL_List.Functions.zipWith3,
CAL_List.Functions.zipWith4,
CAL_List.Functions.zipWith5,
CAL_List.Functions.zipWith6,
CAL_List.Functions.zipWith7
};
private GemGraph gemGraph = null;
private CodeAnalyser codeAnalyser = null;
private CollectorGem messageGem = null;
/**
* this class is used to store and retrieve the collector gems for
* binding trigger and actions to metrics and message properties.
*/
private class GeneratorBindingContext implements BindingContext {
private final Map<Object, CollectorGem> collectorMap = new HashMap<Object, CollectorGem>();
/**
* @see org.openquark.samples.bam.BindingContext#getCollector(java.lang.Object)
*/
public CollectorGem getCollector (Object id) {
if (collectorMap != null) {
CollectorGem gem = collectorMap.get (id);
if (gem != null) {
return gem;
} else {
throw new IllegalArgumentException ("There is no collector for " + id);
}
}
throw new IllegalStateException ("The collector map has not been built");
}
/**
* Add a new collector to the binding context
* @param id
* @param gem
*/
public void addCollector(Object id, CollectorGem gem) {
collectorMap.put(id, gem);
}
}
private final GeneratorBindingContext bindingContext = new GeneratorBindingContext ();
/**
* constructs the GemGraphGenerator with the job description and the calServices
*/
public GemGraphGenerator (BasicCALServices calServices, MonitorJobDescription jobDescription) {
this.calServices = calServices;
this.jobDescription = jobDescription;
codeAnalyser =new CodeAnalyser(
calServices.getTypeChecker(),
calServices.getCALWorkspace().getMetaModule(MonitorApp.TARGET_MODULE).getTypeInfo(),
true,
false);
}
/**
* get the source representing the gem
* @return the source function definition that represents the gem
*/
public SourceModel.FunctionDefn getCalSource() {
GemGraph gem = getGemGraph ();
return gem.getCALSource();
}
/**
* Gets the gem representing the JobDescription
* builds the gem graph if it has not already been built.
*
* @return Returns the completed gem graph
*/
private GemGraph getGemGraph () {
if (gemGraph == null) {
if (!buildGemGraph()) {
return null;
}
}
return gemGraph;
}
/**
* Builds the gem graph
* @return true if the graph is created successfully
*/
private boolean buildGemGraph () {
try {
gemGraph = new GemGraph ();
DurationLogger logger = new DurationLogger ();
addCollectorForMessage();
addCollectorsForMessageProperties ();
addCollectorsForMetrics();
logger.report("Time to add collectors: ", true);
Gem triggerResultGem = addTriggerGems ();
logger.report("Time to add triggers: ", true);
Gem actionResultGem = addActionGems ();
logger.report("Time to add actions: ", true);
finishGemGraph (triggerResultGem, actionResultGem);
logger.report("Time to finish graph: ", true);
assert graphIsValid() : gemGraph.toString();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* This adds a collector for the message input
*/
private void addCollectorForMessage () {
messageGem = new CollectorGem ();
messageGem.setName ("messages");
// We must set the type of the message collector gem to ensure that the type of the
// gem graph will not be ambiguous. To see why the type might be ambiguous,
// consider the average metric (BAM.average),
// which has a type signature of Num a => [a] -> [Double]. Using this metric
// is not sufficient to unambiguously constrain the type of a message property.
// This is true of most of the metrics. Once the type of the message is defined, it
// will ensure all the other types in the gem graph are unambiguous.
ArrayList<TypeExpr> types=new ArrayList<TypeExpr>();
for (final MessagePropertyDescription propertyDescription : jobDescription.getMessagePropertyDescriptions()) {
types.add(propertyDescription.getCalType(calServices));
}
messageGem.setDeclaredType(
TypeExpr.makeListType(
TypeExpr.makeTupleType(types),
calServices.getCALWorkspace().getMetaModule(MonitorApp.TARGET_MODULE).getTypeInfo())
);
gemGraph.addGem(messageGem);
assert graphIsValid() : gemGraph.toString();
}
/**
* This adds collector gems for each of the message properties
*/
private void addCollectorsForMessageProperties () {
Collection<MessagePropertyDescription> propertyInfos = jobDescription.getMessagePropertyDescriptions();
int i=0;
for (final MessagePropertyDescription propertyInfo : propertyInfos) {
//create the message reflector
ReflectorGem messageReflector = new ReflectorGem (messageGem);
gemGraph.addGem(messageReflector);
// Create a code gem to extract the ith property from the message list input
// e.g. the code gem for the 5th message property would be:
// List.map (\msg -> msg.#5) msg
// which takes the input list of message tuples as input, and outputs a new list
// containing the 5th element of every message.
Gem propertyExtractorGem = new CodeGem(codeAnalyser, CAL_List.Functions.map.getQualifiedName() + " (\\msg -> msg.#" + ++i +") msg", Collections.<String>emptySet());
gemGraph.addGem(propertyExtractorGem);
// create the collector gem for this message property
CollectorGem propertyGem = new CollectorGem ();
propertyGem.setName (makeCollectorName (propertyInfo.name));
gemGraph.addGem(propertyGem);
gemGraph.connectGems(messageReflector.getOutputPart(), propertyExtractorGem.getInputPart(0));
gemGraph.connectGems(propertyExtractorGem.getOutputPart(), propertyGem.getInputPart(0));
bindingContext.addCollector(propertyInfo, propertyGem);
assert graphIsValid() : gemGraph.toString();
}
}
/**
* This adds collector gems for all of the required metrics
*/
private void addCollectorsForMetrics () {
Collection<MetricDescription> metrics = jobDescription.getMetricDescriptions();
for (final MetricDescription md : metrics) {
//create the message reflector
ReflectorGem messagePropertyReflector = new ReflectorGem (bindingContext.getCollector(md.getPropertyDescription()));
gemGraph.addGem(messagePropertyReflector);
//create the gem to compute this metric
FunctionalAgentGem computeMetricGem = new FunctionalAgentGem(calServices.getGemEntity(md.getGemName()));
gemGraph.addGem(computeMetricGem);
//create the collector gem for this metric
CollectorGem metricGem = new CollectorGem ();
metricGem.setName(makeCollectorName(md.getInternalName()));
gemGraph.addGem(metricGem);
gemGraph.connectGems( messagePropertyReflector.getOutputPart(), computeMetricGem.getInputPart(0));
gemGraph.connectGems( computeMetricGem.getOutputPart(), metricGem.getInputPart(0));
bindingContext.addCollector(md, metricGem);
assert graphIsValid() : gemGraph.toString();
}
}
/**
* Converts a name to a form suitable for use as the name of a CollectorGem
*
* @param name
* @return collector name
*/
private String makeCollectorName (String name) {
return name.toLowerCase().replace('.', '_');
}
/**
* Adds all of the trigger gems to the graph and connects them to the message property/metric collectors
*/
private Gem addTriggerGems () {
List<TriggerDescription> boundGemList = jobDescription.getTriggerDescriptions();
return addGems (boundGemList);
}
/**
* Adds all of the action gems to the graph and connects them to the message property/metric collectors
*/
private Gem addActionGems () {
List<ActionDescription> boundGemList = jobDescription.getActionDescriptions();
return addGems (boundGemList);
}
/**
* Add a list of gems to the gem graph. Each gem and it's inputs are defined by a BoundGemDescription
*
* @param boundGemList
* @return Returns the top-level gem that results from conjoining all the gems described by the boundGemList
*/
private Gem addGems (List<? extends BoundGemDescription> boundGemList) {
if (boundGemList == null || boundGemList.size() == 0) {
throw new IllegalArgumentException ("The list of bound gems must not be empty");
}
Gem result = null;
for (int gemN = 0; gemN < boundGemList.size(); ++gemN) {
BoundGemDescription gemDescription = boundGemList.get(gemN);
FunctionalAgentGem zipGem = new FunctionalAgentGem(calServices.getGemEntity(zippingGems[gemDescription.getVariableBindingCount()]));
FunctionalAgentGem zippingFunctionGem = new FunctionalAgentGem(calServices.getGemEntity(gemDescription.getQualifiedName()));
gemGraph.addGem(zipGem);
gemGraph.addGem(zippingFunctionGem);
connectInputs (zipGem, zippingFunctionGem, gemDescription, gemGraph);
if (result == null) {
result = zipGem;
} else {
FunctionalAgentGem zipGem2 = new FunctionalAgentGem(calServices.getGemEntity(CAL_List.Functions.zipWith));
gemGraph.addGem (zipGem2);
FunctionalAgentGem andGem = new FunctionalAgentGem(calServices.getGemEntity(CAL_Prelude.Functions.and));
gemGraph.addGem (andGem);
andGem.getInputPart(0).setBurnt(true);
andGem.getInputPart(1).setBurnt(true);
gemGraph.connectGems(andGem.getOutputPart(), zipGem2.getInputPart(0));
gemGraph.connectGems(zipGem.getOutputPart(), zipGem2.getInputPart(1));
gemGraph.connectGems(result.getOutputPart(), zipGem2.getInputPart(2));
result = zipGem2;
}
}
return result;
}
/**
* Connect the inputs to a gem. The inputs are defined by InputBindings in the BoundGemDescription.
*
* @param zipGem
* @param zippingFunctionGem
* @param boundGemDescription
* @param gemGraph
*/
private void connectInputs (FunctionalAgentGem zipGem, FunctionalAgentGem zippingFunctionGem, BoundGemDescription boundGemDescription, GemGraph gemGraph) {
//connect the zipping function
int zipInput=0;
gemGraph.connectGems(zippingFunctionGem.getOutputPart(),zipGem.getInputPart(zipInput++));
//connect the parameters
for (int inputN = 0; inputN < boundGemDescription.getBindingCount(); ++inputN) {
InputBinding binding = boundGemDescription.getNthBinding(inputN);
Gem outputGem = binding.getOutputGem(calServices, gemGraph, bindingContext);
if (binding.isConstant()) {
gemGraph.connectGems(outputGem.getOutputPart(), zippingFunctionGem.getInputPart(inputN));
} else {
zippingFunctionGem.getInputPart(inputN).setBurnt(true);
gemGraph.connectGems(outputGem.getOutputPart(), zipGem.getInputPart(zipInput++));
}
}
assert graphIsValid() : gemGraph.toString();
}
/**
* Check that the graph is valid
* @return true if the graph is valid
*/
private boolean graphIsValid() {
TypeCheckInfo typeCheckInfo = calServices.getTypeCheckInfo(MonitorApp.TARGET_MODULE);
return (gemGraph.checkGraphValid(typeCheckInfo));
}
/**
* This combines the output of the trigger gems with the action gems and connects the output
* of the action gems to the result gem.
*
* @param triggerResultGem
* @param actionResultGem
*/
private void finishGemGraph (Gem triggerResultGem, Gem actionResultGem) {
FunctionalAgentGem zipGem = new FunctionalAgentGem(calServices.getGemEntity(CAL_List.Functions.zipWith));
gemGraph.addGem (zipGem);
FunctionalAgentGem andGem = new FunctionalAgentGem(calServices.getGemEntity(CAL_Prelude.Functions.and));
gemGraph.addGem (andGem);
andGem.getInputPart(0).setBurnt(true);
andGem.getInputPart(1).setBurnt(true);
gemGraph.connectGems(andGem.getOutputPart(), zipGem.getInputPart(0));
gemGraph.connectGems(triggerResultGem.getOutputPart(), zipGem.getInputPart(1));
gemGraph.connectGems(actionResultGem.getOutputPart(), zipGem.getInputPart(2));
Gem toIteratorGem = new FunctionalAgentGem(calServices.getGemEntity(CAL_List.Functions.toJIterator));
gemGraph.addGem(toIteratorGem);
gemGraph.connectGems(zipGem.getOutputPart(), toIteratorGem.getInputPart(0));
gemGraph.connectGems( toIteratorGem.getOutputPart(), gemGraph.getTargetCollector().getInputPart(0));
}
}