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