1 | package com.framsticks.parsers; |
---|
2 | |
---|
3 | import java.io.File; |
---|
4 | import java.io.FileInputStream; |
---|
5 | import java.io.FileNotFoundException; |
---|
6 | import java.io.InputStream; |
---|
7 | import java.util.LinkedList; |
---|
8 | import java.util.List; |
---|
9 | |
---|
10 | import javax.xml.parsers.DocumentBuilder; |
---|
11 | import javax.xml.parsers.DocumentBuilderFactory; |
---|
12 | |
---|
13 | import org.apache.logging.log4j.Logger; |
---|
14 | import org.apache.logging.log4j.LogManager; |
---|
15 | import org.w3c.dom.Document; |
---|
16 | import org.w3c.dom.Element; |
---|
17 | import org.w3c.dom.NamedNodeMap; |
---|
18 | import org.w3c.dom.Node; |
---|
19 | import org.w3c.dom.NodeList; |
---|
20 | |
---|
21 | import com.framsticks.params.Access; |
---|
22 | import com.framsticks.params.ParamFlags; |
---|
23 | import com.framsticks.params.PrimitiveParam; |
---|
24 | import com.framsticks.params.Registry; |
---|
25 | import com.framsticks.util.AutoAttacher; |
---|
26 | import com.framsticks.util.AutoBuilder; |
---|
27 | import com.framsticks.util.FramsticksException; |
---|
28 | import com.framsticks.util.lang.Strings; |
---|
29 | |
---|
30 | public class XmlLoader { |
---|
31 | private static final Logger log = LogManager.getLogger(XmlLoader.class); |
---|
32 | |
---|
33 | protected Registry registry = new Registry(); |
---|
34 | |
---|
35 | /** |
---|
36 | * |
---|
37 | */ |
---|
38 | public XmlLoader() { |
---|
39 | registry.registerAndBuild(AutoInjector.class); |
---|
40 | } |
---|
41 | |
---|
42 | /** |
---|
43 | * @return the registry |
---|
44 | */ |
---|
45 | public Registry getRegistry() { |
---|
46 | return registry; |
---|
47 | } |
---|
48 | |
---|
49 | boolean useLowerCase = false; |
---|
50 | |
---|
51 | /** |
---|
52 | * @param useLowerCase the useLowerCase to set |
---|
53 | */ |
---|
54 | public void setUseLowerCase(boolean useLowerCase) { |
---|
55 | this.useLowerCase = useLowerCase; |
---|
56 | } |
---|
57 | |
---|
58 | public String mangleName(String name) { |
---|
59 | return useLowerCase ? name.toLowerCase() : name; |
---|
60 | } |
---|
61 | |
---|
62 | public String mangleAttribute(String name) { |
---|
63 | return useLowerCase ? name.toLowerCase() : Strings.uncapitalize(name); |
---|
64 | } |
---|
65 | |
---|
66 | public Object processElement(Element element, Class<?> enclosingClass) { |
---|
67 | final String name = mangleName(element.getNodeName()); |
---|
68 | if (name.equals("import")) { |
---|
69 | String className = element.getAttribute("class"); |
---|
70 | try { |
---|
71 | registry.registerAndBuild(Class.forName(className)); |
---|
72 | return null; |
---|
73 | } catch (ClassNotFoundException e) { |
---|
74 | throw new FramsticksException().msg("failed to import class").arg("name", name).cause(e); |
---|
75 | } |
---|
76 | } |
---|
77 | if (name.equals("include")) { |
---|
78 | String fileName = element.getAttribute("file"); |
---|
79 | if (Strings.notEmpty(fileName)) { |
---|
80 | try { |
---|
81 | return load(new FileInputStream(new File(fileName)), enclosingClass); |
---|
82 | } catch (FileNotFoundException e) { |
---|
83 | throw new FramsticksException().msg("failed to include file").arg("file", fileName).cause(e); |
---|
84 | } |
---|
85 | } |
---|
86 | String resourceName = element.getAttribute("resource"); |
---|
87 | if (Strings.notEmpty(resourceName)) { |
---|
88 | Class<?> javaClass = enclosingClass; |
---|
89 | String className = element.getAttribute("class"); |
---|
90 | if (Strings.notEmpty(className)) { |
---|
91 | try { |
---|
92 | javaClass = Class.forName(className); |
---|
93 | } catch (ClassNotFoundException e) { |
---|
94 | throw new FramsticksException().msg("failed to find class for resource loading").arg("class name", className).cause(e); |
---|
95 | } |
---|
96 | } |
---|
97 | |
---|
98 | return load(javaClass.getResourceAsStream(resourceName), enclosingClass); |
---|
99 | } |
---|
100 | throw new FramsticksException().msg("invalid <include/> node"); |
---|
101 | } |
---|
102 | |
---|
103 | Access access = registry.createAccess(name); |
---|
104 | |
---|
105 | Object object = access.createAccessee(); |
---|
106 | assert object != null; |
---|
107 | access.select(object); |
---|
108 | |
---|
109 | NamedNodeMap attributes = element.getAttributes(); |
---|
110 | for (int i = 0; i < attributes.getLength(); ++i) { |
---|
111 | Node attributeNode = attributes.item(i); |
---|
112 | PrimitiveParam<?> param = access.getFramsClass().getParamEntry(mangleAttribute(attributeNode.getNodeName()), PrimitiveParam.class); |
---|
113 | if (param.hasFlag(ParamFlags.READONLY)) { |
---|
114 | throw new FramsticksException().msg("cannot configure readonly param").arg("param", param).arg("in", access); |
---|
115 | } |
---|
116 | access.set(param, attributeNode.getNodeValue()); |
---|
117 | } |
---|
118 | |
---|
119 | NodeList children = element.getChildNodes(); |
---|
120 | log.debug("found {} children in {}", children.getLength(), object); |
---|
121 | for (int i = 0; i < children.getLength(); ++i) { |
---|
122 | Node childNode = children.item(i); |
---|
123 | if (!(childNode instanceof Element)) { |
---|
124 | continue; |
---|
125 | } |
---|
126 | Object childObject = processElement((Element) childNode, object.getClass()); |
---|
127 | if (childObject == null) { |
---|
128 | continue; |
---|
129 | } |
---|
130 | |
---|
131 | List<Object> childrenObjects = new LinkedList<>(); |
---|
132 | |
---|
133 | if (childObject instanceof AutoBuilder) { |
---|
134 | childrenObjects.addAll(((AutoBuilder) childObject).autoFinish()); |
---|
135 | } else { |
---|
136 | childrenObjects.add(childObject); |
---|
137 | } |
---|
138 | |
---|
139 | for (Object child : childrenObjects) { |
---|
140 | if (child instanceof AutoAttacher) { |
---|
141 | ((AutoAttacher) child).attachTo(access.getSelected()); |
---|
142 | } else { |
---|
143 | access.tryAutoAppend(child); |
---|
144 | } |
---|
145 | } |
---|
146 | } |
---|
147 | log.debug("loaded {}", object); |
---|
148 | |
---|
149 | return object; |
---|
150 | } |
---|
151 | |
---|
152 | protected Object load(InputStream stream, Class<?> enclosingClass) { |
---|
153 | try { |
---|
154 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
---|
155 | DocumentBuilder db = factory.newDocumentBuilder(); |
---|
156 | |
---|
157 | Document document = db.parse(stream); |
---|
158 | document.getDocumentElement().normalize(); |
---|
159 | Element element = document.getDocumentElement(); |
---|
160 | assert element != null; |
---|
161 | |
---|
162 | return processElement(element, enclosingClass); |
---|
163 | |
---|
164 | } catch (Exception e) { |
---|
165 | throw new FramsticksException().msg("failed to load").cause(e); |
---|
166 | } |
---|
167 | } |
---|
168 | |
---|
169 | public <T> T load(Class<T> type, InputStream stream) { |
---|
170 | registry.registerAndBuild(type); |
---|
171 | |
---|
172 | Object object = load(stream, type); |
---|
173 | if (type.isAssignableFrom(object.getClass())) { |
---|
174 | return type.cast(object); |
---|
175 | } |
---|
176 | throw new FramsticksException().msg("invalid type has been loaded"); |
---|
177 | } |
---|
178 | } |
---|
179 | |
---|