/*
# Licensed Materials - Property of IBM
# Copyright IBM Corp. 2015
*/
package com.ibm.streamsx.topology.generator.spl;
import static com.ibm.streamsx.topology.internal.gson.GsonUtilities.jboolean;
import static com.ibm.streamsx.topology.internal.gson.GsonUtilities.jobject;
import static com.ibm.streamsx.topology.internal.gson.GsonUtilities.jstring;
import static com.ibm.streamsx.topology.builder.JParamTypes.TYPE_COMPOSITE_PARAMETER;
import static com.ibm.streamsx.topology.builder.JParamTypes.TYPE_SUBMISSION_PARAMETER;
import static com.ibm.streamsx.topology.generator.functional.FunctionalOpProperties.FUNCTIONAL_LOGIC_PARAM;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.ibm.streamsx.topology.builder.JParamTypes;
import com.ibm.streamsx.topology.internal.gson.GsonUtilities;
/**
* A Submission Time Value is the SPL realization of a "Submission Parameter".
*/
public class SubmissionTimeValue {
private static final String OP_ATTR_SPL_SUBMISSION_PARAMS = "__spl_submissionParams";
/** map<name,opParam> opParam has type TYPE_SUBMISSION_PARAMETER */
private final Map<String,JsonObject> allSubmissionParams = new HashMap<>();
/**
* Map of submission value names to the composite parameter
* that represents them at the main composite level.
*/
private final Map<String,JsonObject> submissionMainCompositeParams = new HashMap<>();
/** map<opName,opJsonObject> */
private Map<String,JsonObject> functionalOps = new HashMap<>();
private ParamsInfo paramsInfo;
/**
* Info for SPL operator parameters
* NAME_SUBMISSION_PARAM_NAMES and NAME_SUBMISSION_PARAM_VALUES
*/
static class ParamsInfo {
/** The value for the NAME_SUBMISSION_PARAM_NAMES param.
* <p>
* A string of comma separated literal STV opParamName strings.
* e.g., "__jaa_threshold", "__jaa_width"
*/
final String names;
/** The value for the NAME_SUBMISSION_PARAM_VALUES param.
* <p>
* A string of comma separated STV opParam expression strings.
* e.g., (rstring) $__jaa_threshold, (rstring) $__jaa_width
*/
final String values;
ParamsInfo(String names, String values) {
this.names = names;
this.values = values;
}
}
/**
* Create a composite parameter name for the submission parameter name.
* @param spName the submission parameter name
* @return the composite parameter name
*/
private static String mkCompositeParamName(String spName) {
spName = spName.replace('.', '_');
return "__spl_stv_" + SPLGenerator.getSPLCompatibleName(spName);
}
SubmissionTimeValue(JsonObject graph) {
createMainCompositeParamsForAllSubmissionValues(graph);
paramsInfo = mkSubmissionParamsInfo();
}
/**
* Get a collection of all of the submission parameters used in the graph.
* @param graph
* @return {@code map<spOpParamName,spParam>}
*/
private void createMainCompositeParamsForAllSubmissionValues(JsonObject graph) {
Map<String,JsonObject> all = this.allSubmissionParams;
JsonObject params = GsonUtilities.jobject(graph, "parameters");
if (params != null) {
for (Entry<String, JsonElement> e : params.entrySet()) {
JsonObject param = e.getValue().getAsJsonObject();
if (TYPE_SUBMISSION_PARAMETER.equals(jstring(param, "type"))) {
JsonObject sp = jobject(param, "value");
all.put(jstring(sp, "name"), param);
}
}
}
for (JsonObject param : all.values())
addMainCompositeParam(params, param);
}
/**
* Create a main composite to represent a submission parameter.
*
* A parameter with name width, type int32 default 3 is mapped to in the main composite:
*
* expression<int32> $__spl_stv_width : (int32) getSubmissionTimeValue("width", "3");
*
*/
private void addMainCompositeParam(JsonObject params, JsonObject param) {
JsonObject spv = jobject(param, "value");
String spname = jstring(spv, "name");
String metaType = jstring(spv, "metaType");
String cpname = mkCompositeParamName(spname);
JsonObject cp = new JsonObject();
cp.addProperty("type", TYPE_COMPOSITE_PARAMETER);
JsonObject cpv = new JsonObject();
cpv.addProperty("name", cpname);
cpv.addProperty("metaType", metaType);
String splspname = SPLGenerator.stringLiteral(spname);
String splType = Types.metaTypeToSPL(metaType);
String cpdv;
if (!spv.has("defaultValue")) {
cpdv = String.format("(%s) getSubmissionTimeValue(%s)", splType, splspname);
} else {
JsonPrimitive defaultValueJson = spv.get("defaultValue").getAsJsonPrimitive();
String defaultValue;
if (metaType.startsWith("UINT")) {
StringBuilder sbunsigned = new StringBuilder();
sbunsigned.append("(rstring) ");
SPLGenerator.numberLiteral(sbunsigned, defaultValueJson, metaType);
defaultValue = sbunsigned.toString();
} else {
defaultValue = SPLGenerator.stringLiteral(defaultValueJson.getAsString());
}
cpdv = String.format("(%s) getSubmissionTimeValue(%s, %s)", splType, splspname, defaultValue);
}
cpv.addProperty("defaultValue", cpdv);
cp.add("value", cpv);
params.add(cpname, cp);
submissionMainCompositeParams.put(spname, cp);
}
/**
* Create a inner composite to access a submission parameter defined in
* a main composite..
*
* A parameter with name width, type int32 default 3 is mapped to in the main composite:
*
* expression<int32> $__spl_stv_width;
*
*/
private void addInnerCompositeParameter(JsonObject params, JsonObject param) {
assert TYPE_SUBMISSION_PARAMETER.equals(jstring(param, "type"));
JsonObject spv = jobject(param, "value");
String spname = jstring(spv, "name");
String metaType = jstring(spv, "metaType");
String cpname = mkCompositeParamName(spname);
if (params.has(cpname))
return;
JsonObject cp = new JsonObject();
cp.addProperty("type", TYPE_COMPOSITE_PARAMETER);
JsonObject cpv = new JsonObject();
cpv.addProperty("name", cpname);
cpv.addProperty("metaType", metaType);
cp.add("value", cpv);
params.add(cpname, cp);
}
/**
* Create a ParamsInfo for the topology's submission parameters.
* This is how submission parameter values are passed into
* a functional operator.
* @return the parameter info. null if no submission parameters.
*/
private ParamsInfo mkSubmissionParamsInfo() {
if (allSubmissionParams.isEmpty())
return null;
StringBuilder namesSb = new StringBuilder();
StringBuilder valuesSb = new StringBuilder();
boolean first = true;
for (String opParamName : allSubmissionParams.keySet()) {
JsonObject spParam = allSubmissionParams.get(opParamName);
JsonObject spval = jobject(spParam, "value");
String name = jstring(spval, "name");
if (first)
first = false;
else {
namesSb.append(", ");
valuesSb.append(", ");
}
namesSb.append(SPLGenerator.stringLiteral(name));
valuesSb.append("(rstring) ").append(generateCompositeParamReference(spval));
}
return new ParamsInfo(namesSb.toString(), valuesSb.toString());
}
/**
* Enrich the json composite operator definition's parameters
* to include parameters for submission parameters.
* <p>
* The composite is augmented with a TYPE_SUBMISSION_PARAMETER parameter
* for each submission parameter used within the composite - e.g, as
* a parallel width value or SPL operator parameter value.
* <p>
* If the composite has any functional operator children, enrich
* the composite to have declarations for all submission parameters.
* Also accumulate the functional children and make them available via
* {@link #getFunctionalOps()}.
*
* @param composite the composite definition
*/
void addJsonParamDefs(JsonObject composite) {
// scan immediate children ops for submission param use
// and add corresponding param definitions to the composite.
// Also, if the op has functional logic, enrich the op too...
// and further enrich the composite.
if (allSubmissionParams.isEmpty())
return;
// scan for spParams
JsonObject spParams = new JsonObject();
AtomicBoolean addedAll = new AtomicBoolean();
GsonUtilities.objectArray(composite, "operators", op -> {
JsonObject params = jobject(op, "parameters");
if (params != null) {
boolean addAll = false;
for (Entry<String, JsonElement> p : params.entrySet()) {
// if functional logic add "submissionParameters" param
if (params.has(FUNCTIONAL_LOGIC_PARAM)) {
functionalOps.put(jstring(op, "name"), op);
addAll = true;
break;
}
else {
JsonObject param = p.getValue().getAsJsonObject();
String type = jstring(param, "type");
if (TYPE_SUBMISSION_PARAMETER.equals(type)) {
addInnerCompositeParameter(spParams, param);
}
}
}
if (addAll && !addedAll.getAndSet(true)) {
for (String name : allSubmissionParams.keySet()) {
addInnerCompositeParameter(spParams, allSubmissionParams.get(name));
}
}
}
boolean isParallel = jboolean(op, "parallelOperator");
if (isParallel) {
JsonElement width = op.get("width");
if (width.isJsonObject()) {
JsonObject jwidth = width.getAsJsonObject();
String type = jstring(jwidth, "type");
if (TYPE_SUBMISSION_PARAMETER.equals(type)) {
addInnerCompositeParameter(spParams, jwidth);
}
}
}
});
// augment the composite's parameters
JsonObject params = jobject(composite, "parameters");
if (params == null && !GsonUtilities.jisEmpty(spParams)) {
params = new JsonObject();
composite.add("parameters", params);
}
for (Entry<String, JsonElement> p : spParams.entrySet()) {
String pname = p.getKey();
if (!params.has(pname))
params.add(pname, spParams.get(pname));
}
// make the results of our efforts available to addJsonInstanceParams
composite.add(OP_ATTR_SPL_SUBMISSION_PARAMS, spParams);
}
/**
* Akin to addJsonParamDefs(), enrich the json composite operator instance's
* invocation parameters with submission parameter references.
* @param compInstance the composite instance
* @param composite the composite definition
*/
void addJsonInstanceParams(JsonObject compInstance, JsonObject composite) {
JsonObject spParams = jobject(composite, OP_ATTR_SPL_SUBMISSION_PARAMS);
if (spParams != null) {
JsonObject opParams = jobject(compInstance, "parameters");
if (opParams == null) {
compInstance.add("parameters", opParams = new JsonObject());
}
for (Entry<String, JsonElement> p : spParams.entrySet()) {
JsonObject spParam = p.getValue().getAsJsonObject();
// need to end up generating: __spl_stv_foo : $__spl_stv_foo;
opParams.add(p.getKey(), compositeParameterReference(spParam));
}
}
}
/**
* Return a SPL expression that accesses
* a submission time value.
* @param name
* @return
*/
JsonObject getSPLExpression(JsonObject param) {
assert jstring(param, "type").equals(TYPE_SUBMISSION_PARAMETER);
String name = jstring(jobject(param, "value"), "name");
return compositeParameterReference(submissionMainCompositeParams.get(name));
}
/**
* Create an SPL expression that is a reference to a
* composite parameter.
*/
private static JsonObject compositeParameterReference(JsonObject compParam) {
assert jstring(compParam, "type").equals(TYPE_COMPOSITE_PARAMETER);
JsonObject spval = jobject(compParam, "value");
String name = jstring(spval, "name");
JsonObject ref = new JsonObject();
ref.addProperty("type", JParamTypes.TYPE_SPL_EXPRESSION);
ref.addProperty("value", "$" + name);
return ref;
}
// /** Get the graph's submission parameters in the form of a
// * operator parameter of TYPE_SPL_SUBMISSION_PARAMS
// * @return the op parameter. null if no submission params in the topology.
// */
// JSONObject getSubmissionParamsParam() {
// return submissionParamsParam;
// }
/** Get the info for operator NAME_SUBISSION_PARAM_NAMES
* and NAMEgraph's submission parameter info in the form of a
* operator parameter of TYPE_SPL_SUBMISSION_PARAMS
* @return the op parameter. null if no submission params in the topology.
*/
ParamsInfo getSplInfo() {
return paramsInfo;
}
/** Get the list of functional ops learned by {@link #addJsonParamDefs(JSONObject)}.
* @return the collection of functional ops map<opName, opJsonObject>
*/
Map<String,JsonObject> getFunctionalOps() {
return functionalOps;
}
/**
* Generate a submission time value SPL param value definition
* in a main composite.
* <p>
* e.g.,
* <pre>{@code
* param
* expression<uint32> $__jaa_stv_foo : (uint32) getSubmissionTimeValue("foo", 3)
* }</pre>
* @param spval JSONObject for the submission parameter's value
* @param sb
*/
void generateMainDef(JsonObject spval, StringBuilder sb) {
String paramName = generateCompositeParamReference(spval);
String spName = SPLGenerator.stringLiteral(jstring(spval, "name"));
String metaType = jstring(spval, "metaType");
String splType = Types.metaTypeToSPL(metaType);
sb.append(String.format("expression<%s> %s : ", splType, paramName));
if (!spval.has("defaultValue")) {
sb.append(String.format("(%s) getSubmissionTimeValue(%s)", splType, spName));
} else {
JsonPrimitive defaultValueJson = spval.get("defaultValue").getAsJsonPrimitive();
String defaultValue;
if (metaType.startsWith("UINT")) {
StringBuilder sbunsigned = new StringBuilder();
sbunsigned.append("(rstring) ");
SPLGenerator.numberLiteral(sbunsigned, defaultValueJson, metaType);
defaultValue = sbunsigned.toString();
} else {
defaultValue = SPLGenerator.stringLiteral(defaultValueJson.getAsString());
}
sb.append(String.format("(%s) getSubmissionTimeValue(%s, %s)", splType, spName, defaultValue));
}
}
/**
* Generate a submission time value SPL param value definition
* in an inner (non-main) composite definition.
* <p>
* e.g.,
* <pre>{@code
* param
* expression<uint32> $__jaa_stv_foo
* }</pre>
* @param spval JSONObject for the submission parameter's value
* @param sb
*/
void generateInnerDef(JsonObject spval, StringBuilder sb) {
String paramName = generateCompositeParamReference(spval);
String metaType = jstring(spval, "metaType");
String splType = Types.metaTypeToSPL(metaType);
sb.append(String.format("expression<%s> %s", splType, paramName));
}
/**
* Generate a $__jaa_stv_... composite parameter name
* for the submission time value parameter.
* <p>
* <pre><code>
* composite {
* param expression<int32> $__jaa_stv_foo = (int32) getSubmissionTimeValue(...)
* graph
* {@literal @}parallel(width=$__jaa_stv_foo)
* </code></pre>
* @param spval JSONObject for the submission parameter's value
* @return the name
*/
static String generateCompositeParamReference(JsonObject spval) {
return "$" + mkCompositeParamName(spval.get("name").getAsString());
}
}