1package com.xtremelabs.robolectric.res;
2
3import java.io.File;
4import java.util.ArrayList;
5import java.util.HashMap;
6import java.util.List;
7import java.util.Map;
8
9import org.w3c.dom.Document;
10import org.w3c.dom.NamedNodeMap;
11import org.w3c.dom.Node;
12import org.w3c.dom.NodeList;
13
14import android.content.Context;
15import android.text.TextUtils;
16import android.view.Menu;
17import android.view.MenuItem;
18import android.view.SubMenu;
19
20import com.xtremelabs.robolectric.tester.android.util.TestAttributeSet;
21import com.xtremelabs.robolectric.util.I18nException;
22
23public 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