OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 142 | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 142 Rev 174
Line 11... Line 11...
11
 * When distributing the software, include this License Header Notice in each file.
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
12
 */
13
 
13
 
14
 package org.openconcerto.xml;
14
 package org.openconcerto.xml;
15
 
15
 
16
import org.openconcerto.utils.StringUtils;
-
 
17
import org.openconcerto.utils.cache.CacheResult;
-
 
18
import org.openconcerto.utils.cache.ICache;
-
 
19
import org.openconcerto.utils.cc.ExnTransformer;
-
 
20
 
-
 
21
import java.awt.Dimension;
16
import java.awt.Dimension;
22
import java.awt.Point;
17
import java.awt.Point;
23
import java.awt.Rectangle;
18
import java.awt.Rectangle;
24
import java.beans.BeanInfo;
19
import java.beans.BeanInfo;
25
import java.beans.DefaultPersistenceDelegate;
20
import java.beans.DefaultPersistenceDelegate;
Line 33... Line 28...
33
import java.beans.XMLEncoder;
28
import java.beans.XMLEncoder;
34
import java.io.ByteArrayInputStream;
29
import java.io.ByteArrayInputStream;
35
import java.io.ByteArrayOutputStream;
30
import java.io.ByteArrayOutputStream;
36
import java.io.IOException;
31
import java.io.IOException;
37
import java.lang.reflect.Array;
32
import java.lang.reflect.Array;
38
import java.lang.reflect.Constructor;
-
 
39
import java.lang.reflect.InvocationTargetException;
-
 
40
import java.lang.reflect.Method;
33
import java.lang.reflect.Method;
41
import java.nio.charset.Charset;
34
import java.nio.charset.Charset;
42
import java.util.ArrayList;
-
 
43
import java.util.Collection;
35
import java.util.Collection;
44
import java.util.HashMap;
36
import java.util.HashMap;
45
import java.util.List;
37
import java.util.List;
46
import java.util.Map;
38
import java.util.Map;
47
import java.util.Map.Entry;
39
import java.util.Map.Entry;
48
import java.util.RandomAccess;
40
import java.util.RandomAccess;
49
import java.util.Set;
41
import java.util.Set;
50
import java.util.Stack;
-
 
51
 
42
 
52
import org.jdom2.Element;
43
import org.jdom2.Element;
53
import org.jdom2.JDOMException;
44
import org.jdom2.JDOMException;
54
import org.jdom2.input.SAXBuilder;
45
import org.jdom2.input.SAXBuilder;
55
 
46
 
Line 60... Line 51...
60
 */
51
 */
61
public class XMLCodecUtils {
52
public class XMLCodecUtils {
62
 
53
 
63
    private static final String END_DECL = "?>";
54
    private static final String END_DECL = "?>";
64
 
55
 
65
    private static final ICache<Object, Method, Object> cache = new ICache<Object, Method, Object>(60, -1, "methods for " + XMLCodecUtils.class);
-
 
66
    private static final ICache<Object, Constructor<?>, Object> cacheCtor = new ICache<Object, Constructor<?>, Object>(60, -1, "constructors for " + XMLCodecUtils.class);
-
 
67
 
-
 
68
    // the same as XMLEncoder
56
    // the same as XMLEncoder
69
    private static final Charset CS = Charset.forName("UTF-8");
57
    private static final Charset CS = Charset.forName("UTF-8");
70
 
58
 
71
    // just to get to java.beans.MetaData :
59
    // just to get to java.beans.MetaData :
72
    // Encoder.setPersistenceDelegate() actually add its arguments to a static object,
60
    // Encoder.setPersistenceDelegate() actually add its arguments to a static object,
Line 83... Line 71...
83
        public void exceptionThrown(Exception e) {
71
        public void exceptionThrown(Exception e) {
84
            throw new IllegalStateException(e);
72
            throw new IllegalStateException(e);
85
        }
73
        }
86
    };
74
    };
87
 
75
 
-
 
76
    public static final XMLDecoderJDOM XML_DECODER_JDOM = new XMLDecoderJDOM();
-
 
77
    public static final XMLDecoderStAX XML_DECODER_STAX = new XMLDecoderStAX();
-
 
78
 
88
    /**
79
    /**
89
     * Register a {@link PersistenceDelegate} for the passed class. This method tries to set
80
     * Register a {@link PersistenceDelegate} for the passed class. This method tries to set
90
     * <code>del</code> as the default for any subsequent {@link Encoder}, but with the current JRE
81
     * <code>del</code> as the default for any subsequent {@link Encoder}, but with the current JRE
91
     * this cannot be guaranteed.
82
     * this cannot be guaranteed.
92
     * 
83
     * 
Line 128... Line 119...
128
            throw new IllegalStateException(exn);
119
            throw new IllegalStateException(exn);
129
        }
120
        }
130
        return res;
121
        return res;
131
    }
122
    }
132
 
123
 
-
 
124
    public static final String encodeAsArray(final Map<?, ?> m) {
-
 
125
        return encodeAsArray(m, new StringBuilder(1024)).toString();
-
 
126
    }
-
 
127
 
-
 
128
    /**
-
 
129
     * Encode a map as an array. Useful since parsing an array is faster than parsing a map.
-
 
130
     * 
-
 
131
     * @param m the map to encode.
-
 
132
     * @param sb where to encode.
-
 
133
     * @return <code>sb</code>.
-
 
134
     * @see AbstractXMLDecoder#decodeFromArray(Object, Class, Class)
-
 
135
     */
-
 
136
    public static final StringBuilder encodeAsArray(final Map<?, ?> m, final StringBuilder sb) {
-
 
137
        final Object[] array = new Object[m.size() * 2];
-
 
138
        int i = 0;
-
 
139
        for (final Entry<?, ?> e : m.entrySet()) {
-
 
140
            array[i++] = e.getKey();
-
 
141
            array[i++] = e.getValue();
-
 
142
        }
-
 
143
        assert i == array.length;
-
 
144
        return encodeSimple(array, sb);
-
 
145
    }
-
 
146
 
-
 
147
    public static final <K, V> Map<K, V> decodeFromArray(final Object[] array, final Class<K> keyClass, final Class<V> valueClass) {
-
 
148
        return decodeFromArray(array, keyClass, valueClass, null);
-
 
149
    }
-
 
150
 
-
 
151
    public static final <K, V> Map<K, V> decodeFromArray(final Object[] array, final Class<K> keyClass, final Class<V> valueClass, Map<K, V> m) {
-
 
152
        final int l = array.length;
-
 
153
        if (m == null)
-
 
154
            m = new HashMap<K, V>((int) (l / 2 / 0.8f) + 1, 0.8f);
-
 
155
        for (int i = 0; i < l; i += 2) {
-
 
156
            final K key = keyClass.cast(array[i]);
-
 
157
            final V value = valueClass.cast(array[i + 1]);
-
 
158
            m.put(key, value);
-
 
159
        }
-
 
160
        return m;
-
 
161
    }
-
 
162
 
133
    /**
163
    /**
134
     * Encodes an object using {@link XMLEncoder}, stripping the XML declaration.
164
     * Encodes an object using {@link XMLEncoder}, stripping the XML declaration.
135
     * 
165
     * 
136
     * @param o the object to encode.
166
     * @param o the object to encode.
137
     * @return the xml string suitable to pass to {{@link #decode1(String)}.
167
     * @return the xml string suitable to pass to {{@link #decode1(String)}.
Line 151... Line 181...
151
     * @return the xml without the XML declaration.
181
     * @return the xml without the XML declaration.
152
     * @see #encode(Object)
182
     * @see #encode(Object)
153
     */
183
     */
154
    public static final String encodeSimple(Object o) {
184
    public static final String encodeSimple(Object o) {
155
        final StringBuilder sb = new StringBuilder(256);
185
        final StringBuilder sb = new StringBuilder(256);
-
 
186
        return encodeSimple(o, sb).toString();
-
 
187
    }
-
 
188
 
-
 
189
    public static final StringBuilder encodeSimple(final Object o, final StringBuilder sb) {
156
        sb.append("<java version=\"1.6.0\" class=\"java.beans.XMLDecoder\">");
190
        sb.append("<java version=\"1.6.0\" class=\"java.beans.XMLDecoder\">");
157
        encodeSimpleRec(o, sb);
191
        encodeSimpleRec(o, sb);
158
        sb.append("</java>");
192
        sb.append("</java>");
159
        return sb.toString();
193
        return sb;
160
    }
194
    }
161
 
195
 
162
    private static final void createElemEscaped(final String elemName, final Object o, final StringBuilder sb) {
196
    private static final void createElemEscaped(final String elemName, final Object o, final StringBuilder sb) {
163
        createElem(elemName, JDOM2Utils.OUTPUTTER.escapeElementEntities(o.toString()), sb);
197
        createElem(elemName, JDOM2Utils.OUTPUTTER.escapeElementEntities(o.toString()), sb);
164
    }
198
    }
Line 359... Line 393...
359
     * 
393
     * 
360
     * @param javaElem a "java" element.
394
     * @param javaElem a "java" element.
361
     * @return the decoded object.
395
     * @return the decoded object.
362
     */
396
     */
363
    public static final Object decode1(Element javaElem) {
397
    public static final Object decode1(Element javaElem) {
364
        final Element elem = (Element) javaElem.getChildren().get(0);
-
 
365
        try {
-
 
366
            return eval(elem, new Stack<Object>(), new HashMap<String, Object>());
-
 
367
        } catch (Exception e) {
-
 
368
            throw new IllegalStateException("error decoding " + JDOM2Utils.output(javaElem), e);
-
 
369
        }
-
 
370
    }
-
 
371
 
-
 
372
    private static final Object eval(Element elem, Stack<Object> context, final Map<String, Object> ids)
-
 
373
            throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException {
-
 
374
        final String n = elem.getName();
-
 
375
        // Ordered from real world scenario
-
 
376
        // string : 80k
-
 
377
        // void : 53k
-
 
378
        // int : 18k
-
 
379
        // object : 9k
-
 
380
        // null : 6k
-
 
381
        if (n.equals("string")) {
-
 
382
            return elem.getText();
-
 
383
        } else if (n.equals("void") || n.equals("object")) {
-
 
384
            final String idref = elem.getAttributeValue("idref");
-
 
385
            if (idref != null) {
-
 
386
                if (!ids.containsKey(idref))
-
 
387
                    throw new IllegalStateException("id '" + idref + "' wasn't defined");
-
 
388
                return ids.get(idref);
-
 
389
            }
-
 
390
            final String id = elem.getAttributeValue("id");
-
 
391
            final String targetClass = elem.getAttributeValue("class");
-
 
392
            final Object target = targetClass == null ? context.peek() : Class.forName(targetClass);
-
 
393
            final String propAttr = elem.getAttributeValue("property");
-
 
394
            final String indexAttr = elem.getAttributeValue("index");
-
 
395
            final String methodAttr = elem.getAttributeValue("method");
-
 
396
 
-
 
397
            // statement or expression
-
 
398
            final Object res = evalContainer(elem, context, ids, new ExnTransformer<List<Object>, Object, Exception>() {
-
 
399
                @Override
-
 
400
                public Object transformChecked(List<Object> args) throws Exception {
-
 
401
                    // call the statement
-
 
402
                    final Object res;
-
 
403
 
-
 
404
                    if (propAttr != null) {
-
 
405
                        final String methodName = (args.size() == 0 ? "get" : "set") + StringUtils.firstUp(propAttr);
-
 
406
                        res = invoke(target, methodName, args);
-
 
407
                    } else if (indexAttr != null) {
-
 
408
                        final String methodName;
-
 
409
                        if (target instanceof List) {
-
 
410
                            methodName = args.size() == 0 ? "get" : "set";
-
 
411
                            // get(index) or set(index, value)
-
 
412
                            args.add(0, Integer.valueOf(indexAttr));
-
 
413
                            res = invoke(target, methodName, args);
-
 
414
                        } else if (target.getClass().isArray()) {
-
 
415
                            final Class<?> componentType = target.getClass().getComponentType();
-
 
416
                            // in Array there's set(array, int index, Object value) or
-
 
417
                            // setPrimitive(array, int index, primitive value)
-
 
418
                            methodName = (args.size() == 0 ? "get" : "set") + (componentType.isPrimitive() ? StringUtils.firstUp(componentType.getSimpleName()) : "");
-
 
419
                            args.add(0, target);
-
 
420
                            args.add(1, Integer.valueOf(indexAttr));
-
 
421
                            res = invoke(Array.class, methodName, args);
-
 
422
                        } else
-
 
423
                            throw new IllegalStateException("use index with neither List nor array: " + target);
-
 
424
                    } else if (methodAttr != null) {
-
 
425
                        res = invoke(target, methodAttr, args);
-
 
426
                    } else
-
 
427
                        res = getCtor((Class<?>) target, args).newInstance(args.toArray());
-
 
428
                    return res;
-
 
429
                }
-
 
430
            });
-
 
431
            // not very functional but it works
-
 
432
            if (id != null)
-
 
433
                ids.put(id, res);
-
 
434
            return res;
-
 
435
        } else if (n.equals("int")) {
-
 
436
            return Integer.valueOf(elem.getText());
-
 
437
        } else if (n.equals("null")) {
-
 
438
            return null;
-
 
439
        } else if (n.equals("boolean")) {
-
 
440
            return Boolean.valueOf(elem.getText());
-
 
441
        } else if (n.equals("byte")) {
-
 
442
            return Byte.valueOf(elem.getText());
-
 
443
        } else if (n.equals("char")) {
-
 
444
            return Character.valueOf(elem.getText().charAt(0));
-
 
445
        } else if (n.equals("short")) {
-
 
446
            return Short.valueOf(elem.getText());
-
 
447
        } else if (n.equals("long")) {
-
 
448
            return Long.valueOf(elem.getText());
-
 
449
        } else if (n.equals("float")) {
-
 
450
            return Float.valueOf(elem.getText());
-
 
451
        } else if (n.equals("double")) {
-
 
452
            return Double.valueOf(elem.getText());
-
 
453
        } else if (n.equals("array")) {
-
 
454
            final String classAttr = elem.getAttributeValue("class");
-
 
455
            final String lengthAttr = elem.getAttributeValue("length");
-
 
456
 
-
 
457
            final Class<?> componentClass = parseClassName(classAttr);
-
 
458
            if (lengthAttr != null) {
-
 
459
                context.push(Array.newInstance(componentClass, Integer.parseInt(lengthAttr)));
-
 
460
                for (final Object child : elem.getChildren()) {
-
 
461
                    eval((Element) child, context, ids);
-
 
462
                }
-
 
463
                return context.pop();
-
 
464
            } else {
-
 
465
                return evalContainer(elem, context, ids, new ExnTransformer<List<Object>, Object, RuntimeException>() {
-
 
466
                    @Override
-
 
467
                    public Object transformChecked(List<Object> args) {
-
 
468
                        final Object res = Array.newInstance(componentClass, args.size());
-
 
469
                        for (int j = 0; j < args.size(); j++) {
-
 
470
                            Array.set(res, j, args.get(j));
-
 
471
                        }
-
 
472
                        return res;
-
 
473
                    }
-
 
474
                });
-
 
475
            }
-
 
476
        } else if (n.equals("class")) {
-
 
477
            return Class.forName(elem.getText());
-
 
478
        } else
-
 
479
            throw new UnsupportedOperationException("doesn't yet support " + n);
-
 
480
    }
-
 
481
 
-
 
482
    private static final Object evalContainer(final Element parent, Stack<Object> context, final Map<String, Object> ids, final ExnTransformer<List<Object>, Object, ? extends Exception> transf)
-
 
483
            throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException {
-
 
484
        final List<Object> args = new ArrayList<Object>();
-
 
485
        final List<?> children = parent.getChildren();
-
 
486
        int i = 0;
-
 
487
        boolean noVoid = true;
-
 
488
        final int size = children.size();
-
 
489
        while (i < size && noVoid) {
-
 
490
            final Element child = (Element) children.get(i);
-
 
491
            if (child.getName().equals("void"))
-
 
492
                noVoid = false;
-
 
493
            else {
-
 
494
                args.add(eval(child, context, ids));
-
 
495
                i++;
-
 
496
            }
-
 
497
        }
-
 
498
 
-
 
499
        // call the statement
-
 
500
        final Object res = transf.transformCheckedWithExn(args, false, InvocationTargetException.class, InstantiationException.class, IllegalAccessException.class);
-
 
501
 
-
 
502
        context.push(res);
-
 
503
 
-
 
504
        // now call the voids
-
 
505
        while (i < children.size()) {
-
 
506
            final Element child = (Element) children.get(i);
-
 
507
            eval(child, context, ids);
-
 
508
            i++;
-
 
509
        }
-
 
510
        return context.pop();
-
 
511
    }
-
 
512
 
-
 
513
    private static final Object invoke(final Object target, String methodName, final List<Object> args) throws IllegalAccessException, InvocationTargetException {
-
 
514
        // <object class="Cell" method="createEmpty" >
-
 
515
        // for static methods the target is already a class
-
 
516
        final Class clazz = target instanceof Class ? (Class) target : target.getClass();
-
 
517
        final Method m = getMethod(clazz, methodName, args);
-
 
518
        return m.invoke(target, args.toArray());
398
        return XML_DECODER_JDOM.decode1(javaElem);
519
    }
-
 
520
 
-
 
521
    private static final Method getMethod(Class<?> clazz, String name, List<Object> actualArgs) {
-
 
522
        final List<Class<?>> actualClasses = objectsToClasses(actualArgs);
-
 
523
        final List<Object> key = new ArrayList<Object>(3);
-
 
524
        key.add(clazz);
-
 
525
        key.add(name);
-
 
526
        key.add(actualClasses);
-
 
527
 
-
 
528
        final CacheResult<Method> cacheRes = cache.check(key);
-
 
529
        if (cacheRes.getState() == CacheResult.State.VALID)
-
 
530
            return cacheRes.getRes();
-
 
531
 
-
 
532
        final Method res = findMethod(clazz, name, actualClasses);
-
 
533
        cache.put(key, res);
-
 
534
        return res;
-
 
535
    }
-
 
536
 
-
 
537
    private static final Constructor getCtor(Class<?> clazz, List<Object> actualArgs) {
-
 
538
        final List<Class<?>> actualClasses = objectsToClasses(actualArgs);
-
 
539
        final List<Object> key = new ArrayList<Object>(3);
-
 
540
        key.add(clazz);
-
 
541
        key.add(actualClasses);
-
 
542
 
-
 
543
        final CacheResult<Constructor<?>> cacheRes = cacheCtor.check(key);
-
 
544
        if (cacheRes.getState() == CacheResult.State.VALID)
-
 
545
            return cacheRes.getRes();
-
 
546
 
-
 
547
        final Constructor res = findCtor(clazz, actualClasses);
-
 
548
        cacheCtor.put(key, res);
-
 
549
        return res;
-
 
550
    }
-
 
551
 
-
 
552
    private static final List<Class<?>> objectsToClasses(List<Object> actualArgs) {
-
 
553
        final List<Class<?>> actualClasses = new ArrayList<Class<?>>(actualArgs.size());
-
 
554
        for (final Object actualArg : actualArgs)
-
 
555
            actualClasses.add(actualArg == null ? null : actualArg.getClass());
-
 
556
        return actualClasses;
-
 
557
    }
-
 
558
 
-
 
559
    // TODO return the most specific matching method instead of the first one
-
 
560
    // (handle both Sub/Superclass and primitive/object type)
-
 
561
    private static final Method findMethod(Class<?> clazz, String name, List<Class<?>> actualArgs) {
-
 
562
        for (final Method m : clazz.getMethods()) {
-
 
563
            if (m.getName().equals(name) && callableWith(m.getParameterTypes(), actualArgs)) {
-
 
564
                return m;
-
 
565
            }
-
 
566
        }
-
 
567
        return null;
-
 
568
    }
-
 
569
 
-
 
570
    // TODO see findMethod()
-
 
571
    private static final Constructor findCtor(Class<?> clazz, List<Class<?>> actualArgs) {
-
 
572
        for (final Constructor m : clazz.getConstructors()) {
-
 
573
            if (callableWith(m.getParameterTypes(), actualArgs)) {
-
 
574
                return m;
-
 
575
            }
-
 
576
        }
-
 
577
        return null;
-
 
578
    }
-
 
579
 
-
 
580
    private static final boolean callableWith(Class<?>[] formalArgs, List<Class<?>> actualArgs) {
-
 
581
        if (formalArgs.length != actualArgs.size())
-
 
582
            return false;
-
 
583
        int i = 0;
-
 
584
        for (final Class<?> argClass : formalArgs) {
-
 
585
            final Class<?> actualArg = actualArgs.get(i);
-
 
586
            // null match everything
-
 
587
            if (actualArg != null && !argClass.isAssignableFrom(actualArg) && argClass != getPrimitive(actualArg))
-
 
588
                return false;
-
 
589
            i++;
-
 
590
        }
-
 
591
 
-
 
592
        return true;
-
 
593
    }
-
 
594
 
-
 
595
    private static Class<?> getPrimitive(Class<?> argClass) {
-
 
596
        if (argClass == Boolean.class)
-
 
597
            return Boolean.TYPE;
-
 
598
        else if (argClass == Character.class)
-
 
599
            return Character.TYPE;
-
 
600
        else if (argClass == Byte.class)
-
 
601
            return Byte.TYPE;
-
 
602
        else if (argClass == Short.class)
-
 
603
            return Short.TYPE;
-
 
604
        else if (argClass == Integer.class)
-
 
605
            return Integer.TYPE;
-
 
606
        else if (argClass == Long.class)
-
 
607
            return Long.TYPE;
-
 
608
        else if (argClass == Float.class)
-
 
609
            return Float.TYPE;
-
 
610
        else if (argClass == Double.class)
-
 
611
            return Double.TYPE;
-
 
612
        else
-
 
613
            return null;
-
 
614
    }
-
 
615
 
-
 
616
    private static final Map<String, Class> primitiveNames = new HashMap<String, Class>();
-
 
617
 
-
 
618
    static {
-
 
619
        primitiveNames.put("boolean", boolean.class);
-
 
620
        primitiveNames.put("byte", byte.class);
-
 
621
        primitiveNames.put("char", char.class);
-
 
622
        primitiveNames.put("short", short.class);
-
 
623
        primitiveNames.put("int", int.class);
-
 
624
        primitiveNames.put("long", long.class);
-
 
625
        primitiveNames.put("float", float.class);
-
 
626
        primitiveNames.put("double", double.class);
-
 
627
    }
-
 
628
 
-
 
629
    /**
-
 
630
     * Parse class names (including primitive).
-
 
631
     * 
-
 
632
     * @param className a class name, eg "java.lang.String" or "int".
-
 
633
     * @return the matching class, eg java.lang.String.class or Integer.TYPE.
-
 
634
     * @throws ClassNotFoundException if the passed name doesn't exist.
-
 
635
     */
-
 
636
    private static Class<?> parseClassName(String className) throws ClassNotFoundException {
-
 
637
        final Class<?> primitive = primitiveNames.get(className);
-
 
638
        if (primitive != null)
-
 
639
            return primitive;
-
 
640
        else
-
 
641
            return Class.forName(className);
-
 
642
    }
399
    }
643
 
400
 
644
    private XMLCodecUtils() {
401
    private XMLCodecUtils() {
645
    }
402
    }
646
 
-
 
647
}
403
}