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.io;
19 
20 import java.lang.Float;
21 import java.lang.Integer;
22 import java.lang.String;
23 
24 import java.util.ArrayList;
25 import java.util.regex.Pattern;
26 
27 import android.filterfw.core.Filter;
28 import android.filterfw.core.FilterFactory;
29 import android.filterfw.core.FilterGraph;
30 import android.filterfw.core.KeyValueMap;
31 import android.filterfw.core.ProtocolException;
32 import android.filterfw.io.GraphReader;
33 import android.filterfw.io.GraphIOException;
34 import android.filterfw.io.PatternScanner;
35 
36 /**
37  * @hide
38  */
39 public class TextGraphReader extends GraphReader {
40 
41     private ArrayList<Command> mCommands = new ArrayList<Command>();
42     private Filter mCurrentFilter;
43     private FilterGraph mCurrentGraph;
44     private KeyValueMap mBoundReferences;
45     private KeyValueMap mSettings;
46     private FilterFactory mFactory;
47 
48     private interface Command {
execute(TextGraphReader reader)49         public void execute(TextGraphReader reader) throws GraphIOException;
50     }
51 
52     private class ImportPackageCommand implements Command {
53         private String mPackageName;
54 
ImportPackageCommand(String packageName)55         public ImportPackageCommand(String packageName) {
56             mPackageName = packageName;
57         }
58 
59         @Override
execute(TextGraphReader reader)60         public void execute(TextGraphReader reader) throws GraphIOException {
61             try {
62                 reader.mFactory.addPackage(mPackageName);
63             } catch (IllegalArgumentException e) {
64                 throw new GraphIOException(e.getMessage());
65             }
66         }
67     }
68 
69     private class AddLibraryCommand implements Command {
70         private String mLibraryName;
71 
AddLibraryCommand(String libraryName)72         public AddLibraryCommand(String libraryName) {
73             mLibraryName = libraryName;
74         }
75 
76         @Override
execute(TextGraphReader reader)77         public void execute(TextGraphReader reader) {
78             reader.mFactory.addFilterLibrary(mLibraryName);
79         }
80     }
81 
82     private class AllocateFilterCommand implements Command {
83         private String mClassName;
84         private String mFilterName;
85 
AllocateFilterCommand(String className, String filterName)86         public AllocateFilterCommand(String className, String filterName) {
87             mClassName = className;
88             mFilterName = filterName;
89         }
90 
execute(TextGraphReader reader)91         public void execute(TextGraphReader reader) throws GraphIOException {
92             // Create the filter
93             Filter filter = null;
94             try {
95                 filter = reader.mFactory.createFilterByClassName(mClassName, mFilterName);
96             } catch (IllegalArgumentException e) {
97                 throw new GraphIOException(e.getMessage());
98             }
99 
100             // Set it as the current filter
101             reader.mCurrentFilter = filter;
102         }
103     }
104 
105     private class InitFilterCommand implements Command {
106         private KeyValueMap mParams;
107 
InitFilterCommand(KeyValueMap params)108         public InitFilterCommand(KeyValueMap params) {
109             mParams = params;
110         }
111 
112         @Override
execute(TextGraphReader reader)113         public void execute(TextGraphReader reader) throws GraphIOException {
114             Filter filter = reader.mCurrentFilter;
115             try {
116                 filter.initWithValueMap(mParams);
117             } catch (ProtocolException e) {
118                 throw new GraphIOException(e.getMessage());
119             }
120             reader.mCurrentGraph.addFilter(mCurrentFilter);
121         }
122     }
123 
124     private class ConnectCommand implements Command {
125         private String mSourceFilter;
126         private String mSourcePort;
127         private String mTargetFilter;
128         private String mTargetName;
129 
ConnectCommand(String sourceFilter, String sourcePort, String targetFilter, String targetName)130         public ConnectCommand(String sourceFilter,
131                               String sourcePort,
132                               String targetFilter,
133                               String targetName) {
134             mSourceFilter = sourceFilter;
135             mSourcePort = sourcePort;
136             mTargetFilter = targetFilter;
137             mTargetName = targetName;
138         }
139 
140         @Override
execute(TextGraphReader reader)141         public void execute(TextGraphReader reader) {
142             reader.mCurrentGraph.connect(mSourceFilter, mSourcePort, mTargetFilter, mTargetName);
143         }
144     }
145 
146     @Override
readGraphString(String graphString)147     public FilterGraph readGraphString(String graphString) throws GraphIOException {
148         FilterGraph result = new FilterGraph();
149 
150         reset();
151         mCurrentGraph = result;
152         parseString(graphString);
153         applySettings();
154         executeCommands();
155         reset();
156 
157         return result;
158     }
159 
reset()160     private void reset() {
161         mCurrentGraph = null;
162         mCurrentFilter = null;
163         mCommands.clear();
164         mBoundReferences = new KeyValueMap();
165         mSettings = new KeyValueMap();
166         mFactory = new FilterFactory();
167     }
168 
parseString(String graphString)169     private void parseString(String graphString) throws GraphIOException {
170         final Pattern commandPattern = Pattern.compile("@[a-zA-Z]+");
171         final Pattern curlyClosePattern = Pattern.compile("\\}");
172         final Pattern curlyOpenPattern = Pattern.compile("\\{");
173         final Pattern ignorePattern = Pattern.compile("(\\s+|//[^\\n]*\\n)+");
174         final Pattern packageNamePattern = Pattern.compile("[a-zA-Z\\.]+");
175         final Pattern libraryNamePattern = Pattern.compile("[a-zA-Z\\./:]+");
176         final Pattern portPattern = Pattern.compile("\\[[a-zA-Z0-9\\-_]+\\]");
177         final Pattern rightArrowPattern = Pattern.compile("=>");
178         final Pattern semicolonPattern = Pattern.compile(";");
179         final Pattern wordPattern = Pattern.compile("[a-zA-Z0-9\\-_]+");
180 
181         final int STATE_COMMAND           = 0;
182         final int STATE_IMPORT_PKG        = 1;
183         final int STATE_ADD_LIBRARY       = 2;
184         final int STATE_FILTER_CLASS      = 3;
185         final int STATE_FILTER_NAME       = 4;
186         final int STATE_CURLY_OPEN        = 5;
187         final int STATE_PARAMETERS        = 6;
188         final int STATE_CURLY_CLOSE       = 7;
189         final int STATE_SOURCE_FILTERNAME = 8;
190         final int STATE_SOURCE_PORT       = 9;
191         final int STATE_RIGHT_ARROW       = 10;
192         final int STATE_TARGET_FILTERNAME = 11;
193         final int STATE_TARGET_PORT       = 12;
194         final int STATE_ASSIGNMENT        = 13;
195         final int STATE_EXTERNAL          = 14;
196         final int STATE_SETTING           = 15;
197         final int STATE_SEMICOLON         = 16;
198 
199         int state = STATE_COMMAND;
200         PatternScanner scanner = new PatternScanner(graphString, ignorePattern);
201 
202         String curClassName = null;
203         String curSourceFilterName = null;
204         String curSourcePortName = null;
205         String curTargetFilterName = null;
206         String curTargetPortName = null;
207 
208         // State machine main loop
209         while (!scanner.atEnd()) {
210             switch (state) {
211                 case STATE_COMMAND: {
212                     String curCommand = scanner.eat(commandPattern, "<command>");
213                     if (curCommand.equals("@import")) {
214                         state = STATE_IMPORT_PKG;
215                     } else if (curCommand.equals("@library")) {
216                         state = STATE_ADD_LIBRARY;
217                     } else if (curCommand.equals("@filter")) {
218                         state = STATE_FILTER_CLASS;
219                     } else if (curCommand.equals("@connect")) {
220                         state = STATE_SOURCE_FILTERNAME;
221                     } else if (curCommand.equals("@set")) {
222                         state = STATE_ASSIGNMENT;
223                     } else if (curCommand.equals("@external")) {
224                         state = STATE_EXTERNAL;
225                     } else if (curCommand.equals("@setting")) {
226                         state = STATE_SETTING;
227                     } else {
228                         throw new GraphIOException("Unknown command '" + curCommand + "'!");
229                     }
230                     break;
231                 }
232 
233                 case STATE_IMPORT_PKG: {
234                     String packageName = scanner.eat(packageNamePattern, "<package-name>");
235                     mCommands.add(new ImportPackageCommand(packageName));
236                     state = STATE_SEMICOLON;
237                     break;
238                 }
239 
240                 case STATE_ADD_LIBRARY: {
241                     String libraryName = scanner.eat(libraryNamePattern, "<library-name>");
242                     mCommands.add(new AddLibraryCommand(libraryName));
243                     state = STATE_SEMICOLON;
244                     break;
245                 }
246 
247                 case STATE_FILTER_CLASS:
248                     curClassName = scanner.eat(wordPattern, "<class-name>");
249                     state = STATE_FILTER_NAME;
250                     break;
251 
252                 case STATE_FILTER_NAME: {
253                     String curFilterName = scanner.eat(wordPattern, "<filter-name>");
254                     mCommands.add(new AllocateFilterCommand(curClassName, curFilterName));
255                     state = STATE_CURLY_OPEN;
256                     break;
257                 }
258 
259                 case STATE_CURLY_OPEN:
260                     scanner.eat(curlyOpenPattern, "{");
261                     state = STATE_PARAMETERS;
262                     break;
263 
264                 case STATE_PARAMETERS: {
265                     KeyValueMap params = readKeyValueAssignments(scanner, curlyClosePattern);
266                     mCommands.add(new InitFilterCommand(params));
267                     state = STATE_CURLY_CLOSE;
268                     break;
269                 }
270 
271                 case STATE_CURLY_CLOSE:
272                     scanner.eat(curlyClosePattern, "}");
273                     state = STATE_COMMAND;
274                     break;
275 
276                 case STATE_SOURCE_FILTERNAME:
277                     curSourceFilterName = scanner.eat(wordPattern, "<source-filter-name>");
278                     state = STATE_SOURCE_PORT;
279                     break;
280 
281                 case STATE_SOURCE_PORT: {
282                     String portString = scanner.eat(portPattern, "[<source-port-name>]");
283                     curSourcePortName = portString.substring(1, portString.length() - 1);
284                     state = STATE_RIGHT_ARROW;
285                     break;
286                 }
287 
288                 case STATE_RIGHT_ARROW:
289                     scanner.eat(rightArrowPattern, "=>");
290                     state = STATE_TARGET_FILTERNAME;
291                     break;
292 
293                 case STATE_TARGET_FILTERNAME:
294                     curTargetFilterName = scanner.eat(wordPattern, "<target-filter-name>");
295                     state = STATE_TARGET_PORT;
296                     break;
297 
298                 case STATE_TARGET_PORT: {
299                     String portString = scanner.eat(portPattern, "[<target-port-name>]");
300                     curTargetPortName = portString.substring(1, portString.length() - 1);
301                     mCommands.add(new ConnectCommand(curSourceFilterName,
302                                                      curSourcePortName,
303                                                      curTargetFilterName,
304                                                      curTargetPortName));
305                     state = STATE_SEMICOLON;
306                     break;
307                 }
308 
309                 case STATE_ASSIGNMENT: {
310                     KeyValueMap assignment = readKeyValueAssignments(scanner, semicolonPattern);
311                     mBoundReferences.putAll(assignment);
312                     state = STATE_SEMICOLON;
313                     break;
314                 }
315 
316                 case STATE_EXTERNAL: {
317                     String externalName = scanner.eat(wordPattern, "<external-identifier>");
318                     bindExternal(externalName);
319                     state = STATE_SEMICOLON;
320                     break;
321                 }
322 
323                 case STATE_SETTING: {
324                     KeyValueMap setting = readKeyValueAssignments(scanner, semicolonPattern);
325                     mSettings.putAll(setting);
326                     state = STATE_SEMICOLON;
327                     break;
328                 }
329 
330                 case STATE_SEMICOLON:
331                     scanner.eat(semicolonPattern, ";");
332                     state = STATE_COMMAND;
333                     break;
334             }
335         }
336 
337         // Make sure end of input was expected
338         if (state != STATE_SEMICOLON && state != STATE_COMMAND) {
339             throw new GraphIOException("Unexpected end of input!");
340         }
341     }
342 
343     @Override
readKeyValueAssignments(String assignments)344     public KeyValueMap readKeyValueAssignments(String assignments) throws GraphIOException {
345         final Pattern ignorePattern = Pattern.compile("\\s+");
346         PatternScanner scanner = new PatternScanner(assignments, ignorePattern);
347         return readKeyValueAssignments(scanner, null);
348     }
349 
readKeyValueAssignments(PatternScanner scanner, Pattern endPattern)350     private KeyValueMap readKeyValueAssignments(PatternScanner scanner,
351                                                 Pattern endPattern) throws GraphIOException {
352         // Our parser is a state-machine, and these are our states
353         final int STATE_IDENTIFIER = 0;
354         final int STATE_EQUALS     = 1;
355         final int STATE_VALUE      = 2;
356         final int STATE_POST_VALUE = 3;
357 
358         final Pattern equalsPattern = Pattern.compile("=");
359         final Pattern semicolonPattern = Pattern.compile(";");
360         final Pattern wordPattern = Pattern.compile("[a-zA-Z]+[a-zA-Z0-9]*");
361         final Pattern stringPattern = Pattern.compile("'[^']*'|\\\"[^\\\"]*\\\"");
362         final Pattern intPattern = Pattern.compile("[0-9]+");
363         final Pattern floatPattern = Pattern.compile("[0-9]*\\.[0-9]+f?");
364         final Pattern referencePattern = Pattern.compile("\\$[a-zA-Z]+[a-zA-Z0-9]");
365         final Pattern booleanPattern = Pattern.compile("true|false");
366 
367         int state = STATE_IDENTIFIER;
368         KeyValueMap newVals = new KeyValueMap();
369         String curKey = null;
370         String curValue = null;
371 
372         while (!scanner.atEnd() && !(endPattern != null && scanner.peek(endPattern))) {
373             switch (state) {
374                 case STATE_IDENTIFIER:
375                     curKey = scanner.eat(wordPattern, "<identifier>");
376                     state = STATE_EQUALS;
377                     break;
378 
379                 case STATE_EQUALS:
380                     scanner.eat(equalsPattern, "=");
381                     state = STATE_VALUE;
382                     break;
383 
384                 case STATE_VALUE:
385                     if ((curValue = scanner.tryEat(stringPattern)) != null) {
386                         newVals.put(curKey, curValue.substring(1, curValue.length() - 1));
387                     } else if ((curValue = scanner.tryEat(referencePattern)) != null) {
388                         String refName = curValue.substring(1, curValue.length());
389                         Object referencedObject = mBoundReferences != null
390                             ? mBoundReferences.get(refName)
391                             : null;
392                         if (referencedObject == null) {
393                             throw new GraphIOException(
394                                 "Unknown object reference to '" + refName + "'!");
395                         }
396                         newVals.put(curKey, referencedObject);
397                     } else if ((curValue = scanner.tryEat(booleanPattern)) != null) {
398                         newVals.put(curKey, Boolean.parseBoolean(curValue));
399                     } else if ((curValue = scanner.tryEat(floatPattern)) != null) {
400                         newVals.put(curKey, Float.parseFloat(curValue));
401                     } else if ((curValue = scanner.tryEat(intPattern)) != null) {
402                         newVals.put(curKey, Integer.parseInt(curValue));
403                     } else {
404                         throw new GraphIOException(scanner.unexpectedTokenMessage("<value>"));
405                     }
406                     state = STATE_POST_VALUE;
407                     break;
408 
409                 case STATE_POST_VALUE:
410                     scanner.eat(semicolonPattern, ";");
411                     state = STATE_IDENTIFIER;
412                     break;
413             }
414         }
415 
416         // Make sure end is expected
417         if (state != STATE_IDENTIFIER && state != STATE_POST_VALUE) {
418             throw new GraphIOException(
419                 "Unexpected end of assignments on line " + scanner.lineNo() + "!");
420         }
421 
422         return newVals;
423     }
424 
bindExternal(String name)425     private void bindExternal(String name) throws GraphIOException {
426         if (mReferences.containsKey(name)) {
427             Object value = mReferences.get(name);
428             mBoundReferences.put(name, value);
429         } else {
430             throw new GraphIOException("Unknown external variable '" + name + "'! "
431                 + "You must add a reference to this external in the host program using "
432                 + "addReference(...)!");
433         }
434     }
435 
436     /**
437      * Unused for now: Often you may want to declare references that are NOT in a certain graph,
438      * e.g. when reading multiple graphs with the same reader. We could print a warning, but even
439      * that may be too much.
440      **/
checkReferences()441     private void checkReferences() throws GraphIOException {
442         for (String reference : mReferences.keySet()) {
443             if (!mBoundReferences.containsKey(reference)) {
444                 throw new GraphIOException(
445                     "Host program specifies reference to '" + reference + "', which is not "
446                     + "declared @external in graph file!");
447             }
448         }
449     }
450 
applySettings()451     private void applySettings() throws GraphIOException {
452         for (String setting : mSettings.keySet()) {
453             Object value = mSettings.get(setting);
454             if (setting.equals("autoBranch")) {
455                 expectSettingClass(setting, value, String.class);
456                 if (value.equals("synced")) {
457                     mCurrentGraph.setAutoBranchMode(FilterGraph.AUTOBRANCH_SYNCED);
458                 } else if (value.equals("unsynced")) {
459                     mCurrentGraph.setAutoBranchMode(FilterGraph.AUTOBRANCH_UNSYNCED);
460                 } else if (value.equals("off")) {
461                     mCurrentGraph.setAutoBranchMode(FilterGraph.AUTOBRANCH_OFF);
462                 } else {
463                     throw new GraphIOException("Unknown autobranch setting: " + value + "!");
464                 }
465             } else if (setting.equals("discardUnconnectedOutputs")) {
466                 expectSettingClass(setting, value, Boolean.class);
467                 mCurrentGraph.setDiscardUnconnectedOutputs((Boolean)value);
468             } else {
469                 throw new GraphIOException("Unknown @setting '" + setting + "'!");
470             }
471         }
472     }
473 
expectSettingClass(String setting, Object value, Class expectedClass)474     private void expectSettingClass(String setting,
475                                     Object value,
476                                     Class expectedClass) throws GraphIOException {
477         if (value.getClass() != expectedClass) {
478             throw new GraphIOException("Setting '" + setting + "' must have a value of type "
479                 + expectedClass.getSimpleName() + ", but found a value of type "
480                 + value.getClass().getSimpleName() + "!");
481         }
482     }
483 
executeCommands()484     private void executeCommands() throws GraphIOException {
485         for (Command command : mCommands) {
486             command.execute(this);
487         }
488     }
489 }
490