[87] | 1 | package com.framsticks.parsers; |
---|
| 2 | |
---|
[100] | 3 | import java.io.File; |
---|
| 4 | import java.io.FileInputStream; |
---|
| 5 | import java.io.FileNotFoundException; |
---|
[88] | 6 | import java.io.InputStream; |
---|
[90] | 7 | import java.util.LinkedList; |
---|
| 8 | import java.util.List; |
---|
[88] | 9 | |
---|
| 10 | import javax.xml.parsers.DocumentBuilder; |
---|
| 11 | import javax.xml.parsers.DocumentBuilderFactory; |
---|
| 12 | |
---|
[100] | 13 | import org.apache.logging.log4j.Logger; |
---|
| 14 | import org.apache.logging.log4j.LogManager; |
---|
[88] | 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 | |
---|
[100] | 21 | import com.framsticks.params.Access; |
---|
[101] | 22 | import com.framsticks.params.ParamFlags; |
---|
| 23 | import com.framsticks.params.PrimitiveParam; |
---|
[88] | 24 | import com.framsticks.params.Registry; |
---|
[102] | 25 | import com.framsticks.util.AutoAttacher; |
---|
[90] | 26 | import com.framsticks.util.AutoBuilder; |
---|
[88] | 27 | import com.framsticks.util.FramsticksException; |
---|
[99] | 28 | import com.framsticks.util.lang.Strings; |
---|
[88] | 29 | |
---|
[87] | 30 | public class XmlLoader { |
---|
[100] | 31 | private static final Logger log = LogManager.getLogger(XmlLoader.class); |
---|
[87] | 32 | |
---|
[88] | 33 | protected Registry registry = new Registry(); |
---|
| 34 | |
---|
| 35 | /** |
---|
| 36 | * |
---|
| 37 | */ |
---|
| 38 | public XmlLoader() { |
---|
[100] | 39 | registry.registerAndBuild(AutoInjector.class); |
---|
[88] | 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 | |
---|
[99] | 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 | |
---|
[100] | 66 | public Object processElement(Element element, Class<?> enclosingClass) { |
---|
[99] | 67 | final String name = mangleName(element.getNodeName()); |
---|
[88] | 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 | } |
---|
[100] | 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 | } |
---|
[88] | 97 | |
---|
[100] | 98 | return load(javaClass.getResourceAsStream(resourceName), enclosingClass); |
---|
| 99 | } |
---|
| 100 | throw new FramsticksException().msg("invalid <include/> node"); |
---|
| 101 | } |
---|
[88] | 102 | |
---|
[100] | 103 | Access access = registry.createAccess(name); |
---|
| 104 | |
---|
[88] | 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); |
---|
[101] | 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()); |
---|
[88] | 117 | } |
---|
| 118 | |
---|
| 119 | NodeList children = element.getChildNodes(); |
---|
[100] | 120 | log.debug("found {} children in {}", children.getLength(), object); |
---|
[88] | 121 | for (int i = 0; i < children.getLength(); ++i) { |
---|
| 122 | Node childNode = children.item(i); |
---|
| 123 | if (!(childNode instanceof Element)) { |
---|
| 124 | continue; |
---|
| 125 | } |
---|
[100] | 126 | Object childObject = processElement((Element) childNode, object.getClass()); |
---|
[88] | 127 | if (childObject == null) { |
---|
| 128 | continue; |
---|
| 129 | } |
---|
| 130 | |
---|
[90] | 131 | List<Object> childrenObjects = new LinkedList<>(); |
---|
| 132 | |
---|
| 133 | if (childObject instanceof AutoBuilder) { |
---|
| 134 | childrenObjects.addAll(((AutoBuilder) childObject).autoFinish()); |
---|
| 135 | } else { |
---|
| 136 | childrenObjects.add(childObject); |
---|
[88] | 137 | } |
---|
[90] | 138 | |
---|
| 139 | for (Object child : childrenObjects) { |
---|
[102] | 140 | if (child instanceof AutoAttacher) { |
---|
| 141 | ((AutoAttacher) child).attachTo(access.getSelected()); |
---|
| 142 | } else { |
---|
| 143 | access.tryAutoAppend(child); |
---|
| 144 | } |
---|
[90] | 145 | } |
---|
[88] | 146 | } |
---|
[100] | 147 | log.debug("loaded {}", object); |
---|
[88] | 148 | |
---|
| 149 | return object; |
---|
| 150 | } |
---|
| 151 | |
---|
[100] | 152 | protected Object load(InputStream stream, Class<?> enclosingClass) { |
---|
[88] | 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 | |
---|
[100] | 162 | return processElement(element, enclosingClass); |
---|
[88] | 163 | |
---|
| 164 | } catch (Exception e) { |
---|
| 165 | throw new FramsticksException().msg("failed to load").cause(e); |
---|
| 166 | } |
---|
| 167 | } |
---|
| 168 | |
---|
[90] | 169 | public <T> T load(Class<T> type, InputStream stream) { |
---|
[88] | 170 | registry.registerAndBuild(type); |
---|
| 171 | |
---|
[100] | 172 | Object object = load(stream, type); |
---|
[88] | 173 | if (type.isAssignableFrom(object.getClass())) { |
---|
| 174 | return type.cast(object); |
---|
| 175 | } |
---|
| 176 | throw new FramsticksException().msg("invalid type has been loaded"); |
---|
| 177 | } |
---|
[87] | 178 | } |
---|
[88] | 179 | |
---|