View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.waarp.common.xml;
21  
22  import org.waarp.common.exception.InvalidArgumentException;
23  import org.waarp.common.utility.WaarpStringUtils;
24  
25  import java.io.InvalidObjectException;
26  import java.math.BigDecimal;
27  import java.math.BigInteger;
28  import java.sql.Date;
29  import java.sql.Timestamp;
30  import java.text.ParseException;
31  import java.util.ArrayList;
32  import java.util.Collection;
33  import java.util.List;
34  
35  /**
36   * XmlValue base element
37   */
38  public class XmlValue {
39  
40    private static final String CAN_NOT_CONVERT_VALUE = "Can not convert value ";
41  
42    private static final String TO_TYPE = " to type ";
43  
44    private static final String CAN_NOT_CONVERT_VALUE_FROM =
45        "Can not convert value from ";
46  
47    private final XmlDecl decl;
48  
49    private Object value;
50  
51    private List<?> values;
52  
53    private XmlValue[] subXml;
54  
55    public XmlValue(final XmlDecl decl) {
56      this.decl = decl;
57      if (this.decl.isSubXml()) {
58        if (this.decl.isMultiple()) {
59          value = null;
60          values = new ArrayList<XmlValue>();
61          subXml = null;
62          return;
63        }
64        final int len = this.decl.getSubXmlSize();
65        final XmlDecl[] newDecls = this.decl.getSubXml();
66        subXml = new XmlValue[len];
67        for (int i = 0; i < len; i++) {
68          subXml[i] = new XmlValue(newDecls[i]);
69        }
70        value = null;
71        values = null;
72        return;
73      }
74      if (this.decl.isMultiple()) {
75        value = null;
76        switch (getType()) {
77          case BOOLEAN:
78          case STRING:
79          case TIMESTAMP:
80          case SQLDATE:
81          case SHORT:
82          case DOUBLE:
83          case LONG:
84          case BYTE:
85          case CHARACTER:
86          case FLOAT:
87          case INTEGER:
88            values = new ArrayList<Boolean>();
89            break;
90          case XVAL:
91          case EMPTY:
92          default:
93            break;
94        }
95      }
96    }
97  
98    @SuppressWarnings("unchecked")
99    public XmlValue(final XmlValue from) {
100     this(from.decl);
101     if (decl.isSubXml()) {
102       if (decl.isMultiple()) {
103         final List<XmlValue[]> subvalues = (List<XmlValue[]>) from.values;
104         for (final XmlValue[] xmlValues : subvalues) {
105           final XmlValue[] newValues = new XmlValue[xmlValues.length];
106           for (int i = 0; i < xmlValues.length; i++) {
107             newValues[i] = new XmlValue(xmlValues[i]);
108           }
109           ((Collection<XmlValue[]>) values).add(newValues);
110         }
111       } else {
112         for (int i = 0; i < from.subXml.length; i++) {
113           subXml[i] = new XmlValue(from.subXml[i]);
114         }
115       }
116     } else if (decl.isMultiple()) {
117       final List<Object> subvalues = (List<Object>) from.values;
118       for (final Object object : subvalues) {
119         try {
120           addValue(getCloneValue(getType(), object));
121         } catch (final InvalidObjectException e) {
122           // nothing
123         }
124       }
125     } else {
126       try {
127         setValue(from.getCloneValue());
128       } catch (final InvalidObjectException e) {
129         // Nothing
130       }
131     }
132   }
133 
134   /**
135    * @return the decl
136    */
137   public final XmlDecl getDecl() {
138     return decl;
139   }
140 
141   /**
142    * Get Java field name
143    *
144    * @return the field name
145    */
146   public final String getName() {
147     return decl.getName();
148   }
149 
150   /**
151    * @return the type
152    */
153   public final Class<?> getClassType() {
154     return decl.getClassType();
155   }
156 
157   /**
158    * @return the type
159    */
160   public final XmlType getType() {
161     return decl.getType();
162   }
163 
164   /**
165    * @return the xmlPath
166    */
167   public final String getXmlPath() {
168     return decl.getXmlPath();
169   }
170 
171   /**
172    * @return True if this Value is a subXml
173    */
174   public final boolean isSubXml() {
175     return decl.isSubXml();
176   }
177 
178   /**
179    * @return the associated SubXML with the XmlValue (might be null if
180    *     singleton
181    *     or Multiple)
182    */
183   public final XmlValue[] getSubXml() {
184     return subXml;
185   }
186 
187   /**
188    * @return True if the Value are list of values
189    */
190   public final boolean isMultiple() {
191     return decl.isMultiple();
192   }
193 
194   /**
195    * @return the associated list with the XmlValues (might be null if
196    *     singleton
197    *     or SubXml)
198    */
199   public final List<?> getList() {
200     return values;
201   }
202 
203   /**
204    * Add a value into the Multiple values from the String (not compatible with
205    * subXml)
206    *
207    * @param valueOrig
208    *
209    * @throws InvalidObjectException
210    * @throws InvalidArgumentException
211    */
212   @SuppressWarnings("unchecked")
213   public final void addFromString(final String valueOrig)
214       throws InvalidObjectException, InvalidArgumentException {
215     final String valueNew = XmlUtil.getExtraTrimed(valueOrig);
216     switch (getType()) {
217       case BOOLEAN:
218         ((Collection<Boolean>) values).add(
219             (Boolean) convert(getClassType(), valueNew));
220         break;
221       case INTEGER:
222         ((Collection<Integer>) values).add(
223             (Integer) convert(getClassType(), valueNew));
224         break;
225       case FLOAT:
226         ((Collection<Float>) values).add(
227             (Float) convert(getClassType(), valueNew));
228         break;
229       case CHARACTER:
230         ((Collection<Character>) values).add(
231             (Character) convert(getClassType(), valueNew));
232         break;
233       case BYTE:
234         ((Collection<Byte>) values).add(
235             (Byte) convert(getClassType(), valueNew));
236         break;
237       case LONG:
238         ((Collection<Long>) values).add(
239             (Long) convert(getClassType(), valueNew));
240         break;
241       case DOUBLE:
242         ((Collection<Double>) values).add(
243             (Double) convert(getClassType(), valueNew));
244         break;
245       case SHORT:
246         ((Collection<Short>) values).add(
247             (Short) convert(getClassType(), valueNew));
248         break;
249       case SQLDATE:
250         ((Collection<Date>) values).add(
251             (Date) convert(getClassType(), valueNew));
252         break;
253       case TIMESTAMP:
254         ((Collection<Timestamp>) values).add(
255             (Timestamp) convert(getClassType(), valueNew));
256         break;
257       case STRING:
258         ((Collection<String>) values).add(
259             (String) convert(getClassType(), valueNew));
260         break;
261       case XVAL:
262         throw new InvalidObjectException(
263             "XVAL cannot be assigned from String directly");
264         // ((List<XmlValue>) this.values).add((XmlValue) value)
265       case EMPTY:
266         throw new InvalidObjectException("EMPTY cannot be assigned");
267     }
268   }
269 
270   /**
271    * Add a value into the Multiple values from the Object
272    *
273    * @param value
274    *
275    * @throws InvalidObjectException
276    */
277   @SuppressWarnings("unchecked")
278   public final void addValue(final Object value) throws InvalidObjectException {
279     if (getType().isNativelyCompatible(value)) {
280       switch (getType()) {
281         case BOOLEAN:
282           ((Collection<Boolean>) values).add((Boolean) value);
283           break;
284         case INTEGER:
285           ((Collection<Integer>) values).add((Integer) value);
286           break;
287         case FLOAT:
288           ((Collection<Float>) values).add((Float) value);
289           break;
290         case CHARACTER:
291           ((Collection<Character>) values).add((Character) value);
292           break;
293         case BYTE:
294           ((Collection<Byte>) values).add((Byte) value);
295           break;
296         case LONG:
297           ((Collection<Long>) values).add((Long) value);
298           break;
299         case DOUBLE:
300           ((Collection<Double>) values).add((Double) value);
301           break;
302         case SHORT:
303           ((Collection<Short>) values).add((Short) value);
304           break;
305         case SQLDATE:
306           if (Date.class.isAssignableFrom(value.getClass())) {
307             ((Collection<Date>) values).add((Date) value);
308           } else if (java.util.Date.class.isAssignableFrom(value.getClass())) {
309             ((Collection<Date>) values).add(
310                 new Date(((java.util.Date) value).getTime()));
311           }
312           break;
313         case TIMESTAMP:
314           ((Collection<Timestamp>) values).add((Timestamp) value);
315           break;
316         case STRING:
317           ((Collection<String>) values).add(
318               XmlUtil.getExtraTrimed((String) value));
319           break;
320         case XVAL:
321           ((Collection<XmlValue[]>) values).add((XmlValue[]) value);
322           break;
323         default:
324           throw new InvalidObjectException(
325               CAN_NOT_CONVERT_VALUE_FROM + value.getClass() + TO_TYPE +
326               getClassType());
327       }
328     } else {
329       throw new InvalidObjectException(
330           CAN_NOT_CONVERT_VALUE_FROM + value.getClass() + TO_TYPE +
331           getClassType());
332     }
333   }
334 
335   /**
336    * @return the value as Object (might be null if multiple)
337    */
338   public final Object getValue() {
339     return value;
340   }
341 
342   /**
343    * Utility function to get a clone of a value
344    *
345    * @param type
346    * @param value
347    *
348    * @return the clone Object
349    *
350    * @throws InvalidObjectException
351    */
352   public static Object getCloneValue(final XmlType type, final Object value)
353       throws InvalidObjectException {
354     if (value == null) {
355       throw new InvalidObjectException(
356           "Can not convert value from null to type " + type.classType);
357     }
358     switch (type) {
359       case BOOLEAN:
360         return value;
361       case INTEGER:
362         return value;
363       case FLOAT:
364         return value;
365       case CHARACTER:
366         return value;
367       case BYTE:
368         return value;
369       case LONG:
370         return value;
371       case DOUBLE:
372         return value;
373       case SHORT:
374         return value;
375       case SQLDATE:
376         return new Date(((Date) value).getTime());
377       case TIMESTAMP:
378         return new Timestamp(((Timestamp) value).getTime());
379       case STRING:
380         return value;
381       case XVAL:
382         return new XmlValue((XmlValue) value);
383       case EMPTY:
384       default:
385         throw new InvalidObjectException(
386             CAN_NOT_CONVERT_VALUE_FROM + value.getClass() + TO_TYPE +
387             type.classType);
388     }
389   }
390 
391   /**
392    * @return a clone of the value as Object (might be null if multiple)
393    *
394    * @throws InvalidObjectException
395    */
396   public final Object getCloneValue() throws InvalidObjectException {
397     if (getType() == XmlType.EMPTY) {
398       return new XmlValue(decl);
399     }
400     return getCloneValue(getType(), value);
401   }
402 
403   /**
404    * @return the value as a string
405    */
406   public final String getString() {
407     if (getType().isString()) {
408       return XmlUtil.getExtraTrimed((String) value);
409     }
410     throw new IllegalArgumentException(
411         CAN_NOT_CONVERT_VALUE_FROM + decl.getClassType() + " to type String");
412   }
413 
414   /**
415    * @return the value as an integer
416    */
417   public final int getInteger() {
418     if (getType().isInteger()) {
419       return (Integer) value;
420     }
421     throw new IllegalArgumentException(
422         CAN_NOT_CONVERT_VALUE_FROM + decl.getClassType() + " to type Integer");
423   }
424 
425   /**
426    * @return the value as a boolean
427    */
428   public final boolean getBoolean() {
429     if (getType().isBoolean()) {
430       return (Boolean) value;
431     }
432     throw new IllegalArgumentException(
433         CAN_NOT_CONVERT_VALUE_FROM + decl.getClassType() + " to type Boolean");
434   }
435 
436   /**
437    * @return the value as a long
438    */
439   public final long getLong() {
440     if (getType().isLong()) {
441       return (Long) value;
442     }
443     throw new IllegalArgumentException(
444         CAN_NOT_CONVERT_VALUE_FROM + decl.getClassType() + " to type Long");
445   }
446 
447   /**
448    * @return the value as a float
449    */
450   public final float getFloat() {
451     if (getType().isFloat()) {
452       return (Float) value;
453     }
454     throw new IllegalArgumentException(
455         CAN_NOT_CONVERT_VALUE_FROM + decl.getClassType() + " to type Float");
456   }
457 
458   /**
459    * @return the value as a float
460    */
461   public final char getCharacter() {
462     if (getType().isCharacter()) {
463       return (Character) value;
464     }
465     throw new IllegalArgumentException(
466         CAN_NOT_CONVERT_VALUE_FROM + decl.getClassType() +
467         " to type Character");
468   }
469 
470   /**
471    * @return the value as a float
472    */
473   public final byte getByte() {
474     if (getType().isByte()) {
475       return (Byte) value;
476     }
477     throw new IllegalArgumentException(
478         CAN_NOT_CONVERT_VALUE_FROM + decl.getClassType() + " to type Byte");
479   }
480 
481   /**
482    * @return the value as a float
483    */
484   public final double getDouble() {
485     if (getType().isDouble()) {
486       return (Double) value;
487     }
488     throw new IllegalArgumentException(
489         CAN_NOT_CONVERT_VALUE_FROM + decl.getClassType() + " to type Double");
490   }
491 
492   /**
493    * @return the value as a float
494    */
495   public final short getShort() {
496     if (getType().isShort()) {
497       return (Short) value;
498     }
499     throw new IllegalArgumentException(
500         CAN_NOT_CONVERT_VALUE_FROM + decl.getClassType() + " to type Short");
501   }
502 
503   /**
504    * @return the value as a float
505    */
506   public final Date getDate() {
507     if (getType().isDate()) {
508       return (Date) value;
509     }
510     throw new IllegalArgumentException(
511         CAN_NOT_CONVERT_VALUE_FROM + decl.getClassType() + " to type Date");
512   }
513 
514   /**
515    * @return the value as a float
516    */
517   public final Timestamp getTimestamp() {
518     if (getType().isTimestamp()) {
519       return (Timestamp) value;
520     }
521     throw new IllegalArgumentException(
522         CAN_NOT_CONVERT_VALUE_FROM + decl.getClassType() +
523         " to type Timestamp");
524   }
525 
526   /**
527    * Set a value from String
528    *
529    * @param value
530    *
531    * @throws InvalidArgumentException
532    */
533   public final void setFromString(final String value)
534       throws InvalidArgumentException {
535     this.value = convert(getClassType(), XmlUtil.getExtraTrimed(value));
536   }
537 
538   /**
539    * Test if the Value is empty. If it is a SubXml or isMultiple, check if
540    * subnodes are present but not if those
541    * nodes are empty.
542    *
543    * @return True if the Value is Empty
544    */
545   public final boolean isEmpty() {
546     if (isSubXml()) {
547       if (isMultiple()) {
548         return values.isEmpty();
549       } else {
550         return subXml.length == 0;
551       }
552     }
553     if (isMultiple()) {
554       return values.isEmpty();
555     } else {
556       return value == null;
557     }
558   }
559 
560   /**
561    * Get a value into a String
562    *
563    * @return the value in String format
564    */
565   public final String getIntoString() {
566     if (!isMultiple() && !isSubXml()) {
567       if (value != null) {
568         return value.toString();
569       } else {
570         return "";
571       }
572     } else {
573       throw new IllegalArgumentException(
574           "Cannot convert Multiple values to single String");
575     }
576   }
577 
578   /**
579    * @param value the value to set
580    *
581    * @throws InvalidObjectException
582    * @throws NumberFormatException
583    */
584   @SuppressWarnings("unchecked")
585   public final void setValue(final Object value) throws InvalidObjectException {
586     if (getType().isNativelyCompatible(value)) {
587       switch (getType()) {
588         case BOOLEAN:
589         case TIMESTAMP:
590         case SHORT:
591         case DOUBLE:
592         case LONG:
593         case BYTE:
594         case CHARACTER:
595         case FLOAT:
596         case INTEGER:
597           this.value = value;
598           break;
599         case SQLDATE:
600           if (Date.class.isAssignableFrom(value.getClass())) {
601             this.value = value;
602           } else if (java.util.Date.class.isAssignableFrom(value.getClass())) {
603             this.value = new Date(((java.util.Date) value).getTime());
604           }
605           break;
606         case STRING:
607           this.value = XmlUtil.getExtraTrimed((String) value);
608           break;
609         case XVAL:
610           final XmlValue[] newValue = (XmlValue[]) value;
611           if (isSubXml()) {
612             // should check also internal XmlDecl equality but
613             // can only check size
614             if (decl.getSubXmlSize() != newValue.length) {
615               throw new InvalidObjectException(
616                   "XmlDecl are not compatible from Array of XmlValue" +
617                   TO_TYPE + getClassType());
618             }
619             if (isMultiple()) {
620               ((List<XmlValue[]>) values).add(newValue);
621             } else {
622               subXml = newValue;
623             }
624           } else {
625             throw new InvalidObjectException(
626                 "Can not convert value from Array of XmlValue" + TO_TYPE +
627                 getClassType());
628           }
629           break;
630         default:
631           throw new InvalidObjectException(
632               CAN_NOT_CONVERT_VALUE_FROM + value.getClass() + TO_TYPE +
633               getClassType());
634       }
635     } else {
636       throw new InvalidObjectException(
637           CAN_NOT_CONVERT_VALUE_FROM + value.getClass() + TO_TYPE +
638           getClassType());
639     }
640   }
641 
642   /**
643    * Convert String value to the specified type. Throws
644    * InvalidArgumentException
645    * if type is unrecognized.
646    *
647    * @throws InvalidArgumentException
648    */
649   protected static Object convert(final Class<?> type, final String value)
650       throws InvalidArgumentException {
651     try {
652       // test from specific to general
653       //
654       if (String.class.isAssignableFrom(type)) {
655         return value;
656       }
657       // primitives
658       //
659       else if (type.equals(Boolean.TYPE)) {
660         if ("1".equals(value)) {
661           return Boolean.TRUE;
662         }
663         return Boolean.valueOf(value);
664       } else if (type.equals(Integer.TYPE)) {
665         return Integer.valueOf(value);
666       } else if (type.equals(Float.TYPE)) {
667         return Float.valueOf(value);
668       } else if (type.equals(Character.TYPE)) {
669         return Character.valueOf(value.charAt(0));
670       } else if (type.equals(Byte.TYPE)) {
671         return Byte.valueOf(value);
672       } else if (type.equals(Long.TYPE)) {
673         return Long.valueOf(value);
674       } else if (type.equals(Double.TYPE)) {
675         return Double.valueOf(value);
676       } else if (type.equals(Short.TYPE)) {
677         return Short.valueOf(value);
678       }
679       // primitive wrappers
680       //
681       else if (Boolean.class.isAssignableFrom(type)) {
682         if ("true".equalsIgnoreCase(value)) {
683           return Boolean.TRUE;
684         } else {
685           return Boolean.FALSE;
686         }
687       } else if (Character.class.isAssignableFrom(type)) {
688         if (value.length() == 1) {
689           return Character.valueOf(value.charAt(0));
690         } else {
691           throw new IllegalArgumentException(
692               CAN_NOT_CONVERT_VALUE + value + TO_TYPE + type);
693         }
694       } else if (Number.class.isAssignableFrom(type)) {
695         if (Double.class.isAssignableFrom(type)) {
696           return Double.valueOf(value);
697         } else if (Float.class.isAssignableFrom(type)) {
698           return Float.valueOf(value);
699         } else if (Integer.class.isAssignableFrom(type)) {
700           return Integer.valueOf(value);
701         } else if (Long.class.isAssignableFrom(type)) {
702           return Long.valueOf(value);
703         } else if (Short.class.isAssignableFrom(type)) {
704           return Short.valueOf(value);
705         }
706         // other primitive-like classes
707         //
708         else if (BigDecimal.class.isAssignableFrom(type)) {
709           throw new IllegalArgumentException("Can not use type " + type);
710         } else if (BigInteger.class.isAssignableFrom(type)) {
711           throw new IllegalArgumentException("Can not use type " + type);
712         } else {
713           throw new IllegalArgumentException(
714               CAN_NOT_CONVERT_VALUE + value + TO_TYPE + type);
715         }
716       }
717       //
718       // Time and date. We stick close to the JDBC representations
719       // for time and date, but add the "GMT" timezone so XML files
720       // can be transferred across timezones without ambiguity. See
721       // java.sql.Date.toString() and java.sql.Timestamp.toString().
722       //
723       else if (Date.class.isAssignableFrom(type)) {
724         return new Date(
725             WaarpStringUtils.getDateFormat().parse(value).getTime());
726       } else if (Timestamp.class.isAssignableFrom(type)) {
727         final int dotIndex = value.indexOf('.');
728         final int spaceIndex = value.indexOf(' ', dotIndex);
729         if (dotIndex < 0 || spaceIndex < 0) {
730           throw new IllegalArgumentException(
731               CAN_NOT_CONVERT_VALUE + value + TO_TYPE + type);
732         }
733         final Timestamp ts = new Timestamp(WaarpStringUtils.getTimestampFormat()
734                                                            .parse(
735                                                                value.substring(
736                                                                    0, dotIndex))
737                                                            .getTime());
738         final int nanos =
739             Integer.parseInt(value.substring(dotIndex + 1, spaceIndex));
740         ts.setNanos(nanos);
741 
742         return ts;
743       } else if (java.util.Date.class.isAssignableFrom(type)) {
744         // Should not be
745         return new Date(
746             WaarpStringUtils.getTimeFormat().parse(value).getTime());
747       } else {
748         throw new IllegalArgumentException(
749             CAN_NOT_CONVERT_VALUE + value + TO_TYPE + type);
750       }
751     } catch (final NumberFormatException e) {
752       throw new InvalidArgumentException(
753           CAN_NOT_CONVERT_VALUE + value + TO_TYPE + type);
754     } catch (final IllegalArgumentException e) {
755       throw new InvalidArgumentException(
756           CAN_NOT_CONVERT_VALUE + value + TO_TYPE + type, e);
757     } catch (final ParseException e) {
758       throw new InvalidArgumentException(
759           CAN_NOT_CONVERT_VALUE + value + TO_TYPE + type);
760     }
761   }
762 
763   @Override
764   public String toString() {
765     return "Val: " + (isMultiple()? values.size() + " elements" :
766         value != null? value.toString() :
767             subXml != null? "subXml" : "no value") + ' ' + decl;
768   }
769 
770   public final String toFullString() {
771     final StringBuilder detail = new StringBuilder("Val: " + (isMultiple()?
772         values.size() + " elements" : value != null? value.toString() :
773         subXml != null? "subXml" : "no value") + ' ' + decl);
774     if (decl.isSubXml()) {
775       if (isMultiple()) {
776         detail.append('[');
777         for (final Object obj : values) {
778           if (obj instanceof XmlValue) {
779             detail.append(((XmlValue) obj).toFullString()).append(", ");
780           } else {
781             detail.append('[');
782             for (final XmlValue obj2 : (XmlValue[]) obj) {
783               detail.append(obj2.toFullString()).append(", ");
784             }
785             detail.append("], ");
786           }
787         }
788         detail.append(']');
789       } else {
790         detail.append('[');
791         for (final XmlValue obj : subXml) {
792           detail.append(obj.toFullString()).append(", ");
793         }
794         detail.append(']');
795       }
796     }
797     return detail.toString();
798   }
799 }