Implement ScriptGroup and add test.

Change-Id: I6ce0479c20f425d501c759c15717aa8b418c3f5f
diff --git a/graphics/java/android/renderscript/ScriptGroup.java b/graphics/java/android/renderscript/ScriptGroup.java
new file mode 100644
index 0000000..c4064b5
--- /dev/null
+++ b/graphics/java/android/renderscript/ScriptGroup.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.renderscript;
+
+import java.lang.reflect.Method;
+
+/**
+ * @hide
+ **/
+public class ScriptGroup extends BaseObj {
+    Node mNodes[];
+    Connection mConnections[];
+    Node mFirstNode;
+    IO mOutputs[];
+    IO mInputs[];
+
+    static class IO {
+        Script mScript;
+        Allocation mAllocation;
+        String mName;
+
+        IO(Script s) {
+            mScript = s;
+        }
+        IO(Script s, String n) {
+            mScript = s;
+            mName = n;
+        }
+    }
+
+    static class Connection {
+        Node mTo[];
+        String mToName[];
+        Node mFrom;
+        Type mAllocationType;
+        Allocation mInternalAllocation;
+
+        Connection(Node out, Type t) {
+            mFrom = out;
+            mAllocationType = t;
+        }
+
+        void addTo(Node n, String name) {
+            if (mTo == null) {
+                mTo = new Node[1];
+                mToName = new String[1];
+            } else {
+                Node nt[] = new Node[mTo.length + 1];
+                String ns[] = new String[mTo.length + 1];
+                System.arraycopy(mTo, 0, nt, 0, mTo.length);
+                System.arraycopy(mToName, 0, ns, 0, mTo.length);
+                mTo = nt;
+                mToName = ns;
+            }
+            mTo[mTo.length - 1] = n;
+            mToName[mTo.length - 1] = name;
+        }
+    }
+
+    static class Node {
+        Script mScript;
+        Connection mInput[] = new Connection[8];
+        Connection mOutput[] = new Connection[1];
+        int mInputCount;
+        int mOutputCount;
+        int mDepth;
+        boolean mSeen;
+
+        Node mNext;
+
+        Node(Script s) {
+            mScript = s;
+        }
+
+        void addInput(Connection c) {
+            if (mInput.length <= mInputCount) {
+                Connection[] nc = new Connection[mInput.length + 8];
+                System.arraycopy(mInput, 0, nc, 0, mInputCount);
+                mInput = nc;
+            }
+            mInput[mInputCount++] = c;
+        }
+
+        void addOutput(Connection c) {
+            if (mOutput.length <= mOutputCount) {
+                Connection[] nc = new Connection[mOutput.length + 8];
+                System.arraycopy(mOutput, 0, nc, 0, mOutputCount);
+                mOutput = nc;
+            }
+            mOutput[mOutputCount++] = c;
+        }
+    }
+
+
+    ScriptGroup(int id, RenderScript rs) {
+        super(id, rs);
+    }
+
+    void init(int nodeCount, int connectionCount) {
+        mNodes = new Node[nodeCount];
+        mConnections = new Connection[connectionCount];
+
+        android.util.Log.v("RSR", "init" + nodeCount + ", " + connectionCount);
+
+        // Count outputs and create array.
+        Node n = mFirstNode;
+        int outputCount = 0;
+        int inputCount = 0;
+        int connectionIndex = 0;
+        int nodeNum = 0;
+        while (n != null) {
+            mNodes[nodeNum++] = n;
+
+            // Look for unattached kernel inputs
+            boolean hasInput = false;
+            for (int ct=0; ct < n.mInput.length; ct++) {
+                if (n.mInput[ct] != null) {
+                    if (n.mInput[ct].mToName == null) {
+                        hasInput = true;
+                    }
+                }
+            }
+            if (!hasInput) {
+                if (mInputs == null) {
+                    mInputs = new IO[1];
+                }
+                if (mInputs.length <= inputCount) {
+                    IO t[] = new IO[mInputs.length + 1];
+                    System.arraycopy(mInputs, 0, t, 0, mInputs.length);
+                    mInputs = t;
+                }
+                mInputs[inputCount++] = new IO(n.mScript);
+            }
+
+            // Look for unattached kernel outputs
+            boolean hasOutput = false;
+            for (int ct=0; ct < n.mOutput.length; ct++) {
+                if (n.mOutput[ct] != null) {
+                    hasOutput = true;
+                }
+            }
+            if (!hasOutput) {
+                if (mOutputs == null) {
+                    mOutputs = new IO[1];
+                }
+                if (mOutputs.length <= outputCount) {
+                    IO t[] = new IO[mOutputs.length + 1];
+                    System.arraycopy(mOutputs, 0, t, 0, mOutputs.length);
+                    mOutputs = t;
+                }
+                mOutputs[outputCount++] = new IO(n.mScript);
+            }
+
+            // Make allocations for internal connections
+            // Since script outputs are unique, use those to avoid duplicates.
+            for (int ct=0; ct < n.mOutput.length; ct++) {
+                android.util.Log.v("RSR", "init out2 " + n.mOutput[ct]);
+                if (n.mOutput[ct] != null) {
+                    Connection t = n.mOutput[ct];
+                    mConnections[connectionIndex++] = t;
+                    t.mInternalAllocation = Allocation.createTyped(mRS, t.mAllocationType);
+                }
+            }
+
+            n = n.mNext;
+        }
+    }
+
+    public void setInput(Script s, Allocation a) {
+        for (int ct=0; ct < mInputs.length; ct++) {
+            if (mInputs[ct].mScript == s) {
+                mInputs[ct].mAllocation = a;
+                return;
+            }
+        }
+        throw new RSIllegalArgumentException("Script not found");
+    }
+
+    public void setOutput(Script s, Allocation a) {
+        for (int ct=0; ct < mOutputs.length; ct++) {
+            if (mOutputs[ct].mScript == s) {
+                mOutputs[ct].mAllocation = a;
+                return;
+            }
+        }
+        throw new RSIllegalArgumentException("Script not found");
+    }
+
+    public void execute() {
+        android.util.Log.v("RSR", "execute");
+        boolean more = true;
+        int depth = 0;
+        while (more) {
+            more = false;
+            for (int ct=0; ct < mNodes.length; ct++) {
+                if (mNodes[ct].mDepth == depth) {
+                    more = true;
+
+                    Allocation kernelIn = null;
+                    for (int ct2=0; ct2 < mNodes[ct].mInputCount; ct2++) {
+                        android.util.Log.v("RSR", " kin " + ct2 + ", to " + mNodes[ct].mInput[ct2].mTo[0] + ", name " + mNodes[ct].mInput[ct2].mToName[0]);
+                        if (mNodes[ct].mInput[ct2].mToName[0] == null) {
+                            kernelIn = mNodes[ct].mInput[ct2].mInternalAllocation;
+                            break;
+                        }
+                    }
+
+                    Allocation kernelOut= null;
+                    for (int ct2=0; ct2 < mNodes[ct].mOutputCount; ct2++) {
+                        android.util.Log.v("RSR", " kout " + ct2 + ", from " + mNodes[ct].mOutput[ct2].mFrom);
+                        if (mNodes[ct].mOutput[ct2].mFrom != null) {
+                            kernelOut = mNodes[ct].mOutput[ct2].mInternalAllocation;
+                            break;
+                        }
+                    }
+                    if (kernelOut == null) {
+                        for (int ct2=0; ct2 < mOutputs.length; ct2++) {
+                            if (mOutputs[ct2].mScript == mNodes[ct].mScript) {
+                                kernelOut = mOutputs[ct2].mAllocation;
+                                break;
+                            }
+                        }
+                    }
+
+                    android.util.Log.v("RSR", "execute calling " + mNodes[ct] + ", with " + kernelIn);
+                    if (kernelIn != null) {
+                        try {
+
+                            Method m = mNodes[ct].mScript.getClass().getMethod("forEach_root",
+                                          new Class[] { Allocation.class, Allocation.class });
+                            m.invoke(mNodes[ct].mScript, new Object[] {kernelIn, kernelOut} );
+                        } catch (Throwable t) {
+                            android.util.Log.e("RSR", "execute error " + t);
+                        }
+                    } else {
+                        try {
+                            Method m = mNodes[ct].mScript.getClass().getMethod("forEach_root",
+                                          new Class[] { Allocation.class });
+                            m.invoke(mNodes[ct].mScript, new Object[] {kernelOut} );
+                        } catch (Throwable t) {
+                            android.util.Log.e("RSR", "execute error " + t);
+                        }
+                    }
+
+                }
+            }
+            depth ++;
+        }
+
+    }
+
+
+    public static class Builder {
+        RenderScript mRS;
+        Node mFirstNode;
+        int mConnectionCount = 0;
+        int mNodeCount = 0;
+
+        public Builder(RenderScript rs) {
+            mRS = rs;
+        }
+
+        private void validateRecurse(Node n, int depth) {
+            n.mSeen = true;
+            if (depth > n.mDepth) {
+                n.mDepth = depth;
+            }
+
+            android.util.Log.v("RSR", " validateRecurse outputCount " + n.mOutputCount);
+            for (int ct=0; ct < n.mOutputCount; ct++) {
+                for (int ct2=0; ct2 < n.mOutput[ct].mTo.length; ct2++) {
+                    if (n.mOutput[ct].mTo[ct2].mSeen) {
+                        throw new RSInvalidStateException("Loops in group not allowed.");
+                    }
+                    validateRecurse(n.mOutput[ct].mTo[ct2], depth + 1);
+                }
+            }
+        }
+
+        private void validate() {
+            android.util.Log.v("RSR", "validate");
+            Node n = mFirstNode;
+            while (n != null) {
+                n.mSeen = false;
+                n.mDepth = 0;
+                n = n.mNext;
+            }
+
+            n = mFirstNode;
+            while (n != null) {
+                android.util.Log.v("RSR", "validate n= " + n);
+                if ((n.mSeen == false) && (n.mInputCount == 0)) {
+                    android.util.Log.v("RSR", " recursing " + n);
+                    validateRecurse(n, 0);
+                }
+                n = n.mNext;
+            }
+        }
+
+        private Node findScript(Script s) {
+            Node n = mFirstNode;
+            while (n != null) {
+                if (n.mScript == s) {
+                    return n;
+                }
+                n = n.mNext;
+            }
+            return null;
+        }
+
+        private void addNode(Node n) {
+            n.mNext = mFirstNode;
+            mFirstNode = n;
+        }
+
+        public Builder addConnection(Type t, Script output, Script input, String inputName) {
+            android.util.Log.v("RSR", "addConnection " + t +", " + output + ", " + input);
+
+            // Look for existing output
+            Node nout = findScript(output);
+            Connection c;
+            if (nout == null) {
+                // Make new node
+                android.util.Log.v("RSR", "addConnection new output node");
+                nout = new Node(output);
+                mNodeCount++;
+                c = new Connection(nout, t);
+                mConnectionCount++;
+                nout.addOutput(c);
+                addNode(nout);
+            } else {
+                // Add to existing node
+                android.util.Log.v("RSR", "addConnection reuse output node");
+                if (nout.mOutput[0] != null) {
+                    if (nout.mOutput[0].mFrom.mScript != output) {
+                        throw new RSInvalidStateException("Changed output of existing node");
+                    }
+                    if (nout.mOutput[0].mAllocationType != t) {
+                        throw new RSInvalidStateException("Changed output type of existing node");
+                    }
+                }
+                c = nout.mOutput[0];
+            }
+            // At this point we should have a connection attached to a script ouput.
+
+            // Find input
+            Node nin = findScript(input);
+            if (nin == null) {
+                android.util.Log.v("RSR", "addConnection new input node");
+                nin = new Node(input);
+                mNodeCount++;
+                addNode(nin);
+            }
+            c.addTo(nin, inputName);
+            nin.addInput(c);
+
+            validate();
+            return this;
+        }
+
+        public ScriptGroup create() {
+            ScriptGroup sg = new ScriptGroup(0, mRS);
+            sg.mFirstNode = mFirstNode;
+            mFirstNode = null;
+
+            android.util.Log.v("RSR", "create nodes= " + mNodeCount + ", Connections= " + mConnectionCount);
+
+            sg.init(mNodeCount, mConnectionCount);
+            return sg;
+        }
+
+    }
+
+
+}
+
+