1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2012 - Scilab Enterprises - Calixte DENIZET
4  *
5  * Copyright (C) 2012 - 2016 - Scilab Enterprises
6  *
7  * This file is hereby licensed under the terms of the GNU GPL v2.0,
8  * pursuant to article 5.3.4 of the CeCILL v.2.1.
9  * This file was originally licensed under the terms of the CeCILL v2.1,
10  * and continues to be available under such terms.
11  * For more information, see the COPYING file which you should have received
12  * along with this program.
13  *
14  */
15 
16 package org.scilab.modules.commons.xml;
17 
18 import java.awt.Color;
19 import java.io.BufferedOutputStream;
20 import java.io.ByteArrayOutputStream;
21 import java.io.File;
22 import java.io.FilenameFilter;
23 import java.io.FileFilter;
24 import java.io.IOException;
25 import java.io.UnsupportedEncodingException;
26 import java.lang.annotation.Annotation;
27 import java.lang.annotation.Retention;
28 import java.lang.annotation.RetentionPolicy;
29 import java.lang.reflect.Array;
30 import java.lang.reflect.Constructor;
31 import java.lang.reflect.InvocationTargetException;
32 import java.lang.reflect.Method;
33 import java.util.ArrayList;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.HashSet;
38 import java.util.Set;
39 
40 import javax.swing.KeyStroke;
41 import javax.swing.event.EventListenerList;
42 import javax.xml.parsers.DocumentBuilder;
43 import javax.xml.parsers.DocumentBuilderFactory;
44 import javax.xml.parsers.ParserConfigurationException;
45 import javax.xml.transform.OutputKeys;
46 import javax.xml.transform.Transformer;
47 import javax.xml.transform.TransformerConfigurationException;
48 import javax.xml.transform.TransformerException;
49 import javax.xml.transform.TransformerFactoryConfigurationError;
50 import javax.xml.transform.dom.DOMSource;
51 import javax.xml.transform.stream.StreamResult;
52 import javax.xml.xpath.XPath;
53 import javax.xml.xpath.XPathConstants;
54 import javax.xml.xpath.XPathExpressionException;
55 import javax.xml.xpath.XPathFactory;
56 
57 import org.xml.sax.SAXException;
58 import org.w3c.dom.Document;
59 import org.w3c.dom.Element;
60 import org.w3c.dom.NamedNodeMap;
61 import org.w3c.dom.Node;
62 import org.w3c.dom.NodeList;
63 
64 import org.scilab.modules.commons.ScilabConstants;
65 import org.scilab.modules.commons.ScilabGeneralPrefs;
66 import org.scilab.modules.commons.gui.ScilabKeyStroke;
67 import org.scilab.modules.localization.Messages;
68 
69 /**
70  * Class to retrieve object from the xml configuration file
71  *
72  * @author Calixte DENIZET
73  *
74  */
75 public class XConfiguration {
76 
77     // User configuration file
78     private static final String SCI = System.getenv("SCI");
79     private static final String SCILAB_CONFIG_FILE = SCI + "/modules/preferences/etc/XConfiguration.xml";
80 
81     private static final String ERROR_READ = Messages.gettext("Could not load file: ");
82     private static final String ERROR_WRITE = Messages.gettext("Could not write the file: ");
83     private static final String SEVERE_ERROR = Messages.gettext("A severe error occurred: cannot load the preferences file.");
84     private static final String PARSING_ERROR = Messages.gettext("An error occurred when loading the preferences file, try to reload the default one.");
85 
86     private static final XPathFactory xpathFactory = ScilabXPathFactory.newInstance();
87     private static final Map < Class<?>, StringParser > conv = new HashMap < Class<?>, StringParser > ();
88 
89     private static final EventListenerList listenerList = new EventListenerList();
90     private static final Set<String> modifiedPaths = new HashSet<String>();
91 
92     private static Document doc;
93     private static boolean hasBeenRead;
94     private static boolean mustSave = true;;
95     private static String USER_CONFIG_FILE = ScilabConstants.SCIHOME.toString() + "/XConfiguration.xml";
96 
97     static {
98         if (ScilabConstants.SCIHOME != null && ScilabConstants.SCIHOME.canRead() && ScilabConstants.SCIHOME.canWrite()) {
99             USER_CONFIG_FILE = ScilabConstants.SCIHOME.toString() + "/XConfiguration.xml";
100         } else {
101             USER_CONFIG_FILE = SCILAB_CONFIG_FILE;
102             mustSave = false;
103         }
104 
ScilabGeneralPrefs.getInstance()105         addXConfigurationListener(ScilabGeneralPrefs.getInstance());
106 
107         try {
108             Class histoprefs = ClassLoader.getSystemClassLoader().loadClass("org.scilab.modules.history_manager.HistoryPrefs");
109             Method getinstance = histoprefs.getDeclaredMethod("getInstance");
addXConfigurationListener(XConfigurationListener)getinstance.invoke(null)110             addXConfigurationListener((XConfigurationListener)getinstance.invoke(null));
111         } catch (ClassNotFoundException e) {
112             // Nothing displayed (always occurs in MN mode)
113         } catch (Exception e) {
114             System.err.println(e);
115         }
116     }
117 
118     /**
119      * Get the document in SCIHOME corresponding to the configuration file.
120      * @return the configuration document.
121      */
getXConfigurationDocument()122     public static Document getXConfigurationDocument() {
123         if (doc == null) {
124             boolean error = false;
125             File xml = new File(USER_CONFIG_FILE);
126             if (!xml.exists() && mustSave) {
127                 ScilabXMLUtilities.writeDocument(createDocument(), USER_CONFIG_FILE);
128             }
129 
130             DocumentBuilder docBuilder = null;
131 
132             if (mustSave) {
133                 try {
134                     DocumentBuilderFactory factory = ScilabDocumentBuilderFactory.newInstance();
135                     docBuilder = factory.newDocumentBuilder();
136                     doc = docBuilder.parse(xml);
137                     float version = getDocumentVersion(doc);
138                     float defaultVersion = getDocumentVersion(getDefaultDocument());
139                     if (defaultVersion != version) {
140                         xml.delete();
141                         doc = null;
142                         return getXConfigurationDocument();
143                     } else {
144                         return doc;
145                     }
146                 } catch (ParserConfigurationException pce) {
147                     error = true;
148                 } catch (SAXException se) {
149                     error = true;
150                 } catch (IOException ioe) {
151                     error = true;
152                 }
153 
154                 if (error) {
155                     if (hasBeenRead) {
156                         System.err.println(SEVERE_ERROR);
157                         doc = null;
158                         xml.delete();
159                         return docBuilder.newDocument();
160                     }
161 
162                     hasBeenRead = true;
163                     doc = null;
164                     xml.delete();
165                     System.err.println(PARSING_ERROR);
166                     return getXConfigurationDocument();
167                 }
168 
169                 return docBuilder.newDocument();
170             } else {
171                 doc = createDocument();
172             }
173         }
174 
175         return doc;
176     }
177 
178     /**
179      * Save the modifications
180      */
writeDocument(String filename, Node written)181     public static void writeDocument(String filename, Node written) {
182         if (mustSave) {
183             Transformer transformer = null;
184             try {
185                 transformer = ScilabTransformerFactory.newInstance().newTransformer();
186             } catch (TransformerConfigurationException e1) {
187                 System.err.println(ERROR_WRITE + filename);
188                 return;
189             } catch (TransformerFactoryConfigurationError e1) {
190                 System.err.println(ERROR_WRITE + filename);
191                 return;
192             }
193             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
194 
195             StreamResult result = new StreamResult(new File(filename));
196             DOMSource source = new DOMSource(written);
197             try {
198                 transformer.transform(source, result);
199             } catch (TransformerException e) {
200                 System.err.println(ERROR_WRITE + filename);
201                 return;
202             }
203 
204             // Invalidate the current document
205             if (filename.equals(USER_CONFIG_FILE)) {
206                 doc = null;
207             }
208         }
209     }
210 
211     /**
212      * Save the modifications
213      */
dumpNode(Node written)214     public static String dumpNode(Node written) {
215         Transformer transformer = null;
216         try {
217             transformer = ScilabTransformerFactory.newInstance().newTransformer();
218         } catch (TransformerConfigurationException e1) {
219             System.err.println("Cannot dump xml");
220             return "";
221         } catch (TransformerFactoryConfigurationError e1) {
222             System.err.println("Cannot dump xml");
223             return "";
224         }
225         transformer.setOutputProperty(OutputKeys.INDENT, "yes");
226 
227         ByteArrayOutputStream stream = new ByteArrayOutputStream();
228         StreamResult result = new StreamResult(new BufferedOutputStream(stream));
229         DOMSource source = new DOMSource(written);
230         String str = "";
231         try {
232             transformer.transform(source, result);
233             str = stream.toString("UTF-8");
234         } catch (TransformerException e) {
235             System.err.println("Cannot dump xml");
236             return str;
237         } catch (UnsupportedEncodingException e) {
238             e.printStackTrace();
239         } finally {
240             try {
241                 stream.close();
242             } catch (Exception e) { }
243         }
244 
245         return str;
246     }
247 
248     /**
249      * Get the document version
250      * @param doc the document
251      * @return the version
252      */
getDocumentVersion(Document doc)253     private static float getDocumentVersion(Document doc) {
254         if (doc != null) {
255             Element root = doc.getDocumentElement();
256             String version = root.getAttribute("version");
257 
258             try {
259                 return Float.parseFloat(version);
260             } catch (NumberFormatException e) { }
261         }
262 
263         return 0.0f;
264     }
265 
266     /**
267      * Get the default document
268      * @return the document
269      */
getDefaultDocument()270     private static Document getDefaultDocument() {
271         DocumentBuilder docBuilder;
272         DocumentBuilderFactory factory;
273         Document mainDoc = null;
274 
275         try {
276             factory = ScilabDocumentBuilderFactory.newInstance();
277             docBuilder = factory.newDocumentBuilder();
278             mainDoc = docBuilder.parse(SCILAB_CONFIG_FILE);
279         } catch (ParserConfigurationException pce) {
280             System.err.println("Cannot create a XML DocumentBuilder:\n" + pce);
281             return null;
282         } catch (SAXException se) {
283             System.err.println("Weird... Cannot parse basic file:\n" + se);
284             return null;
285         } catch (IOException ioe) {
286             System.err.println("Weird... Cannot parse basic file:\n" + ioe);
287             return null;
288         }
289 
290         return mainDoc;
291     }
292 
293     /**
294      * Create a document in using the XConfiguration-*.xml found in SCI/modules/MODULE_NAME/etc/
295      * @return the built document
296      */
createDocument()297     public static Document createDocument() {
298         DocumentBuilder docBuilder;
299         DocumentBuilderFactory factory;
300         Document mainDoc = getDefaultDocument();
301         if (mainDoc == null) {
302             return null;
303         }
304 
305         Element root = mainDoc.getDocumentElement();
306 
307         factory = ScilabDocumentBuilderFactory.newInstance();
308 
309         try {
310             docBuilder = factory.newDocumentBuilder();
311         } catch (ParserConfigurationException pce) {
312             System.err.println("Cannot create a XML DocumentBuilder:\n" + pce);
313             return null;
314         }
315 
316         List<File> etcs = getEtcDir();
317         for (File etc : etcs) {
318             File[] xmls = etc.listFiles(new FilenameFilter() {
319                 public boolean accept(File dir, String name) {
320                     return name.endsWith(".xml") && name.startsWith("XConfiguration-");
321                 }
322             });
323             for (File xml : xmls) {
324                 try {
325                     Document doc = docBuilder.parse(xml);
326                     if (xml.getName().equals("XConfiguration-general.xml")) {
327                         try {
328                             XPath xp = xpathFactory.newXPath();
329                             NodeList nodes = (NodeList) xp.compile("//shortcuts/body/actions/action-folder/action[contains(@key, 'OSSCKEY')]").evaluate(doc, XPathConstants.NODESET);
330                             for (int i = 0; i < nodes.getLength(); i++) {
331                                 Element e = (Element) nodes.item(i);
332                                 e.setAttribute("key", e.getAttribute("key").replace("OSSCKEY", ScilabKeyStroke.getOSMetaKey()));
333                             }
334                         } catch (XPathExpressionException e) {
335                             System.err.println(e);
336                         }
337                     }
338                     Node node = mainDoc.importNode(doc.getDocumentElement(), true);
339                     NodeList list = root.getElementsByTagName(node.getNodeName());
340                     if (list.getLength() != 0) {
341                         root.replaceChild(node, list.item(0));
342                     }
343                 } catch (SAXException se) {
344                     System.err.println(ERROR_READ + xml.getName());
345                 } catch (IOException ioe) {
346                     System.err.println(ERROR_READ + xml.getName());
347                 }
348             }
349         }
350 
351         return mainDoc;
352     }
353 
354     /**
355      * Get the list of the etc dirs in modules dirs
356      * @return the lit of etc dirs
357      */
getEtcDir()358     public static List<File> getEtcDir() {
359         List<File> list = new ArrayList<File>();
360         File modulesDir = new File(SCI + "/modules/");
361         File[] modules = modulesDir.listFiles(new FileFilter() {
362             public boolean accept(File f) {
363                 return f.isDirectory();
364             }
365         });
366 
367         for (File module : modules) {
368             File etc = new File(module, "/etc/");
369             if (etc.exists() && etc.isDirectory()) {
370                 list.add(etc);
371             }
372         }
373 
374         return list;
375     }
376 
addModifiedPath(String path)377     public static void addModifiedPath(String path) {
378         if (path != null && !path.isEmpty()) {
379             modifiedPaths.add(path);
380         }
381     }
382 
invalidate()383     public static void invalidate() {
384         modifiedPaths.clear();
385         doc = null;
386     }
387 
addXConfigurationListener(XConfigurationListener listener)388     public static void addXConfigurationListener(XConfigurationListener listener) {
389         listenerList.add(XConfigurationListener.class, listener);
390     }
391 
removeXConfigurationListener(XConfigurationListener listener)392     public static void removeXConfigurationListener(XConfigurationListener listener) {
393         listenerList.remove(XConfigurationListener.class, listener);
394     }
395 
getXConfigurationListeners()396     public static XConfigurationListener[] getXConfigurationListeners() {
397         return listenerList.getListeners(XConfigurationListener.class);
398     }
399 
fireXConfigurationEvent()400     public static void fireXConfigurationEvent() {
401         if (!modifiedPaths.isEmpty()) {
402             XConfigurationEvent event = null;
403             Object[] listeners = listenerList.getListenerList();
404             for (int i = listeners.length - 2; i >= 0; i -= 2) {
405                 if (listeners[i] == XConfigurationListener.class) {
406                     if (event == null) {
407                         event = new XConfigurationEvent(modifiedPaths);
408                     }
409                     ((XConfigurationListener) listeners[i + 1]).configurationChanged(event);
410                 }
411             }
412 
413             modifiedPaths.clear();
414         }
415     }
416 
417     /**
418      * Register a StringParser for a given Class
419      * @param type the class type
420      * @param parser the StringParser
421      */
registerStringParser(Class<?> type, StringParser parser)422     public static void registerStringParser(Class<?> type, StringParser parser) {
423         conv.put(type, parser);
424     }
425 
426     /**
427      * Get a StringParser for a given Class
428      * @param type the class type
429      * @return the corresponding parser
430      */
getStringParser(Class<?> type)431     public static StringParser getStringParser(Class<?> type) {
432         return conv.get(type);
433     }
434 
set(final Document doc, final String path, String value)435     public static void set(final Document doc, final String path, String value) {
436         XPath xp = xpathFactory.newXPath();
437         NodeList nodes;
438         try {
439             nodes = (NodeList) xp.compile(path).evaluate(doc, XPathConstants.NODESET);
440         } catch (XPathExpressionException e) {
441             System.err.println(e);
442             return;
443         }
444 
445         for (int i = 0; i < nodes.getLength() ; i++) {
446             Node n = nodes.item(i);
447             if (n != null && n.getNodeType() == Node.ATTRIBUTE_NODE) {
448                 n.setNodeValue(value);
449             }
450         }
451 
452         writeDocument(USER_CONFIG_FILE, doc);
453     }
454 
455     /**
456      * Save the current file
457      */
save()458     public static void save() {
459         if (doc != null) {
460             writeDocument(USER_CONFIG_FILE, doc);
461         }
462     }
463 
464     /**
465      * Get all the nodes with the given path.
466      * All the get nodes are serialized into an object (generic paramater) which must have
467      * a constructor without argument and with methods named set&lt;Attribute Name&gt; with
468      * one argument and no returned value.
469      * For example a node &lt;foo aaa="1" bbb="true" ccc-ddd="#001122"/&gt; could be retrieved with something like
470      * XConfiguration.get(MyObject.class, doc, "//path/to/node") where MyObject should be something like
471      * <code>
472      * public class MyObject {
473      *
474      *    public MyObject() {
475      *       // ...
476      *    }
477      *
478      *    public void setAaa(int a) {
479      *       // ...
480      *    }
481      *
482      *    public void setBbb(boolean b) {
483      *       // ...
484      *    }
485      *
486      *    public void setCccDdd(Color c) {
487      *       // ...
488      *    }
489      * }
490      * </code>
491      * If an attribute must not be retrieved, just remove the setter.
492      *
493      * It is possible to use the annotation @XConfAttribute to make easier the retrievement.
494      * For example
495      * <code>
496      * @XConfAttribute
497      * public class MyObject {
498      *
499      *    public MyObject() {
500      *       // ...
501      *    }
502      *
503      *    @XConfAttribute(attributes={"aaa", "bbb", "ccc-ddd"})
504      *    // the contents of aaa will be converted into int and passed as first argument
505      *    // the contents of bbb will be converted into boolean and passed as second argument
506      *    // the contents of ccc-ddd will be converted into Color and passed as third argument
507      *    public void set(int n, boolean b, Color c) {
508      *       // ...
509      *    }
510      *  }
511      * </code>
512      *
513      * @param type the Class type to retrieve
514      * @param doc the document to explore
515      * @param path the xpath query to retrieve the corresponding nodeset.
516      * @return an array of instance of class type.
517      */
get(final Class<T> type, final Document doc, final String path)518     public static final <T> T[] get(final Class<T> type, final Document doc, final String path) {
519         XPath xp = xpathFactory.newXPath();
520         NodeList nodes;
521         try {
522             nodes = (NodeList) xp.compile(path).evaluate(doc, XPathConstants.NODESET);
523         } catch (XPathExpressionException e) {
524             System.err.println(e);
525             return (T[]) Array.newInstance(type, 0);
526         }
527 
528         if (type.getAnnotation(XConfAttribute.class) != null) {
529             T[] arr = get(type, nodes);
530             if (arr != null) {
531                 return arr;
532             }
533         }
534 
535         Method[] meths = type.getDeclaredMethods();
536         Map<String, Method> mapMethods = new HashMap<String, Method>();
537         for (Method m : meths) {
538             String name = m.getName();
539             if (name.startsWith("set") && m.getParameterTypes().length == 1 && m.getReturnType().equals(Void.TYPE)) {
540                 mapMethods.put(m.getName(), m);
541                 m.setAccessible(true);
542             }
543         }
544 
545         Map<String, String> names = new HashMap<String, String>();
546 
547         T[] values = (T[]) Array.newInstance(type, nodes.getLength());
548         for (int i = 0; i < values.length; i++) {
549             NamedNodeMap map = nodes.item(i).getAttributes();
550             try {
551                 Constructor<T> constructor = type.getDeclaredConstructor(new Class[] {});
552                 constructor.setAccessible(true);
553                 values[i] = constructor.newInstance();
554             } catch (InstantiationException e) {
555                 System.err.println(e);
556                 break;
557             } catch (IllegalAccessException e) {
558                 System.err.println(e);
559                 break;
560             } catch (NoSuchMethodException e) {
561                 System.err.println(e);
562                 break;
563             } catch (InvocationTargetException e) {
564                 System.err.println(e.getTargetException());
565             }
566 
567             for (int j = 0; j < map.getLength(); j++) {
568                 Node n = map.item(j);
569                 String name = n.getNodeName();
570                 String methName = names.get(name);
571                 if (methName == null) {
572                     StringBuilder buffer = new StringBuilder("set");
573                     String[] parts = name.split("-");
574                     for (String part : parts) {
575                         if (part != null && part.length() > 0) {
576                             buffer.append(part.substring(0, 1).toUpperCase());
577                             buffer.append(part.substring(1).toLowerCase());
578                         }
579                     }
580                     methName = buffer.toString();
581                     names.put(name, methName);
582                 }
583                 String value = n.getNodeValue();
584                 Method method = mapMethods.get(methName);
585                 if (method != null) {
586                     Class[] paramsClass = method.getParameterTypes();
587                     StringParser parser = conv.get(paramsClass[0]);
588                     if (parser != null) {
589                         Object[] params = new Object[] {parser.parse(value)};
590                         try {
591                             method.invoke(values[i], params);
592                         } catch (IllegalAccessException e) {
593                             System.err.println(e);
594                         } catch (IllegalArgumentException e) {
595                             System.err.println(e);
596                         } catch (InvocationTargetException e) {
597                             System.err.println(e.getTargetException());
598                         }
599                     }
600                 }
601             }
602         }
603 
604         return values;
605     }
606 
607     /**
608      * Get a Map with where the key is the converted value (according to keyType) of the attribute named key
609      * and the value is given in the same way.
610      * @param doc the document to read
611      * @param key the attribute name where its value will be converted and used as a key in the map
612      * @param keyType the Class of the key
613      * @param value the attribute name where its value will be converted and used as a value in the map
614      * @param valueType the Class of the value
615      * @return the corresponding map.
616      */
get(final Document doc, final String key, final Class<T> keyType, final String value, final Class<U> valueType, final String path)617     public static final <T, U> Map<T, U> get(final Document doc, final String key, final Class<T> keyType, final String value, final Class<U> valueType, final String path) {
618         XPath xp = xpathFactory.newXPath();
619         Map<T, U> map = new HashMap<T, U>();
620         NodeList nodes;
621         try {
622             nodes = (NodeList) xp.compile(path).evaluate(doc, XPathConstants.NODESET);
623         } catch (XPathExpressionException e) {
624             System.err.println(e);
625             return map;
626         }
627 
628         int len = nodes.getLength();
629         for (int i = 0; i < len; i++) {
630             NamedNodeMap nmap = nodes.item(i).getAttributes();
631             Node k = nmap.getNamedItem(key);
632             Node v = nmap.getNamedItem(value);
633             if (k == null || v == null) {
634                 return map;
635             }
636 
637             String kk = k.getNodeValue();
638             String vv = v.getNodeValue();
639 
640             StringParser convK = conv.get(keyType);
641             StringParser convV = conv.get(valueType);
642             if (convK == null || convV == null) {
643                 return map;
644             }
645 
646             map.put((T) convK.parse(kk), (U) convV.parse(vv));
647         }
648 
649         return map;
650     }
651 
652     /**
653      * Getter for annoted class (with @XConfAttribute)
654      * @param type the class type
655      * @param nodes the nodes to read
656      * @return an array of instances of type, if the class is annoted with @XConfAttribute(isStatic=true),
657      * the returned array is empty.
658      */
get(final Class<T> type, NodeList nodes)659     private static final <T> T[] get(final Class<T> type, NodeList nodes) {
660         Method[] meths = type.getDeclaredMethods();
661         List<String[]> attrs = new ArrayList<String[]>();
662         List<Method> methods = new ArrayList<Method>();
663         for (Method m : meths) {
664             String name = m.getName();
665             Annotation ann = m.getAnnotation(XConfAttribute.class);
666             if (ann != null) {
667                 String[] attributes = ((XConfAttribute) ann).attributes();
668                 if (attributes.length == m.getParameterTypes().length) {
669                     m.setAccessible(true);
670                     attrs.add(attributes);
671                     methods.add(m);
672                 } else {
673                     return null;
674                 }
675             }
676         }
677 
678         Annotation ann = type.getAnnotation(XConfAttribute.class);
679         boolean mustInstantiate = !((XConfAttribute) ann).isStatic();
680 
681         T[] values = null;
682         int len = nodes.getLength();
683         if (mustInstantiate) {
684             values = (T[]) Array.newInstance(type, len);
685         }
686 
687         for (int i = 0; i < len; i++) {
688             NamedNodeMap map = nodes.item(i).getAttributes();
689             String nodeName = nodes.item(i).getNodeName();
690 
691             if (mustInstantiate) {
692                 try {
693                     Constructor<T> constructor = type.getDeclaredConstructor(new Class[] {});
694                     constructor.setAccessible(true);
695                     values[i] = constructor.newInstance();
696                 } catch (InstantiationException e) {
697                     System.err.println(e);
698                     break;
699                 } catch (IllegalAccessException e) {
700                     System.err.println(e);
701                     break;
702                 } catch (NoSuchMethodException e) {
703                     System.err.println(e);
704                     break;
705                 } catch (InvocationTargetException e) {
706                     System.err.println(e.getTargetException());
707                 }
708             }
709 
710             for (int j = 0; j < methods.size(); j++) {
711                 Method method = methods.get(j);
712                 ann = method.getAnnotation(XConfAttribute.class);
713                 String tag = ((XConfAttribute) ann).tag();
714                 if (tag.isEmpty() || tag.equals(nodeName)) {
715                     String[] attributes = attrs.get(j);
716                     Object[] params = new Object[attributes.length];
717                     Class[] paramsClass = method.getParameterTypes();
718                     for (int k = 0; k < attributes.length; k++) {
719                         String p = "";
720                         Node node = null;
721                         if (map != null) {
722                             node = map.getNamedItem(attributes[k]);
723                         }
724                         if (node != null) {
725                             p = node.getNodeValue();
726                         }
727 
728                         StringParser parser = conv.get(paramsClass[k]);
729                         if (parser != null) {
730                             params[k] = parser.parse(p);
731                         }
732                     }
733 
734                     try {
735                         if (mustInstantiate) {
736                             method.invoke(values[i], params);
737                         } else {
738                             method.invoke(null, params);
739                         }
740                     } catch (IllegalAccessException e) {
741                         System.err.println(e);
742                     } catch (IllegalArgumentException e) {
743                         System.err.println(e);
744                     } catch (InvocationTargetException e) {
745                         System.err.println(e.getTargetException());
746                     }
747                 }
748             }
749         }
750 
751         if (mustInstantiate) {
752             return values;
753         } else {
754             return (T[]) Array.newInstance(type, 0);
755         }
756     }
757 
758     /**
759      * Interface to implement to parse an attribute String into a Java object
760      */
761     public static interface StringParser {
762 
763         /**
764          * Parse a string
765          * @param str the string to parse
766          * @return the corresponding Object
767          */
parse(String str)768         public Object parse(String str);
769     }
770 
771     static {
conv.put(int.class, new StringParser() { public Integer parse(String str) { try { return Integer.parseInt(str); } catch (NumberFormatException e) { try { return (int) Double.parseDouble(str); } catch (NumberFormatException ee) { return new Integer(0); } } } })772         conv.put(int.class, new StringParser() {
773             public Integer parse(String str) {
774                 try {
775                     return Integer.parseInt(str);
776                 } catch (NumberFormatException e) {
777                     try {
778                         return (int) Double.parseDouble(str);
779                     } catch (NumberFormatException ee) {
780                         return new Integer(0);
781                     }
782                 }
783             }
784         });
conv.put(char.class, new StringParser() { public Object parse(String str) { if (str.length() > 0) { return str.charAt(0); } else { return new Character((char) 0); } } })785         conv.put(char.class, new StringParser() {
786             public Object parse(String str) {
787                 if (str.length() > 0) {
788                     return str.charAt(0);
789                 } else {
790                     return new Character((char) 0);
791                 }
792             }
793         });
conv.put(byte.class, new StringParser() { public Object parse(String str) { try { return Byte.parseByte(str); } catch (NumberFormatException e) { try { return (byte) Double.parseDouble(str); } catch (NumberFormatException ee) { return new Byte((byte) 0); } } } })794         conv.put(byte.class, new StringParser() {
795             public Object parse(String str) {
796                 try {
797                     return Byte.parseByte(str);
798                 } catch (NumberFormatException e) {
799                     try {
800                         return (byte) Double.parseDouble(str);
801                     } catch (NumberFormatException ee) {
802                         return new Byte((byte) 0);
803                     }
804                 }
805             }
806         });
conv.put(short.class, new StringParser() { public Object parse(String str) { try { return Short.parseShort(str); } catch (NumberFormatException e) { try { return (short) Double.parseDouble(str); } catch (NumberFormatException ee) { return new Short((short) 0); } } } })807         conv.put(short.class, new StringParser() {
808             public Object parse(String str) {
809                 try {
810                     return Short.parseShort(str);
811                 } catch (NumberFormatException e) {
812                     try {
813                         return (short) Double.parseDouble(str);
814                     } catch (NumberFormatException ee) {
815                         return new Short((short) 0);
816                     }
817                 }
818             }
819         });
conv.put(double.class, new StringParser() { public Object parse(String str) { try { return Double.parseDouble(str); } catch (NumberFormatException ee) { return new Double((double) 0); } } })820         conv.put(double.class, new StringParser() {
821             public Object parse(String str) {
822                 try {
823                     return Double.parseDouble(str);
824                 } catch (NumberFormatException ee) {
825                     return new Double((double) 0);
826                 }
827             }
828         });
conv.put(float.class, new StringParser() { public Object parse(String str) { try { return Float.parseFloat(str); } catch (NumberFormatException ee) { return new Float((float) 0); } } })829         conv.put(float.class, new StringParser() {
830             public Object parse(String str) {
831                 try {
832                     return Float.parseFloat(str);
833                 } catch (NumberFormatException ee) {
834                     return new Float((float) 0);
835                 }
836             }
837         });
conv.put(boolean.class, new StringParser() { public Object parse(String str) { return Boolean.parseBoolean(str); } })838         conv.put(boolean.class, new StringParser() {
839             public Object parse(String str) {
840                 return Boolean.parseBoolean(str);
841             }
842         });
conv.put(long.class, new StringParser() { public Object parse(String str) { try { return Long.parseLong(str); } catch (NumberFormatException e) { try { return (long) Double.parseDouble(str); } catch (NumberFormatException ee) { return new Long((long) 0); } } } })843         conv.put(long.class, new StringParser() {
844             public Object parse(String str) {
845                 try {
846                     return Long.parseLong(str);
847                 } catch (NumberFormatException e) {
848                     try {
849                         return (long) Double.parseDouble(str);
850                     } catch (NumberFormatException ee) {
851                         return new Long((long) 0);
852                     }
853                 }
854             }
855         });
conv.put(String.class, new StringParser() { public Object parse(String str) { return str; } })856         conv.put(String.class, new StringParser() {
857             public Object parse(String str) {
858                 return str;
859             }
860         });
conv.put(Color.class, new StringParser() { public Object parse(String str) { try { return Color.decode(str); } catch (NumberFormatException e) { return Color.BLACK; } } })861         conv.put(Color.class, new StringParser() {
862             public Object parse(String str) {
863                 try {
864                     return Color.decode(str);
865                 } catch (NumberFormatException e) {
866                     return Color.BLACK;
867                 }
868             }
869         });
conv.put(KeyStroke.class, new StringParser() { public Object parse(String str) { String[] toks = str.split(R); StringBuilder buffer = new StringBuilder(); for (int i = 0; i < toks.length - 1; i++) { buffer.append(toks[i].toLowerCase()); buffer.append(R); } buffer.append(toks[toks.length - 1].toUpperCase()); return KeyStroke.getKeyStroke(buffer.toString()); } })870         conv.put(KeyStroke.class, new StringParser() {
871             public Object parse(String str) {
872                 String[] toks = str.split(" +");
873                 StringBuilder buffer = new StringBuilder();
874                 for (int i = 0; i < toks.length - 1; i++) {
875                     buffer.append(toks[i].toLowerCase());
876                     buffer.append(" ");
877                 }
878                 buffer.append(toks[toks.length - 1].toUpperCase());
879                 return KeyStroke.getKeyStroke(buffer.toString());
880             }
881         });
882     }
883 
884     @Retention(RetentionPolicy.RUNTIME)
885     public @interface XConfAttribute {
886 
887         /**
888          * Map method arguments with attributes name
889          * For example,
890          * <code>
891          * @XConfAttribute(tag="mytag", attributes={"a", "b"})
892          * void foo(String one, int tow) { ... }
893          * </code>
894          * The value of attribute "a" is converted into a String and passed as "one" argument,...
895          */
attributes()896     public String[] attributes() default {
897             ""
898         };
899 
tag()900     public String tag() default "";
901 
902         /**
903          * Used to avoid object instanciation so the differents annotated methods must be static.
904          */
isStatic()905     public boolean isStatic() default false;
906     }
907 }
908