1 package com.xtremelabs.robolectric.res; 2 3 import java.io.File; 4 import java.util.ArrayList; 5 import java.util.HashMap; 6 import java.util.List; 7 import java.util.Map; 8 9 import org.w3c.dom.Document; 10 import org.w3c.dom.NamedNodeMap; 11 import org.w3c.dom.Node; 12 import org.w3c.dom.NodeList; 13 14 import android.content.Context; 15 import android.text.TextUtils; 16 import android.view.Menu; 17 import android.view.MenuItem; 18 import android.view.SubMenu; 19 20 import com.xtremelabs.robolectric.tester.android.util.TestAttributeSet; 21 import com.xtremelabs.robolectric.util.I18nException; 22 23 public class MenuLoader extends XmlLoader { 24 private Map<String, MenuNode> menuNodesByMenuName = new HashMap<String, MenuNode>(); 25 private AttrResourceLoader attrResourceLoader; 26 27 public MenuLoader(ResourceExtractor resourceExtractor, AttrResourceLoader attrResourceLoader) { 28 super(resourceExtractor); 29 this.attrResourceLoader = attrResourceLoader; 30 } 31 32 @Override 33 protected void processResourceXml(File xmlFile, Document document, boolean ignored) throws Exception { 34 MenuNode topLevelNode = new MenuNode("top-level", new HashMap<String, String>()); 35 36 NodeList items = document.getChildNodes(); 37 if (items.getLength() != 1) 38 throw new RuntimeException("Expected only one top-level item in menu file " + xmlFile.getName()); 39 if (items.item(0).getNodeName().compareTo("menu") != 0) 40 throw new RuntimeException("Expected a top-level item called 'menu' in menu file " + xmlFile.getName()); 41 42 processChildren(items.item(0).getChildNodes(), topLevelNode); 43 menuNodesByMenuName.put("menu/" + xmlFile.getName().replace(".xml", ""), topLevelNode); 44 } 45 46 private void processChildren(NodeList childNodes, MenuNode parent) { 47 for (int i = 0; i < childNodes.getLength(); i++) { 48 Node node = childNodes.item(i); 49 processNode(node, parent); 50 } 51 } 52 53 private void processNode(Node node, MenuNode parent) { 54 String name = node.getNodeName(); 55 NamedNodeMap attributes = node.getAttributes(); 56 Map<String, String> attrMap = new HashMap<String, String>(); 57 if (attributes != null) { 58 int length = attributes.getLength(); 59 for (int i = 0; i < length; i++) { 60 Node attr = attributes.item(i); 61 attrMap.put(attr.getNodeName(), attr.getNodeValue()); 62 } 63 } 64 65 if (!name.startsWith("#")) { 66 MenuNode menuNode = new MenuNode(name, attrMap); 67 parent.addChild(menuNode); 68 NodeList children = node.getChildNodes(); 69 if (children != null && children.getLength() != 0) { 70 for (int i = 0; i < children.getLength(); i++) { 71 Node nodei = children.item(i); 72 if (childToIgnore(nodei)) { 73 continue; 74 } else if (validChildren(nodei)) { 75 // recursively add all nodes 76 processNode(nodei, menuNode); 77 } else { 78 throw new RuntimeException("Unknown menu node" 79 + nodei.getNodeName()); 80 } 81 } 82 } 83 } 84 } 85 86 private static boolean childToIgnore(Node nodei) { 87 return TextUtils.isEmpty(nodei.getNodeName()) 88 || nodei.getNodeName().startsWith("#"); 89 } 90 91 private static boolean validChildren(Node nodei) { 92 return nodei.getNodeName().equals("item") 93 || nodei.getNodeName().equals("menu") 94 || nodei.getNodeName().equals("group"); 95 } 96 97 public void inflateMenu(Context context, String key, Menu root) { 98 inflateMenu(context, key, null, root); 99 } 100 101 public void inflateMenu(Context context, int resourceId, Menu root) { 102 inflateMenu(context, resourceExtractor.getResourceName(resourceId), 103 root); 104 } 105 106 private void inflateMenu(Context context, String key, 107 Map<String, String> attributes, Menu root) { 108 MenuNode menuNode = menuNodesByMenuName.get(key); 109 if (menuNode == null) { 110 throw new RuntimeException("Could not find menu " + key); 111 } 112 try { 113 if (attributes != null) { 114 for (Map.Entry<String, String> entry : attributes.entrySet()) { 115 if (!entry.getKey().equals("menu")) { 116 menuNode.attributes.put(entry.getKey(), 117 entry.getValue()); 118 } 119 } 120 } 121 menuNode.inflate(context, root); 122 } catch (I18nException e) { 123 throw e; 124 } catch (Exception e) { 125 throw new RuntimeException("error inflating " + key, e); 126 } 127 } 128 129 public class MenuNode { 130 private String name; 131 private final TestAttributeSet attributes; 132 133 private List<MenuNode> children = new ArrayList<MenuNode>(); 134 135 public MenuNode(String name, Map<String, String> attributes) { 136 this.name = name; 137 this.attributes = new TestAttributeSet(attributes, 138 resourceExtractor, attrResourceLoader, null, false); 139 } 140 141 public List<MenuNode> getChildren() { 142 return children; 143 } 144 145 public void addChild(MenuNode MenuNode) { 146 children.add(MenuNode); 147 } 148 149 private boolean isSubMenuItem(MenuNode child) { 150 List<MenuLoader.MenuNode> ch = child.children; 151 return ch != null && ch.size() == 1 && "menu".equals(ch.get(0).name); 152 } 153 154 private void addChildrenInGroup(MenuNode source, int groupId, Menu root) { 155 for (MenuNode child : source.children) { 156 String name = child.name; 157 TestAttributeSet attributes = child.attributes; 158 if (strictI18n) { 159 attributes.validateStrictI18n(); 160 } 161 if (name.equals("item")) { 162 if (isSubMenuItem(child)) { 163 SubMenu sub = root.addSubMenu(groupId, attributes 164 .getAttributeResourceValue("android", "id", 0), 165 0, attributes.getAttributeValue("android", "title")); 166 MenuNode subMenuNode = child.children.get(0); 167 addChildrenInGroup(subMenuNode, groupId, sub); 168 } else { 169 MenuItem menuItem = root.add(groupId, attributes 170 .getAttributeResourceValue("android", "id", 0), 171 0, attributes.getAttributeValue("android", "title")); 172 } 173 } else if (name.equals("group")) { 174 int newGroupId = attributes.getAttributeResourceValue("android", "id", 0); 175 addChildrenInGroup(child, newGroupId, root); 176 } 177 } 178 } 179 180 public void inflate(Context context, Menu root) throws Exception { 181 addChildrenInGroup(this, 0, root); 182 } 183 } 184 } 185