Home | History | Annotate | Download | only in io
      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 {
     49         public void execute(TextGraphReader reader) throws GraphIOException;
     50     }
     51 
     52     private class ImportPackageCommand implements Command {
     53         private String mPackageName;
     54 
     55         public ImportPackageCommand(String packageName) {
     56             mPackageName = packageName;
     57         }
     58 
     59         @Override
     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 
     72         public AddLibraryCommand(String libraryName) {
     73             mLibraryName = libraryName;
     74         }
     75 
     76         @Override
     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 
     86         public AllocateFilterCommand(String className, String filterName) {
     87             mClassName = className;
     88             mFilterName = filterName;
     89         }
     90 
     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 
    108         public InitFilterCommand(KeyValueMap params) {
    109             mParams = params;
    110         }
    111 
    112         @Override
    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 
    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
    141         public void execute(TextGraphReader reader) {
    142             reader.mCurrentGraph.connect(mSourceFilter, mSourcePort, mTargetFilter, mTargetName);
    143         }
    144     }
    145 
    146     @Override
    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 
    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 
    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
    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 
    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 
    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      **/
    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 
    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 
    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 
    484     private void executeCommands() throws GraphIOException {
    485         for (Command command : mCommands) {
    486             command.execute(this);
    487         }
    488     }
    489 }
    490