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