XMLConfiguration.java 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /*
  2. * @author Ranides Atterwim <ranides@gmail.com>
  3. * @copyright Ranides Atterwim
  4. * @license WTFPL
  5. * @url http://ranides.net/projects/assira
  6. */
  7. package net.ranides.assira.config;
  8. import java.io.File;
  9. import java.io.IOException;
  10. import java.net.URL;
  11. import java.util.Arrays;
  12. import java.util.Iterator;
  13. import java.util.LinkedHashMap;
  14. import java.util.LinkedList;
  15. import java.util.List;
  16. import java.util.Map;
  17. import javax.xml.transform.TransformerException;
  18. import net.ranides.assira.collection.ArrayUtils;
  19. import net.ranides.assira.generic.ValueUtils;
  20. import net.ranides.assira.text.LexicalCast;
  21. import net.ranides.assira.text.StringUtils;
  22. import net.ranides.assira.xml.NodeCollection;
  23. import net.ranides.assira.xml.NodeType;
  24. import net.ranides.assira.xml.XMLTransform;
  25. import org.w3c.dom.Node;
  26. /**
  27. * Klasa obsługująca dostęp do plików z konfiguracją w formacie JSON.
  28. * @author ranides
  29. */
  30. public class XMLConfiguration extends AbstractConfiguration {
  31. private static final FPath ROOT_FPATH = new FPath();
  32. /**
  33. * Tworzy nową konfigurację powiązaną z zasobem wskazywanym przez ścieżkę.
  34. * Wskazany zasób musi prowadzić do treści w formacie JSON.
  35. * @param path
  36. */
  37. public XMLConfiguration(String path) {
  38. super(path);
  39. }
  40. @Override
  41. public Configuration load() throws ConfigException {
  42. try {
  43. if("file".equals(protocol)) {
  44. values = new LinkedHashMap<String, Object>();
  45. putNode(values, XMLTransform.parse(new File(location)), ROOT_FPATH, 0);
  46. return this;
  47. }
  48. if("url".equals(protocol)) {
  49. values = new LinkedHashMap<String, Object>();
  50. putNode(values, XMLTransform.parse(new URL(location)), ROOT_FPATH, 0);
  51. return this;
  52. }
  53. } catch(IOException cause) {
  54. throw new ConfigException(cause);
  55. } catch(TransformerException cause) {
  56. throw new ConfigException(cause);
  57. }
  58. return this;
  59. }
  60. @Override
  61. public Configuration save() throws ConfigException {
  62. try {
  63. if("file".equals(protocol)) {
  64. // XMLTransform.source(new File(location), null);
  65. // return this;
  66. // @todo (ranides # 2) XMLConfiguration.save
  67. throw new IOException("N/A");
  68. }
  69. } catch(IOException cause) {
  70. throw new ConfigException(cause);
  71. }
  72. throw new ConfigException("You can save configuration to files only.");
  73. }
  74. static void putNode(Map<String, Object> target, Node node, FPath path, int index) {
  75. NodeType type = NodeType.valueOf(node);
  76. final FPath prefix;
  77. if(type == NodeType.DOCUMENT) {
  78. prefix = path;
  79. } else {
  80. prefix = new FPath(path, node.getNodeName(), index);
  81. }
  82. int count = 0;
  83. String prev = null;
  84. for(Node item : NodeCollection.nodes(node)) {
  85. if(ValueUtils.isEqual(item.getNodeName(), prev)) {
  86. count++;
  87. }
  88. if(NodeType.ELEMENT.match(item)) {
  89. prev = item.getNodeName();
  90. putNode(target, item, prefix, count);
  91. continue;
  92. }
  93. if(NodeType.TEXT.match(item)) {
  94. String text = StringUtils.trim(item.getTextContent());
  95. if(!text.isEmpty()) {
  96. putValue(target, prefix, text);
  97. }
  98. continue;
  99. }
  100. }
  101. }
  102. @SuppressWarnings("unchecked")
  103. static void putValue(Map<String, Object> target, FPath path, String value) {
  104. Map<String, Object> now = target;
  105. for(FItem item : path) {
  106. // wstawiamy pierwszy element, jeśli coś już siedzi, to jest to map
  107. if( item.index==0 ) {
  108. Object prev = now.get(item.name);
  109. if(prev instanceof Map) {
  110. now = (Map)prev;
  111. } else {
  112. now = indent(now, item.name);
  113. }
  114. }
  115. // wstawiamy drugi element, transformacja na listę...
  116. else if( item.index==1 ) {
  117. Object prev = now.get(item.name);
  118. if(prev instanceof List) {
  119. now = (Map)(((List)prev).get(item.index));
  120. } else {
  121. now = pair(now, item.name);
  122. }
  123. // wstawiamy kolejne elementy, lista musi już siedzieć
  124. } else {
  125. List<Object> prev = (List<Object>)now.get(item.name);
  126. if( item.index < prev.size() ) {
  127. now = (Map)prev.get(item.index);
  128. } else {
  129. now = indent(prev);
  130. }
  131. }
  132. }
  133. FItem head = path.head();
  134. // wstawiamy pierwszy element, nic nie powinno siedzieć...
  135. if(head.index == 0) {
  136. now.put(head.name, value);
  137. }
  138. // wstawiamy drugi element, "coś" powinno siedzieć
  139. else if(head.index == 1) {
  140. pair(now, head.name, value);
  141. }
  142. // wstawiamy kolejne elementy, lista musi już siedzieć
  143. else {
  144. ((List<Object>)now.get(head.name)).add(value);
  145. }
  146. }
  147. private static Map<String, Object> indent(Map<String, Object> target, String name) {
  148. Map<String, Object> map = new LinkedHashMap<String, Object>();
  149. target.put(name, map);
  150. return map;
  151. }
  152. private static Map<String, Object> indent(List<Object> target) {
  153. Map<String, Object> map = new LinkedHashMap<String, Object>();
  154. target.add(map);
  155. return map;
  156. }
  157. private static Map<String, Object> pair(Map<String, Object> target, String name) {
  158. return pair(target, name, new LinkedHashMap<String, Object>());
  159. }
  160. private static <T> T pair(Map<String, Object> target, String name, T value) {
  161. List<Object> nlist = new LinkedList<Object>();
  162. nlist.add( target.get(name));
  163. nlist.add( value );
  164. target.put(name, nlist);
  165. return value;
  166. }
  167. static final class FItem {
  168. final String name;
  169. final int index;
  170. public FItem(String name, int index) {
  171. this.name = name;
  172. this.index = index;
  173. }
  174. @Override
  175. public String toString() {
  176. return name + '[' + index + ']';
  177. }
  178. }
  179. static final class FPath implements Iterable<FItem> {
  180. FItem[] items;
  181. public FPath() {
  182. this.items = new FItem[0];
  183. }
  184. public FPath(String name, int index) {
  185. this(ROOT_FPATH, name, index);
  186. }
  187. public FPath(FPath root, String name, int index) {
  188. this(root, name.split("\\."), index);
  189. }
  190. private FPath(FPath root, String[] names, int index) {
  191. int n = names.length;
  192. int offset = root.items.length;
  193. this.items = Arrays.copyOf(root.items, offset+n);
  194. for(int i=0; i<n; i++) {
  195. this.items[offset+i] = new FItem(names[i], index);
  196. }
  197. }
  198. @Override
  199. public String toString() {
  200. return StringUtils.join(LexicalCast.asList(items), " / ");
  201. }
  202. @Override
  203. public Iterator<FItem> iterator() {
  204. return ArrayUtils.iterator(items, items.length-1, 0);
  205. }
  206. public FItem head() {
  207. return items.length>0 ? items[items.length-1] : null;
  208. }
  209. }
  210. }