1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 
18 package android.filterfw.core;
19 
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.LinkedList;
24 import java.util.Map.Entry;
25 import java.util.Set;
26 import java.util.Stack;
27 
28 import android.filterfw.core.FilterContext;
29 import android.filterfw.core.KeyValueMap;
30 import android.filterpacks.base.FrameBranch;
31 import android.filterpacks.base.NullFilter;
32 
33 import android.util.Log;
34 
35 /**
36  * @hide
37  */
38 public class FilterGraph {
39 
40     private HashSet<Filter> mFilters = new HashSet<Filter>();
41     private HashMap<String, Filter> mNameMap = new HashMap<String, Filter>();
42     private HashMap<OutputPort, LinkedList<InputPort>> mPreconnections = new
43             HashMap<OutputPort, LinkedList<InputPort>>();
44 
45     public static final int AUTOBRANCH_OFF      = 0;
46     public static final int AUTOBRANCH_SYNCED   = 1;
47     public static final int AUTOBRANCH_UNSYNCED = 2;
48 
49     public static final int TYPECHECK_OFF       = 0;
50     public static final int TYPECHECK_DYNAMIC   = 1;
51     public static final int TYPECHECK_STRICT    = 2;
52 
53     private boolean mIsReady = false;
54     private int mAutoBranchMode = AUTOBRANCH_OFF;
55     private int mTypeCheckMode = TYPECHECK_STRICT;
56     private boolean mDiscardUnconnectedOutputs = false;
57 
58     private boolean mLogVerbose;
59     private String TAG = "FilterGraph";
60 
FilterGraph()61     public FilterGraph() {
62         mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
63     }
64 
addFilter(Filter filter)65     public boolean addFilter(Filter filter) {
66         if (!containsFilter(filter)) {
67             mFilters.add(filter);
68             mNameMap.put(filter.getName(), filter);
69             return true;
70         }
71         return false;
72     }
73 
containsFilter(Filter filter)74     public boolean containsFilter(Filter filter) {
75         return mFilters.contains(filter);
76     }
77 
getFilter(String name)78     public Filter getFilter(String name) {
79         return mNameMap.get(name);
80     }
81 
connect(Filter source, String outputName, Filter target, String inputName)82     public void connect(Filter source,
83                         String outputName,
84                         Filter target,
85                         String inputName) {
86         if (source == null || target == null) {
87             throw new IllegalArgumentException("Passing null Filter in connect()!");
88         } else if (!containsFilter(source) || !containsFilter(target)) {
89             throw new RuntimeException("Attempting to connect filter not in graph!");
90         }
91 
92         OutputPort outPort = source.getOutputPort(outputName);
93         InputPort inPort = target.getInputPort(inputName);
94         if (outPort == null) {
95             throw new RuntimeException("Unknown output port '" + outputName + "' on Filter " +
96                                        source + "!");
97         } else if (inPort == null) {
98             throw new RuntimeException("Unknown input port '" + inputName + "' on Filter " +
99                                        target + "!");
100         }
101 
102         preconnect(outPort, inPort);
103     }
104 
connect(String sourceName, String outputName, String targetName, String inputName)105     public void connect(String sourceName,
106                         String outputName,
107                         String targetName,
108                         String inputName) {
109         Filter source = getFilter(sourceName);
110         Filter target = getFilter(targetName);
111         if (source == null) {
112             throw new RuntimeException(
113                 "Attempting to connect unknown source filter '" + sourceName + "'!");
114         } else if (target == null) {
115             throw new RuntimeException(
116                 "Attempting to connect unknown target filter '" + targetName + "'!");
117         }
118         connect(source, outputName, target, inputName);
119     }
120 
getFilters()121     public Set<Filter> getFilters() {
122         return mFilters;
123     }
124 
beginProcessing()125     public void beginProcessing() {
126         if (mLogVerbose) Log.v(TAG, "Opening all filter connections...");
127         for (Filter filter : mFilters) {
128             filter.openOutputs();
129         }
130         mIsReady = true;
131     }
132 
flushFrames()133     public void flushFrames() {
134         for (Filter filter : mFilters) {
135             filter.clearOutputs();
136         }
137     }
138 
closeFilters(FilterContext context)139     public void closeFilters(FilterContext context) {
140         if (mLogVerbose) Log.v(TAG, "Closing all filters...");
141         for (Filter filter : mFilters) {
142             filter.performClose(context);
143         }
144         mIsReady = false;
145     }
146 
isReady()147     public boolean isReady() {
148         return mIsReady;
149     }
150 
setAutoBranchMode(int autoBranchMode)151     public void setAutoBranchMode(int autoBranchMode) {
152         mAutoBranchMode = autoBranchMode;
153     }
154 
setDiscardUnconnectedOutputs(boolean discard)155     public void setDiscardUnconnectedOutputs(boolean discard) {
156         mDiscardUnconnectedOutputs = discard;
157     }
158 
setTypeCheckMode(int typeCheckMode)159     public void setTypeCheckMode(int typeCheckMode) {
160         mTypeCheckMode = typeCheckMode;
161     }
162 
tearDown(FilterContext context)163     public void tearDown(FilterContext context) {
164         if (!mFilters.isEmpty()) {
165             flushFrames();
166             for (Filter filter : mFilters) {
167                 filter.performTearDown(context);
168             }
169             mFilters.clear();
170             mNameMap.clear();
171             mIsReady = false;
172         }
173     }
174 
readyForProcessing(Filter filter, Set<Filter> processed)175     private boolean readyForProcessing(Filter filter, Set<Filter> processed) {
176         // Check if this has been already processed
177         if (processed.contains(filter)) {
178             return false;
179         }
180 
181         // Check if all dependencies have been processed
182         for (InputPort port : filter.getInputPorts()) {
183             Filter dependency = port.getSourceFilter();
184             if (dependency != null && !processed.contains(dependency)) {
185                 return false;
186             }
187         }
188         return true;
189     }
190 
runTypeCheck()191     private void runTypeCheck() {
192         Stack<Filter> filterStack = new Stack<Filter>();
193         Set<Filter> processedFilters = new HashSet<Filter>();
194         filterStack.addAll(getSourceFilters());
195 
196         while (!filterStack.empty()) {
197             // Get current filter and mark as processed
198             Filter filter = filterStack.pop();
199             processedFilters.add(filter);
200 
201             // Anchor output formats
202             updateOutputs(filter);
203 
204             // Perform type check
205             if (mLogVerbose) Log.v(TAG, "Running type check on " + filter + "...");
206             runTypeCheckOn(filter);
207 
208             // Push connected filters onto stack
209             for (OutputPort port : filter.getOutputPorts()) {
210                 Filter target = port.getTargetFilter();
211                 if (target != null && readyForProcessing(target, processedFilters)) {
212                     filterStack.push(target);
213                 }
214             }
215         }
216 
217         // Make sure all ports were setup
218         if (processedFilters.size() != getFilters().size()) {
219             throw new RuntimeException("Could not schedule all filters! Is your graph malformed?");
220         }
221     }
222 
updateOutputs(Filter filter)223     private void updateOutputs(Filter filter) {
224         for (OutputPort outputPort : filter.getOutputPorts()) {
225             InputPort inputPort = outputPort.getBasePort();
226             if (inputPort != null) {
227                 FrameFormat inputFormat = inputPort.getSourceFormat();
228                 FrameFormat outputFormat = filter.getOutputFormat(outputPort.getName(),
229                                                                   inputFormat);
230                 if (outputFormat == null) {
231                     throw new RuntimeException("Filter did not return an output format for "
232                         + outputPort + "!");
233                 }
234                 outputPort.setPortFormat(outputFormat);
235             }
236         }
237     }
238 
runTypeCheckOn(Filter filter)239     private void runTypeCheckOn(Filter filter) {
240         for (InputPort inputPort : filter.getInputPorts()) {
241             if (mLogVerbose) Log.v(TAG, "Type checking port " + inputPort);
242             FrameFormat sourceFormat = inputPort.getSourceFormat();
243             FrameFormat targetFormat = inputPort.getPortFormat();
244             if (sourceFormat != null && targetFormat != null) {
245                 if (mLogVerbose) Log.v(TAG, "Checking " + sourceFormat + " against " + targetFormat + ".");
246 
247                 boolean compatible = true;
248                 switch (mTypeCheckMode) {
249                     case TYPECHECK_OFF:
250                         inputPort.setChecksType(false);
251                         break;
252                     case TYPECHECK_DYNAMIC:
253                         compatible = sourceFormat.mayBeCompatibleWith(targetFormat);
254                         inputPort.setChecksType(true);
255                         break;
256                     case TYPECHECK_STRICT:
257                         compatible = sourceFormat.isCompatibleWith(targetFormat);
258                         inputPort.setChecksType(false);
259                         break;
260                 }
261 
262                 if (!compatible) {
263                     throw new RuntimeException("Type mismatch: Filter " + filter + " expects a "
264                         + "format of type " + targetFormat + " but got a format of type "
265                         + sourceFormat + "!");
266                 }
267             }
268         }
269     }
270 
checkConnections()271     private void checkConnections() {
272         // TODO
273     }
274 
discardUnconnectedOutputs()275     private void discardUnconnectedOutputs() {
276         // Connect unconnected ports to Null filters
277         LinkedList<Filter> addedFilters = new LinkedList<Filter>();
278         for (Filter filter : mFilters) {
279             int id = 0;
280             for (OutputPort port : filter.getOutputPorts()) {
281                 if (!port.isConnected()) {
282                     if (mLogVerbose) Log.v(TAG, "Autoconnecting unconnected " + port + " to Null filter.");
283                     NullFilter nullFilter = new NullFilter(filter.getName() + "ToNull" + id);
284                     nullFilter.init();
285                     addedFilters.add(nullFilter);
286                     port.connectTo(nullFilter.getInputPort("frame"));
287                     ++id;
288                 }
289             }
290         }
291         // Add all added filters to this graph
292         for (Filter filter : addedFilters) {
293             addFilter(filter);
294         }
295     }
296 
removeFilter(Filter filter)297     private void removeFilter(Filter filter) {
298         mFilters.remove(filter);
299         mNameMap.remove(filter.getName());
300     }
301 
preconnect(OutputPort outPort, InputPort inPort)302     private void preconnect(OutputPort outPort, InputPort inPort) {
303         LinkedList<InputPort> targets;
304         targets = mPreconnections.get(outPort);
305         if (targets == null) {
306             targets = new LinkedList<InputPort>();
307             mPreconnections.put(outPort, targets);
308         }
309         targets.add(inPort);
310     }
311 
connectPorts()312     private void connectPorts() {
313         int branchId = 1;
314         for (Entry<OutputPort, LinkedList<InputPort>> connection : mPreconnections.entrySet()) {
315             OutputPort outputPort = connection.getKey();
316             LinkedList<InputPort> inputPorts = connection.getValue();
317             if (inputPorts.size() == 1) {
318                 outputPort.connectTo(inputPorts.get(0));
319             } else if (mAutoBranchMode == AUTOBRANCH_OFF) {
320                 throw new RuntimeException("Attempting to connect " + outputPort + " to multiple "
321                                          + "filter ports! Enable auto-branching to allow this.");
322             } else {
323                 if (mLogVerbose) Log.v(TAG, "Creating branch for " + outputPort + "!");
324                 FrameBranch branch = null;
325                 if (mAutoBranchMode == AUTOBRANCH_SYNCED) {
326                     branch = new FrameBranch("branch" + branchId++);
327                 } else {
328                     throw new RuntimeException("TODO: Unsynced branches not implemented yet!");
329                 }
330                 KeyValueMap branchParams = new KeyValueMap();
331                 branch.initWithAssignmentList("outputs", inputPorts.size());
332                 addFilter(branch);
333                 outputPort.connectTo(branch.getInputPort("in"));
334                 Iterator<InputPort> inputPortIter = inputPorts.iterator();
335                 for (OutputPort branchOutPort : ((Filter)branch).getOutputPorts()) {
336                     branchOutPort.connectTo(inputPortIter.next());
337                 }
338             }
339         }
340         mPreconnections.clear();
341     }
342 
getSourceFilters()343     private HashSet<Filter> getSourceFilters() {
344         HashSet<Filter> sourceFilters = new HashSet<Filter>();
345         for (Filter filter : getFilters()) {
346             if (filter.getNumberOfConnectedInputs() == 0) {
347                 if (mLogVerbose) Log.v(TAG, "Found source filter: " + filter);
348                 sourceFilters.add(filter);
349             }
350         }
351         return sourceFilters;
352     }
353 
354     // Core internal methods /////////////////////////////////////////////////////////////////////////
setupFilters()355     void setupFilters() {
356         if (mDiscardUnconnectedOutputs) {
357             discardUnconnectedOutputs();
358         }
359         connectPorts();
360         checkConnections();
361         runTypeCheck();
362     }
363 }
364