1package com.xtremelabs.robolectric.res;
2
3import java.io.File;
4import java.lang.reflect.Constructor;
5import java.util.ArrayList;
6import java.util.HashMap;
7import java.util.List;
8import java.util.Map;
9
10import org.w3c.dom.Document;
11import org.w3c.dom.NamedNodeMap;
12import org.w3c.dom.Node;
13import org.w3c.dom.NodeList;
14
15import com.xtremelabs.robolectric.Robolectric;
16import com.xtremelabs.robolectric.tester.android.util.TestAttributeSet;
17import com.xtremelabs.robolectric.util.I18nException;
18
19import android.content.Context;
20import android.preference.Preference;
21import android.preference.PreferenceGroup;
22import android.preference.PreferenceScreen;
23import android.util.AttributeSet;
24
25public class PreferenceLoader extends XmlLoader {
26
27    private Map<String, PreferenceNode> prefNodesByResourceName = new HashMap<String, PreferenceNode>();
28
29	public PreferenceLoader(ResourceExtractor resourceExtractor) {
30		super(resourceExtractor);
31	}
32
33	@Override
34	protected void processResourceXml(File xmlFile, Document document, boolean isSystem) throws Exception {
35		PreferenceNode topLevelNode = new PreferenceNode("top-level", new HashMap<String, String>());
36		processChildren(document.getChildNodes(), topLevelNode);
37		prefNodesByResourceName.put( "xml/" + xmlFile.getName().replace(".xml", ""), topLevelNode.getChildren().get(0));
38	}
39
40    private void processChildren(NodeList childNodes, PreferenceNode parent) {
41        for (int i = 0; i < childNodes.getLength(); i++) {
42            Node node = childNodes.item(i);
43            processNode(node, parent);
44        }
45    }
46
47    private void processNode(Node node, PreferenceNode parent) {
48        String name = node.getNodeName();
49        NamedNodeMap attributes = node.getAttributes();
50        Map<String, String> attrMap = new HashMap<String, String>();
51
52        if (attributes != null) {
53            int length = attributes.getLength();
54            for (int i = 0; i < length; i++) {
55                Node attr = attributes.item(i);
56                attrMap.put(attr.getNodeName(), attr.getNodeValue());
57            }
58        }
59
60        if (!name.startsWith("#")) {
61	        PreferenceNode prefNode = new PreferenceNode(name, attrMap);
62	        if (parent != null) parent.addChild(prefNode);
63
64	        processChildren(node.getChildNodes(), prefNode);
65        }
66    }
67
68	public PreferenceScreen inflatePreferences(Context context, int resourceId) {
69		return inflatePreferences(context, resourceExtractor.getResourceName(resourceId));
70	}
71
72	public PreferenceScreen inflatePreferences(Context context, String key) {
73        try {
74        	PreferenceNode prefNode = prefNodesByResourceName.get(key);
75        	return (PreferenceScreen) prefNode.inflate(context, null);
76        } catch (I18nException e) {
77        	throw e;
78        } catch (Exception e) {
79            throw new RuntimeException("error inflating " + key, e);
80        }
81	}
82
83    public class PreferenceNode {
84        private String name;
85        private final Map<String, String> attributes;
86
87        private List<PreferenceNode> children = new ArrayList<PreferenceNode>();
88
89        public PreferenceNode(String name, Map<String, String> attributes) {
90            this.name = name;
91            this.attributes = attributes;
92        }
93
94        public List<PreferenceNode> getChildren() {
95            return children;
96        }
97
98        public void addChild(PreferenceNode prefNode) {
99            children.add(prefNode);
100        }
101
102        public Preference inflate(Context context, Preference parent) throws Exception {
103        	Preference preference = create(context, (PreferenceGroup) parent);
104
105            for (PreferenceNode child : children) {
106                child.inflate(context, preference);
107            }
108
109        	return preference;
110        }
111
112        private Preference create(Context context, PreferenceGroup parent) throws Exception {
113        	Preference preference = constructPreference(context, parent);
114            if (parent != null && parent != preference) {
115                parent.addPreference(preference);
116            }
117            return preference;
118        }
119
120        private Preference constructPreference(Context context, PreferenceGroup parent) throws Exception {
121        	Class<? extends Preference> clazz = pickViewClass();
122
123           	if (clazz.equals(PreferenceScreen.class)) {
124        		return Robolectric.newInstanceOf(PreferenceScreen.class);
125        	}
126
127           	try {
128                TestAttributeSet attributeSet = new TestAttributeSet(attributes);
129                if (strictI18n) {
130                	attributeSet.validateStrictI18n();
131                }
132                return ((Constructor<? extends Preference>) clazz.getConstructor(Context.class, AttributeSet.class)).newInstance(context, attributeSet);
133            } catch (NoSuchMethodException e) {
134	            try {
135	                return ((Constructor<? extends Preference>) clazz.getConstructor(Context.class)).newInstance(context);
136	            } catch (NoSuchMethodException e1) {
137	                return ((Constructor<? extends Preference>) clazz.getConstructor(Context.class, String.class)).newInstance(context, "");
138	            }
139            }
140        }
141
142        private Class<? extends Preference> pickViewClass() {
143            Class<? extends Preference> clazz = loadClass(name);
144            if (clazz == null) {
145                clazz = loadClass("android.preference." + name);
146            }
147            if (clazz == null) {
148                throw new RuntimeException("couldn't find preference class " + name);
149            }
150            return clazz;
151        }
152
153        private Class<? extends Preference> loadClass(String className) {
154            try {
155                //noinspection unchecked
156                return (Class<? extends Preference>) getClass().getClassLoader().loadClass(className);
157            } catch (ClassNotFoundException e) {
158                return null;
159            }
160        }
161    }
162}
163