View Javadoc

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