summaryrefslogtreecommitdiff
path: root/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/LayoutFileParser.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/LayoutFileParser.java')
-rw-r--r--src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/LayoutFileParser.java552
1 files changed, 552 insertions, 0 deletions
diff --git a/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/LayoutFileParser.java b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/LayoutFileParser.java
new file mode 100644
index 0000000..92d7664
--- /dev/null
+++ b/src/plugins/emulator/src/com/motorola/studio/android/emulator/skin/android/parser/LayoutFileParser.java
@@ -0,0 +1,552 @@
+/*
+* Copyright (C) 2012 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 com.motorola.studio.android.emulator.skin.android.parser;
+
+import static com.motorola.studio.android.common.log.StudioLogger.error;
+import static com.motorola.studio.android.common.log.StudioLogger.warn;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.net.URL;
+import java.nio.CharBuffer;
+import java.util.Collection;
+import java.util.EmptyStackException;
+import java.util.Stack;
+
+import com.motorola.studio.android.common.log.StudioLogger;
+import com.motorola.studio.android.emulator.EmulatorPlugin;
+import com.motorola.studio.android.emulator.core.exception.SkinException;
+import com.motorola.studio.android.emulator.i18n.EmulatorNLS;
+
+/**
+ * DESCRIPTION:
+ * This class parses a layout file into a LayoutFileModel object
+ *
+ * RESPONSIBILITY:
+ * Parse the layout file
+ *
+ * COLABORATORS:
+ * None.
+ *
+ * USAGE:
+ * Call readLayout method passing a file to be parsed and retrieve the
+ * correspondent LayoutFileModel object
+ */
+public class LayoutFileParser implements ILayoutConstants
+{
+ /**
+ * Name of the layout descriptor file at the skin folder
+ */
+ private static final String LAYOUT_FILE_NAME = "layout";
+
+ /**
+ * Name of the pseudo layout descriptor file at the res folder
+ */
+ private static final String PSEUDO_LAYOUT_FILE = "res/pseudolayout";
+
+ /**
+ * The pattern used for generating tokens out of the layout file
+ */
+ private static final String SPLIT_PATTERN = "[\n\r \t]+";
+
+ /**
+ * Parses a layout file
+ *
+ * @param skinFilesPath The path to the skin folder
+ *
+ * @return a model containing all data read from the layout file
+ *
+ * @throws SkinException If it is not possible to read the layout file
+ */
+ public static LayoutFileModel readLayout(File skinFilesPath) throws SkinException
+ {
+ LayoutFileModel model = new LayoutFileModel();
+ File layoutPath = new File(skinFilesPath, LAYOUT_FILE_NAME);
+ String fileContents;
+ if ((layoutPath != null) && (layoutPath.isFile()))
+ {
+ fileContents = getLayoutFileContents(layoutPath);
+ parseLayoutFile(fileContents, model);
+ }
+
+ Collection<String> partNames = model.getPartNames();
+ if ((model.getLayoutNames().size() == 0) && (partNames.size() == 1)
+ && (partNames.iterator().next().equals(PartBean.UNIQUE_PART)))
+ {
+ fileContents = getPseudoLayoutFileContents();
+ parseLayoutFile(fileContents, model);
+ }
+
+ return model;
+ }
+
+ /**
+ * Parses the provided layout file contents
+ *
+ * @param fileContents All the contents of a layout file
+ * @param model The model where to set the parsed data
+ *
+ * @throws SkinException If the layout file is corrupted, or has erroneous syntax
+ */
+ private static void parseLayoutFile(String fileContents, LayoutFileModel model)
+ throws SkinException
+ {
+ // process given string to remove comments
+ String cleanContents = "";
+ BufferedReader reader = null;
+ try
+ {
+ StringBuffer contentBuffer = new StringBuffer();
+ reader = new BufferedReader(new StringReader(fileContents));
+ String line = null;
+ do
+ {
+ line = reader.readLine();
+ String lineCopy = line;
+ if ((line != null) && !lineCopy.trim().startsWith("#"))
+ {
+ contentBuffer.append(line + '\n');
+ }
+ }
+ while (line != null);
+ cleanContents = contentBuffer.toString();
+ }
+ catch (IOException e)
+ {
+ //try to continue with the parser
+ cleanContents = fileContents;
+ }
+ finally
+ {
+ try
+ {
+ reader.close();
+ }
+ catch (IOException e)
+ {
+ StudioLogger.error("Could not close input stream: ", e.getMessage()); //$NON-NLS-1$
+ }
+ }
+
+ String[] tokens = cleanContents.split(SPLIT_PATTERN);
+
+ Stack<Object> stack = new Stack<Object>();
+
+ // At this point, the file has been read into hundreds of token, including blocks,
+ // "{", "}", keys and values
+
+ String currentTag = null;
+ String key = null;
+
+ // Iterate on the tokens
+ try
+ {
+ for (String aToken : tokens)
+ {
+ // When the token is a "{", that means we need to stack something. This "something"
+ // will be removed from stack when we find a matching "}"
+ if (OPEN_BRACKET.equals(aToken))
+ {
+ // Every word is interpreted as a key at first. If we find a "{", it must be
+ // re-interpreted as a tag instead
+ if (key != null)
+ {
+ currentTag = key;
+ key = null;
+ }
+ addElementsToStack(stack, model, currentTag);
+ }
+ // When the token is a "}" we must remove something from the stack
+ else if (CLOSE_BRACKET.equals(aToken))
+ {
+ removeElementsFromStack(stack);
+ }
+ else
+ {
+ // A word is interpreted as a key by default. If the key is already set, we will
+ // have a key-value pair and are able to assign it to something at the model
+ if (key == null)
+ {
+ key = aToken;
+ }
+ else
+ {
+ setKeyValuePair(stack, model, currentTag, key, aToken);
+ key = null;
+ }
+ }
+ }
+
+ }
+ catch (EmptyStackException e)
+ {
+ throw new SkinException(EmulatorNLS.ERR_LayoutFileParser_BracketsDoNotMatch);
+ }
+
+ if (!stack.isEmpty())
+ {
+ // When there is only a part bean at the first level, that means we have finished
+ // parsing a single part layout. Remove it from the stack as well.
+ //
+ // NOTE: when creating the single part layout, we have added this additional element
+ // to the stack
+ if ((stack.size() == 1) && (stack.get(0) instanceof PartBean)
+ && (((PartBean) stack.get(0)).getName().equals(PartBean.UNIQUE_PART)))
+ {
+ stack.pop();
+ }
+ else
+ {
+ throw new SkinException(EmulatorNLS.ERR_LayoutFileParser_BracketsDoNotMatch);
+ }
+ }
+
+ }
+
+ /**
+ * Reads the contents of the provided file into a String object
+ *
+ * @param layoutPath A file pointing to an "layout" file
+ *
+ * @return A string with all the contents of the file
+ *
+ * @throws SkinException If the file cannot be read
+ */
+ private static String getLayoutFileContents(File layoutPath) throws SkinException
+ {
+ int fileSize = (int) layoutPath.length();
+ char[] buffer = new char[fileSize];
+
+ FileReader fr = null;
+ try
+ {
+ fr = new FileReader(layoutPath);
+ fr.read(buffer);
+ }
+ catch (IOException e)
+ {
+ error("The file " + layoutPath.getAbsolutePath() + " could not be read. cause="
+ + e.getMessage());
+ throw new SkinException(EmulatorNLS.ERR_LayoutFileParser_LayoutFileCouldNotBeRead);
+ }
+ finally
+ {
+ try
+ {
+ if (fr != null)
+ {
+ fr.close();
+ }
+ }
+ catch (IOException e)
+ {
+ warn("The file " + layoutPath.getAbsolutePath()
+ + " could not be closed after reading");
+ }
+ }
+
+ return String.copyValueOf(buffer);
+ }
+
+ /**
+ * Gets the contents of the pseudo layout file, for merging to the current model
+ *
+ * @return A string containing all the contents of the pseudo layout file
+ *
+ * @throws SkinException If the file cannot be read
+ */
+ private static String getPseudoLayoutFileContents() throws SkinException
+ {
+ URL url = EmulatorPlugin.getDefault().getBundle().getResource(PSEUDO_LAYOUT_FILE);
+ CharBuffer buffer = CharBuffer.allocate(1024);
+ int readChars = 0;
+
+ InputStream is = null;
+ InputStreamReader isr = null;
+ try
+ {
+ is = url.openStream();
+ isr = new InputStreamReader(is);
+ while (readChars != -1)
+ {
+ readChars = isr.read(buffer);
+ }
+ buffer.flip();
+ }
+ catch (IOException e)
+ {
+ error("The file res/pseudolayout could not be read. cause=" + e.getMessage());
+ throw new SkinException(EmulatorNLS.ERR_LayoutFileParser_LayoutFileCouldNotBeRead);
+ }
+ finally
+ {
+ try
+ {
+ if (is != null)
+ {
+ is.close();
+ }
+ if (isr != null)
+ {
+ isr.close();
+ }
+ }
+ catch (IOException e)
+ {
+ warn("The file " + PSEUDO_LAYOUT_FILE + " could not be closed after reading");
+ }
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * Stacks an element
+ * The stack rules are quite complex. We first start by special cases (in which we analyze the stack
+ * and the current element for accurate interpretation) and then move to the default cases.
+ *
+ * Summarizing, the stack will contain objects from this package (*Bean) as well as String objects.
+ * When a bean is at the top of the stack, we may perform actions on the given object. Strings are added
+ * to the stack for bracket matching and to add a mark for future actions.
+ *
+ * @param stack The stack where to add elements
+ * @param model The model being built
+ * @param elementName The name of the element to add to stack.
+ */
+ private static void addElementsToStack(Stack<Object> stack, LayoutFileModel model,
+ String elementName)
+ {
+ int stackSizeAtStart = stack.size();
+
+ //--------------
+ // SPECIAL CASES
+ //--------------
+
+ // When the stack size is equal to zero, we can have one of those two situations:
+ //
+ // a) THE LAYOUT FILE CONTAINS MULTIPLE LAYOUT AND/OR PARTS: It is possible to have the
+ // following tags: "parts", "layouts", "keyboard" or "network". All of them are handled in the
+ // else clause, by adding the tag name at the stack
+ //
+ // b) THE LAYOUT FILE IS SIMPLE (i.e. it doesn't contain layouts, neither a collection
+ // of parts): It is possible to have the following tags: "display", "background", "button",
+ // "keyboard", "network". The first three belong to a part definition, so we need to include a
+ // PartBean to the stack before the object representing the tag. The last two can be handled the same
+ // way as in item (a)
+ if (stack.size() == 0)
+ {
+ if ((MAIN_LEVEL_DISPLAY.equals(elementName))
+ || (MAIN_LEVEL_BACKGROUND.equals(elementName))
+ || (MAIN_LEVEL_BUTTON.equals(elementName)))
+ {
+ // This is a single part layout. Execute operation described at item (b) above
+ PartBean bean = model.newPart();
+ stack.push(bean);
+
+ if ((MAIN_LEVEL_BACKGROUND.equals(elementName))
+ || (MAIN_LEVEL_BUTTON.equals(elementName)))
+ {
+ stack.push(elementName);
+ }
+ else if (MAIN_LEVEL_DISPLAY.equals(elementName))
+ {
+ RectangleBean display = bean.newDisplay();
+ stack.push(display);
+ }
+ }
+ else
+ {
+ // PARTS, LAYOUTS, KEYBOARD, NETWORK
+ stack.push(elementName);
+ }
+ }
+
+ // When the stack size is equal to one, we can have one of those four situations:
+ //
+ // a) THE ELEMENT AT STACK IS NOT A STRING: In this case, we will handle as default case
+ // b) THE ELEMENT AT STACK IS THE "parts" STRING: It means that the element name denotes the name of
+ // a part. We must create a part with the name of the element, and add it to the stack
+ // c) THE ELEMENT AT STACK IS THE "layouts" STRING: It means that the element name denotes the name of
+ // a layout. We must create a layout with the name of the element, and add it to the stack
+ // d) THE ELEMENT AT STACK IS ANY OTHER STRING: In this case, we will handle as default case
+ else if (stack.size() == 1)
+ {
+ Object previousElement = stack.peek();
+ if (previousElement instanceof String)
+ {
+ if (MAIN_LEVEL_PARTS.equals((String) previousElement))
+ {
+ // elementName is the name of a new part
+ PartBean bean = model.newPart(elementName);
+ stack.push(bean);
+ }
+ else if (MAIN_LEVEL_LAYOUTS.equals((String) previousElement))
+ {
+ // elementName is the name of a new layout
+ LayoutBean bean = model.newLayout(elementName);
+ stack.push(bean);
+ }
+ }
+ }
+
+ //--------------
+ // DEFAULT CASES
+ //--------------
+
+ // Any other case will be handled below. The following clauses cover any other remaining cases not
+ // covered by the special cases. The beans created, when added to the stack, represents structures
+ // already known. If it is not possible to guess what structure we need at the current parse iteration
+ // or if we need an element at the stack to match a close bracket to come, we simply add it as string
+ //
+ // We only execute the following block if the previous cases didn't affect the stack
+ if (stackSizeAtStart == stack.size())
+ {
+ Object stackElem = stack.peek();
+ if (stackElem instanceof PartBean)
+ {
+ if (MAIN_LEVEL_DISPLAY.equals(elementName))
+ {
+ RectangleBean display = ((PartBean) stackElem).newDisplay();
+ stack.push(display);
+ }
+ else if (MAIN_LEVEL_BACKGROUND.equals(elementName))
+ {
+ ImagePositionBean background =
+ ((PartBean) stackElem).newBackground(elementName);
+ stack.push(background);
+ }
+ else
+ {
+ stack.push(elementName);
+ }
+ }
+ else if (stackElem instanceof LayoutBean)
+ {
+ PartRefBean bean = ((LayoutBean) stackElem).newPartRef(elementName);
+ stack.push(bean);
+ }
+ else if (stackElem instanceof String)
+ {
+ if ((MAIN_LEVEL_BUTTON.equals((String) stackElem) || (PART_BUTTONS
+ .equals((String) stackElem))))
+ {
+ Object nonStringObj = findFirstNonStringAtStack(stack);
+ if (nonStringObj != null)
+ {
+ PartBean bean = (PartBean) nonStringObj;
+ ImagePositionBean button = bean.newButton(elementName);
+ stack.push(button);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes an element from the stack
+ *
+ * @param stack The stack from where to remove elements
+ */
+ private static void removeElementsFromStack(Stack<Object> stack)
+ {
+ stack.pop();
+ }
+
+ /**
+ * Set a key-value pair at the object at the top of the stack.
+ * Depending on the key-value pair, we may set attributes to the model itself
+ *
+ * @param stack The stack containing the element to have a property set
+ * @param model The model that can have a property set
+ * @param currentTag The name of the tag containing a model property
+ * @param key The property key
+ * @param value The property value
+ */
+ private static void setKeyValuePair(Stack<Object> stack, LayoutFileModel model,
+ String currentTag, String key, String value)
+ {
+ Object obj = stack.peek();
+ if (obj instanceof String)
+ {
+ Object notStringObj = findFirstNonStringAtStack(stack);
+
+ if (notStringObj instanceof ILayoutBean)
+ {
+ ((ILayoutBean) notStringObj).setKeyValue(key, value);
+ }
+ else
+ {
+ if (MAIN_LEVEL_NETWORK.equals(currentTag))
+ {
+ if (NETWORK_DELAY.equals(key))
+ {
+ model.setNetworkDelay(value);
+ }
+ else if (NETWORK_SPEED.equals(key))
+ {
+ model.setNetworkSpeed(value);
+ }
+ }
+ else if (MAIN_LEVEL_KEYBOARD.equals(currentTag))
+ {
+ if (KEYBOARD_CHARMAP.equals(key))
+ {
+ model.setKeyboardCharmap(value);
+ }
+ }
+ }
+ }
+ else
+ {
+ ((ILayoutBean) obj).setKeyValue(key, value);
+ }
+ }
+
+ /**
+ * Utility method for finding the first non-String object at the stack
+ *
+ * @param stack The stack were to find the first non-String at
+ *
+ * @return The non-String object
+ */
+ private static Object findFirstNonStringAtStack(Stack<Object> stack)
+ {
+ Object firstNonString = null;
+ Object tmpObj = null;
+
+ int i = stack.size() - 1;
+ while (i >= 0)
+ {
+ tmpObj = stack.get(i);
+ if (!(tmpObj instanceof String))
+ {
+ firstNonString = tmpObj;
+ break;
+ }
+ else
+ {
+ i--;
+ }
+ }
+
+ return firstNonString;
+ }
+}