/* * 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.generatemenucode.model; import java.io.File; import java.io.IOException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.motorola.studio.android.common.exception.AndroidException; import com.motorola.studio.android.common.log.StudioLogger; import com.motorola.studio.android.generatecode.AndroidXMLFileConstants; /** * Represents a single menu.xml file. * Based on Android documentation: {@link http://developer.android.com/guide/topics/resources/menu-resource.html}. */ public class MenuFile { private final String name; private final File file; private MenuNode rootMenuNode; /** * @param menuFileName name that may appear into the dialog to create code based on menu * @param menuFilePath absolute file path to menu.xml * @throws AndroidException fail to parse menu.xml */ public MenuFile(String menuFileName, File menuFilePath) throws AndroidException { this.name = menuFileName; this.file = menuFilePath; rootMenuNode = parseDocument(file); } /** * @return the rootMenuNode the in-memory representation from menu.xml */ public MenuNode getRootMenuNode() { return rootMenuNode; } /** * The path to file (relative to project) */ public String getName() { return name; } /** * The path to file (relative to project) */ public String getNameWithoutExtension() { String result; if ((name != null) && name.contains(".")) { result = name.substring(0, name.lastIndexOf('.')); } else { result = name; } return result; } public File getFile() { return file; } /** * Parses an IDocument object containing the menu.xml into a DOM * * @param document the IDocument object * @throws SAXException When a parsing error occurs * @throws IOException When a reading error occurs */ private static final MenuNode parseDocument(File f) throws AndroidException { MenuNode mainMenuNode = null; DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); Document doc = null; try { DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); doc = dBuilder.parse(f); doc.getDocumentElement().normalize(); } catch (Exception e) { StudioLogger.error(MenuFile.class, "Error parsing menu: " + e.getMessage()); throw new AndroidException(e); } Node node = doc.getFirstChild(); mainMenuNode = (MenuNode) readAttributes(node, null); populateNodeForRootMenuNode(mainMenuNode, node); return mainMenuNode; } /** * Populates information about menus initiating recursion (start on menu node that is in the root of the file) * @param mainMenuNode * @param node */ public static void populateNodeForRootMenuNode(MenuNode mainMenuNode, Node node) { NodeList children = node.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { node = children.item(i); if ((node != null) && (node.getNodeType() == Node.ELEMENT_NODE)) { AbstractMenuNode menuNode = readAttributes(node, mainMenuNode); if (node.hasChildNodes()) { //navigate in deep in the tree, using current menuNode as parent node populateNodes(node.getChildNodes(), menuNode); } } } } /** * Populates information about menus on non-root nodes * @param children * @param parentNode */ private static final void populateNodes(NodeList children, AbstractMenuNode parentNode) { Node node; for (int i = 0; i < children.getLength(); i++) { node = children.item(i); if ((node != null) && (node.getNodeType() == Node.ELEMENT_NODE)) { AbstractMenuNode menuNode = readAttributes(node, parentNode); if (node.hasChildNodes()) { //navigate in deep in the tree, using current menuNode as parent node populateNodes(node.getChildNodes(), menuNode); } } } } /** * Reads attributes that are relevant to generate code based on menu * @param node * @param parentNode null if the root node, non-null if internal node * @return current node being navigated */ private static AbstractMenuNode readAttributes(Node node, AbstractMenuNode parentNode) { AbstractMenuNode currentMenuNode = getMenuNode(node.getNodeName()); Node id = node.getAttributes().getNamedItem(AndroidXMLFileConstants.ANDROID_ID); if ((id != null)) { String idText = id.getNodeValue(); idText = idText.replace(AndroidXMLFileConstants.IDENTIFIER, ""); if (currentMenuNode instanceof MenuItemNode) { MenuItemNode menuItemNode = (MenuItemNode) currentMenuNode; menuItemNode.setId(idText); Node onClick = node.getAttributes().getNamedItem(AndroidXMLFileConstants.ANDROID_ON_CLICK); if (onClick != null) { menuItemNode.setOnClickMethod(onClick.getNodeValue()); } } else if (currentMenuNode instanceof GroupNode) { GroupNode groupNode = (GroupNode) currentMenuNode; groupNode.setId(idText); } } if (parentNode != null) { //if internal node => set its parent appendItemNodeStructure(parentNode, currentMenuNode); } return currentMenuNode; } /** * Appends current node into tree representation * @param parentNode * @param currentMenuNode */ public static void appendItemNodeStructure(AbstractMenuNode parentNode, AbstractMenuNode currentMenuNode) { if (parentNode instanceof MenuNode) { MenuNode menuNode = (MenuNode) parentNode; menuNode.add(currentMenuNode); } else if ((parentNode instanceof GroupNode) && (currentMenuNode instanceof MenuItemNode)) { GroupNode groupNode = (GroupNode) parentNode; groupNode.add((MenuItemNode) currentMenuNode); } else if ((parentNode instanceof MenuItemNode) && (currentMenuNode instanceof MenuNode)) { MenuItemNode menuItemNode = (MenuItemNode) parentNode; menuItemNode.setSubMenu((MenuNode) currentMenuNode); } } /** * Gets the type of the node, which can be menu, item or group * @param nodeName * @return */ private static AbstractMenuNode getMenuNode(String nodeName) { AbstractMenuNode node = null; if (nodeName.equals(AbstractMenuNode.MenuNodeType.menu.name())) { node = new MenuNode(); } else if (nodeName.equals(AbstractMenuNode.MenuNodeType.item.name())) { node = new MenuItemNode(); } else if (nodeName.equals(AbstractMenuNode.MenuNodeType.group.name())) { node = new GroupNode(); } return node; } }