blob: 366ef822b0ecee6214a8c21ec971a036fc6355de [file] [log] [blame]
/*
* Copyright (C) 2011 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.filterfw.io;
import java.lang.Float;
import java.lang.Integer;
import java.lang.String;
import java.util.ArrayList;
import java.util.regex.Pattern;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterFactory;
import android.filterfw.core.FilterGraph;
import android.filterfw.core.KeyValueMap;
import android.filterfw.core.ProtocolException;
import android.filterfw.io.GraphReader;
import android.filterfw.io.GraphIOException;
import android.filterfw.io.PatternScanner;
/**
* @hide
*/
public class TextGraphReader extends GraphReader {
private ArrayList<Command> mCommands = new ArrayList<Command>();
private Filter mCurrentFilter;
private FilterGraph mCurrentGraph;
private KeyValueMap mBoundReferences;
private KeyValueMap mSettings;
private FilterFactory mFactory;
private interface Command {
public void execute(TextGraphReader reader) throws GraphIOException;
}
private class ImportPackageCommand implements Command {
private String mPackageName;
public ImportPackageCommand(String packageName) {
mPackageName = packageName;
}
@Override
public void execute(TextGraphReader reader) throws GraphIOException {
try {
reader.mFactory.addPackage(mPackageName);
} catch (IllegalArgumentException e) {
throw new GraphIOException(e.getMessage());
}
}
}
private class AddLibraryCommand implements Command {
private String mLibraryName;
public AddLibraryCommand(String libraryName) {
mLibraryName = libraryName;
}
@Override
public void execute(TextGraphReader reader) {
reader.mFactory.addFilterLibrary(mLibraryName);
}
}
private class AllocateFilterCommand implements Command {
private String mClassName;
private String mFilterName;
public AllocateFilterCommand(String className, String filterName) {
mClassName = className;
mFilterName = filterName;
}
public void execute(TextGraphReader reader) throws GraphIOException {
// Create the filter
Filter filter = null;
try {
filter = reader.mFactory.createFilterByClassName(mClassName, mFilterName);
} catch (IllegalArgumentException e) {
throw new GraphIOException(e.getMessage());
}
// Set it as the current filter
reader.mCurrentFilter = filter;
}
}
private class InitFilterCommand implements Command {
private KeyValueMap mParams;
public InitFilterCommand(KeyValueMap params) {
mParams = params;
}
@Override
public void execute(TextGraphReader reader) throws GraphIOException {
Filter filter = reader.mCurrentFilter;
try {
filter.initWithValueMap(mParams);
} catch (ProtocolException e) {
throw new GraphIOException(e.getMessage());
}
reader.mCurrentGraph.addFilter(mCurrentFilter);
}
}
private class ConnectCommand implements Command {
private String mSourceFilter;
private String mSourcePort;
private String mTargetFilter;
private String mTargetName;
public ConnectCommand(String sourceFilter,
String sourcePort,
String targetFilter,
String targetName) {
mSourceFilter = sourceFilter;
mSourcePort = sourcePort;
mTargetFilter = targetFilter;
mTargetName = targetName;
}
@Override
public void execute(TextGraphReader reader) {
reader.mCurrentGraph.connect(mSourceFilter, mSourcePort, mTargetFilter, mTargetName);
}
}
@Override
public FilterGraph readGraphString(String graphString) throws GraphIOException {
FilterGraph result = new FilterGraph();
reset();
mCurrentGraph = result;
parseString(graphString);
applySettings();
executeCommands();
reset();
return result;
}
private void reset() {
mCurrentGraph = null;
mCurrentFilter = null;
mCommands.clear();
mBoundReferences = new KeyValueMap();
mSettings = new KeyValueMap();
mFactory = new FilterFactory();
}
private void parseString(String graphString) throws GraphIOException {
final Pattern commandPattern = Pattern.compile("@[a-zA-Z]+");
final Pattern curlyClosePattern = Pattern.compile("\\}");
final Pattern curlyOpenPattern = Pattern.compile("\\{");
final Pattern ignorePattern = Pattern.compile("(\\s+|//[^\\n]*\\n)+");
final Pattern packageNamePattern = Pattern.compile("[a-zA-Z\\.]+");
final Pattern libraryNamePattern = Pattern.compile("[a-zA-Z\\./:]+");
final Pattern portPattern = Pattern.compile("\\[[a-zA-Z0-9\\-_]+\\]");
final Pattern rightArrowPattern = Pattern.compile("=>");
final Pattern semicolonPattern = Pattern.compile(";");
final Pattern wordPattern = Pattern.compile("[a-zA-Z0-9\\-_]+");
final int STATE_COMMAND = 0;
final int STATE_IMPORT_PKG = 1;
final int STATE_ADD_LIBRARY = 2;
final int STATE_FILTER_CLASS = 3;
final int STATE_FILTER_NAME = 4;
final int STATE_CURLY_OPEN = 5;
final int STATE_PARAMETERS = 6;
final int STATE_CURLY_CLOSE = 7;
final int STATE_SOURCE_FILTERNAME = 8;
final int STATE_SOURCE_PORT = 9;
final int STATE_RIGHT_ARROW = 10;
final int STATE_TARGET_FILTERNAME = 11;
final int STATE_TARGET_PORT = 12;
final int STATE_ASSIGNMENT = 13;
final int STATE_EXTERNAL = 14;
final int STATE_SETTING = 15;
final int STATE_SEMICOLON = 16;
int state = STATE_COMMAND;
PatternScanner scanner = new PatternScanner(graphString, ignorePattern);
String curClassName = null;
String curSourceFilterName = null;
String curSourcePortName = null;
String curTargetFilterName = null;
String curTargetPortName = null;
// State machine main loop
while (!scanner.atEnd()) {
switch (state) {
case STATE_COMMAND: {
String curCommand = scanner.eat(commandPattern, "<command>");
if (curCommand.equals("@import")) {
state = STATE_IMPORT_PKG;
} else if (curCommand.equals("@library")) {
state = STATE_ADD_LIBRARY;
} else if (curCommand.equals("@filter")) {
state = STATE_FILTER_CLASS;
} else if (curCommand.equals("@connect")) {
state = STATE_SOURCE_FILTERNAME;
} else if (curCommand.equals("@set")) {
state = STATE_ASSIGNMENT;
} else if (curCommand.equals("@external")) {
state = STATE_EXTERNAL;
} else if (curCommand.equals("@setting")) {
state = STATE_SETTING;
} else {
throw new GraphIOException("Unknown command '" + curCommand + "'!");
}
break;
}
case STATE_IMPORT_PKG: {
String packageName = scanner.eat(packageNamePattern, "<package-name>");
mCommands.add(new ImportPackageCommand(packageName));
state = STATE_SEMICOLON;
break;
}
case STATE_ADD_LIBRARY: {
String libraryName = scanner.eat(libraryNamePattern, "<library-name>");
mCommands.add(new AddLibraryCommand(libraryName));
state = STATE_SEMICOLON;
break;
}
case STATE_FILTER_CLASS:
curClassName = scanner.eat(wordPattern, "<class-name>");
state = STATE_FILTER_NAME;
break;
case STATE_FILTER_NAME: {
String curFilterName = scanner.eat(wordPattern, "<filter-name>");
mCommands.add(new AllocateFilterCommand(curClassName, curFilterName));
state = STATE_CURLY_OPEN;
break;
}
case STATE_CURLY_OPEN:
scanner.eat(curlyOpenPattern, "{");
state = STATE_PARAMETERS;
break;
case STATE_PARAMETERS: {
KeyValueMap params = readKeyValueAssignments(scanner, curlyClosePattern);
mCommands.add(new InitFilterCommand(params));
state = STATE_CURLY_CLOSE;
break;
}
case STATE_CURLY_CLOSE:
scanner.eat(curlyClosePattern, "}");
state = STATE_COMMAND;
break;
case STATE_SOURCE_FILTERNAME:
curSourceFilterName = scanner.eat(wordPattern, "<source-filter-name>");
state = STATE_SOURCE_PORT;
break;
case STATE_SOURCE_PORT: {
String portString = scanner.eat(portPattern, "[<source-port-name>]");
curSourcePortName = portString.substring(1, portString.length() - 1);
state = STATE_RIGHT_ARROW;
break;
}
case STATE_RIGHT_ARROW:
scanner.eat(rightArrowPattern, "=>");
state = STATE_TARGET_FILTERNAME;
break;
case STATE_TARGET_FILTERNAME:
curTargetFilterName = scanner.eat(wordPattern, "<target-filter-name>");
state = STATE_TARGET_PORT;
break;
case STATE_TARGET_PORT: {
String portString = scanner.eat(portPattern, "[<target-port-name>]");
curTargetPortName = portString.substring(1, portString.length() - 1);
mCommands.add(new ConnectCommand(curSourceFilterName,
curSourcePortName,
curTargetFilterName,
curTargetPortName));
state = STATE_SEMICOLON;
break;
}
case STATE_ASSIGNMENT: {
KeyValueMap assignment = readKeyValueAssignments(scanner, semicolonPattern);
mBoundReferences.putAll(assignment);
state = STATE_SEMICOLON;
break;
}
case STATE_EXTERNAL: {
String externalName = scanner.eat(wordPattern, "<external-identifier>");
bindExternal(externalName);
state = STATE_SEMICOLON;
break;
}
case STATE_SETTING: {
KeyValueMap setting = readKeyValueAssignments(scanner, semicolonPattern);
mSettings.putAll(setting);
state = STATE_SEMICOLON;
break;
}
case STATE_SEMICOLON:
scanner.eat(semicolonPattern, ";");
state = STATE_COMMAND;
break;
}
}
// Make sure end of input was expected
if (state != STATE_SEMICOLON && state != STATE_COMMAND) {
throw new GraphIOException("Unexpected end of input!");
}
}
@Override
public KeyValueMap readKeyValueAssignments(String assignments) throws GraphIOException {
final Pattern ignorePattern = Pattern.compile("\\s+");
PatternScanner scanner = new PatternScanner(assignments, ignorePattern);
return readKeyValueAssignments(scanner, null);
}
private KeyValueMap readKeyValueAssignments(PatternScanner scanner,
Pattern endPattern) throws GraphIOException {
// Our parser is a state-machine, and these are our states
final int STATE_IDENTIFIER = 0;
final int STATE_EQUALS = 1;
final int STATE_VALUE = 2;
final int STATE_POST_VALUE = 3;
final Pattern equalsPattern = Pattern.compile("=");
final Pattern semicolonPattern = Pattern.compile(";");
final Pattern wordPattern = Pattern.compile("[a-zA-Z]+[a-zA-Z0-9]*");
final Pattern stringPattern = Pattern.compile("'[^']*'|\\\"[^\\\"]*\\\"");
final Pattern intPattern = Pattern.compile("[0-9]+");
final Pattern floatPattern = Pattern.compile("[0-9]*\\.[0-9]+f?");
final Pattern referencePattern = Pattern.compile("\\$[a-zA-Z]+[a-zA-Z0-9]");
final Pattern booleanPattern = Pattern.compile("true|false");
int state = STATE_IDENTIFIER;
KeyValueMap newVals = new KeyValueMap();
String curKey = null;
String curValue = null;
while (!scanner.atEnd() && !(endPattern != null && scanner.peek(endPattern))) {
switch (state) {
case STATE_IDENTIFIER:
curKey = scanner.eat(wordPattern, "<identifier>");
state = STATE_EQUALS;
break;
case STATE_EQUALS:
scanner.eat(equalsPattern, "=");
state = STATE_VALUE;
break;
case STATE_VALUE:
if ((curValue = scanner.tryEat(stringPattern)) != null) {
newVals.put(curKey, curValue.substring(1, curValue.length() - 1));
} else if ((curValue = scanner.tryEat(referencePattern)) != null) {
String refName = curValue.substring(1, curValue.length());
Object referencedObject = mBoundReferences != null
? mBoundReferences.get(refName)
: null;
if (referencedObject == null) {
throw new GraphIOException(
"Unknown object reference to '" + refName + "'!");
}
newVals.put(curKey, referencedObject);
} else if ((curValue = scanner.tryEat(booleanPattern)) != null) {
newVals.put(curKey, Boolean.parseBoolean(curValue));
} else if ((curValue = scanner.tryEat(floatPattern)) != null) {
newVals.put(curKey, Float.parseFloat(curValue));
} else if ((curValue = scanner.tryEat(intPattern)) != null) {
newVals.put(curKey, Integer.parseInt(curValue));
} else {
throw new GraphIOException(scanner.unexpectedTokenMessage("<value>"));
}
state = STATE_POST_VALUE;
break;
case STATE_POST_VALUE:
scanner.eat(semicolonPattern, ";");
state = STATE_IDENTIFIER;
break;
}
}
// Make sure end is expected
if (state != STATE_IDENTIFIER && state != STATE_POST_VALUE) {
throw new GraphIOException(
"Unexpected end of assignments on line " + scanner.lineNo() + "!");
}
return newVals;
}
private void bindExternal(String name) throws GraphIOException {
if (mReferences.containsKey(name)) {
Object value = mReferences.get(name);
mBoundReferences.put(name, value);
} else {
throw new GraphIOException("Unknown external variable '" + name + "'! "
+ "You must add a reference to this external in the host program using "
+ "addReference(...)!");
}
}
/**
* Unused for now: Often you may want to declare references that are NOT in a certain graph,
* e.g. when reading multiple graphs with the same reader. We could print a warning, but even
* that may be too much.
**/
private void checkReferences() throws GraphIOException {
for (String reference : mReferences.keySet()) {
if (!mBoundReferences.containsKey(reference)) {
throw new GraphIOException(
"Host program specifies reference to '" + reference + "', which is not "
+ "declared @external in graph file!");
}
}
}
private void applySettings() throws GraphIOException {
for (String setting : mSettings.keySet()) {
Object value = mSettings.get(setting);
if (setting.equals("autoBranch")) {
expectSettingClass(setting, value, String.class);
if (value.equals("synced")) {
mCurrentGraph.setAutoBranchMode(FilterGraph.AUTOBRANCH_SYNCED);
} else if (value.equals("unsynced")) {
mCurrentGraph.setAutoBranchMode(FilterGraph.AUTOBRANCH_UNSYNCED);
} else if (value.equals("off")) {
mCurrentGraph.setAutoBranchMode(FilterGraph.AUTOBRANCH_OFF);
} else {
throw new GraphIOException("Unknown autobranch setting: " + value + "!");
}
} else if (setting.equals("discardUnconnectedOutputs")) {
expectSettingClass(setting, value, Boolean.class);
mCurrentGraph.setDiscardUnconnectedOutputs((Boolean)value);
} else {
throw new GraphIOException("Unknown @setting '" + setting + "'!");
}
}
}
private void expectSettingClass(String setting,
Object value,
Class expectedClass) throws GraphIOException {
if (value.getClass() != expectedClass) {
throw new GraphIOException("Setting '" + setting + "' must have a value of type "
+ expectedClass.getSimpleName() + ", but found a value of type "
+ value.getClass().getSimpleName() + "!");
}
}
private void executeCommands() throws GraphIOException {
for (Command command : mCommands) {
command.execute(this);
}
}
}