/*
# Licensed Materials - Property of IBM
# Copyright IBM Corp. 2015
*/
package com.ibm.streamsx.topology.generator.spl;
import static com.ibm.streamsx.topology.generator.spl.GraphUtilities.findOperatorsByKinds;
import static com.ibm.streamsx.topology.generator.spl.GraphUtilities.getDownstream;
import static com.ibm.streamsx.topology.generator.spl.GraphUtilities.kind;
import static com.ibm.streamsx.topology.internal.gson.GsonUtilities.array;
import static com.ibm.streamsx.topology.internal.gson.GsonUtilities.jstring;
import java.util.HashSet;
import java.util.Set;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
/**
* Optimize takes the preprocessed graph and adds any optimization.
*/
class Optimizer {
@SuppressWarnings("unused")
private final SPLGenerator generator;
private final JsonObject graph;
Optimizer(SPLGenerator generator, JsonObject graph) {
this.generator = generator;
this.graph = graph;
}
void optimize() {
pyPassByRef();
}
private static final String PY_OP_NS = "com.ibm.streamsx.topology.functional.python";
private static final Set<String> PY_FUNC_OPS = new HashSet<>();
static {
for (String kind : new String[] { "Source", "Filter", "Map", "FlatMap", "ForEach"}) {
PY_FUNC_OPS.add(PY_OP_NS + "::" + kind);
PY_FUNC_OPS.add(PY_OP_NS + "2::" + kind);
}
}
/**
* Setup Python operators to allow pass by reference.
*
* Finds Python functional operators and sets the outputConnections
* parameter representing the number of connections.
*
* If pass by reference cannot be used outputConnections will not be set.
*
* Does not modify the structure of the graph.
* Assumes the graph's structure will not be subsequently modified.
*/
private final void pyPassByRef() {
Set<JsonObject> pyops = findOperatorsByKinds(graph, PY_FUNC_OPS);
if (pyops.isEmpty())
return;
for (JsonObject pyop : pyops) {
JsonArray outputs = array(pyop, "outputs");
if (outputs == null || outputs.size() == 0)
continue;
// Currently only supporting a single output port
// though mostly coded to support N.
assert outputs.size() == 1;
int[] connCounts = new int[outputs.size()];
for (int port = 0; port < connCounts.length; port++) {
connCounts[port] = -1;
JsonObject output = outputs.get(port).getAsJsonObject();
// Can't use the schema objects as we need to not depend on IBM Streams
// classes.
if (!"tuple<blob __spl_po>".equals(jstring(output, "type")))
continue;
JsonArray conns = array(output, "connections");
if (conns == null || conns.size() == 0) {
connCounts[port] = 0;
continue;
}
boolean canPassByRef = true;
// TOOD - downstream for a specific port
Set<JsonObject> connected = getDownstream(pyop, graph);
for (JsonObject connectedOp : connected) {
if (!PY_FUNC_OPS.contains(kind(connectedOp))) {
canPassByRef = false;
break;
}
// TEMP
// Currently only Map and ForEach completly handle
// by reference.
if (!kind(connectedOp).endsWith("::Map") && !kind(connectedOp).endsWith("::ForEach") && !kind(connectedOp).endsWith("::FlatMap")) {
canPassByRef = false;
break;
}
}
if (canPassByRef)
connCounts[port] = conns.size();
}
boolean paramNeeded = false;
for (int oc: connCounts) {
if (oc != -1) {
paramNeeded = true;
break;
}
}
if (paramNeeded) {
JsonObject value = new JsonObject();
if (connCounts.length == 1)
value.addProperty("value", connCounts[0]);
else {
JsonArray ocs = new JsonArray();
for (int oc : connCounts)
ocs.add(new JsonPrimitive(oc));
value.add("value", ocs);
}
GraphUtilities.addOpParameter(pyop, "outputConnections", value);
}
}
}
}