1 /*
   2  * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.xml.internal.bind.v2.model.impl;
  27 
  28 import java.awt.Component;
  29 import java.awt.Graphics;
  30 import java.awt.Image;
  31 import java.awt.MediaTracker;
  32 import java.awt.image.BufferedImage;
  33 import java.io.ByteArrayInputStream;
  34 import java.io.File;
  35 import java.io.IOException;
  36 import java.io.InputStream;
  37 import java.io.OutputStreamWriter;
  38 import java.io.UnsupportedEncodingException;
  39 import java.lang.reflect.Type;
  40 import java.math.BigDecimal;
  41 import java.math.BigInteger;
  42 import java.net.MalformedURLException;
  43 import java.net.URI;
  44 import java.net.URISyntaxException;
  45 import java.net.URL;
  46 import java.util.ArrayList;
  47 import java.util.Calendar;
  48 import java.util.Collections;
  49 import java.util.Date;
  50 import java.util.GregorianCalendar;
  51 import java.util.HashMap;
  52 import java.util.Iterator;
  53 import java.util.List;
  54 import java.util.Map;
  55 import java.util.UUID;
  56 
  57 import javax.activation.DataHandler;
  58 import javax.activation.DataSource;
  59 import javax.activation.MimeType;
  60 import javax.activation.MimeTypeParseException;
  61 import javax.imageio.ImageIO;
  62 import javax.imageio.ImageWriter;
  63 import javax.imageio.stream.ImageOutputStream;
  64 import javax.xml.bind.ValidationEvent;
  65 import javax.xml.bind.helpers.ValidationEventImpl;
  66 import javax.xml.datatype.DatatypeConfigurationException;
  67 import javax.xml.datatype.DatatypeConstants;
  68 import javax.xml.datatype.DatatypeFactory;
  69 import javax.xml.datatype.Duration;
  70 import javax.xml.datatype.XMLGregorianCalendar;
  71 import javax.xml.namespace.QName;
  72 import javax.xml.stream.XMLStreamException;
  73 import javax.xml.transform.OutputKeys;
  74 import javax.xml.transform.Source;
  75 import javax.xml.transform.Transformer;
  76 import javax.xml.transform.TransformerException;
  77 import javax.xml.transform.stream.StreamResult;
  78 
  79 import com.sun.istack.internal.ByteArrayDataSource;
  80 import com.sun.xml.internal.bind.DatatypeConverterImpl;
  81 import com.sun.xml.internal.bind.WhiteSpaceProcessor;
  82 import com.sun.xml.internal.bind.api.AccessorException;
  83 import com.sun.xml.internal.bind.v2.TODO;
  84 import com.sun.xml.internal.bind.v2.WellKnownNamespace;
  85 import com.sun.xml.internal.bind.v2.model.runtime.RuntimeBuiltinLeafInfo;
  86 import com.sun.xml.internal.bind.v2.runtime.Name;
  87 import com.sun.xml.internal.bind.v2.runtime.Transducer;
  88 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
  89 import com.sun.xml.internal.bind.v2.runtime.output.Pcdata;
  90 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data;
  91 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext;
  92 import com.sun.xml.internal.bind.v2.util.ByteArrayOutputStreamEx;
  93 import com.sun.xml.internal.bind.v2.util.DataSourceSource;
  94 import java.util.logging.Logger;
  95 import com.sun.xml.internal.bind.Util;
  96 import java.util.logging.Level;
  97 
  98 import org.xml.sax.SAXException;
  99 
 100 /**
 101  * {@link BuiltinLeafInfoImpl} with a support for runtime.
 102  *
 103  * <p>
 104  * In particular this class defines {@link Transducer}s for the built-in types.
 105  *
 106  * @author Kohsuke Kawaguchi
 107  */
 108 public abstract class RuntimeBuiltinLeafInfoImpl<T> extends BuiltinLeafInfoImpl<Type,Class>
 109     implements RuntimeBuiltinLeafInfo, Transducer<T> {
 110 
 111     private static final Logger logger = Util.getClassLogger();
 112 
 113     private RuntimeBuiltinLeafInfoImpl(Class type, QName... typeNames) {
 114         super(type, typeNames);
 115         LEAVES.put(type,this);
 116     }
 117 
 118     public final Class getClazz() {
 119         return (Class)getType();
 120     }
 121 
 122 
 123     public final Transducer getTransducer() {
 124         return this;
 125     }
 126 
 127     public boolean useNamespace() {
 128         return false;
 129     }
 130 
 131     public final boolean isDefault() {
 132         return true;
 133     }
 134 
 135     public void declareNamespace(T o, XMLSerializer w) throws AccessorException {
 136     }
 137 
 138     public QName getTypeName(T instance) {
 139         return null;
 140     }
 141 
 142     /**
 143      * Those built-in types that print to {@link String}.
 144      */
 145     private static abstract class StringImpl<T> extends RuntimeBuiltinLeafInfoImpl<T> {
 146         protected StringImpl(Class type, QName... typeNames) {
 147             super(type,typeNames);
 148         }
 149 
 150         public abstract String print(T o) throws AccessorException;
 151 
 152         public void writeText(XMLSerializer w, T o, String fieldName) throws IOException, SAXException, XMLStreamException, AccessorException {
 153             w.text(print(o),fieldName);
 154         }
 155 
 156         public void writeLeafElement(XMLSerializer w, Name tagName, T o, String fieldName) throws IOException, SAXException, XMLStreamException, AccessorException {
 157             w.leafElement(tagName,print(o),fieldName);
 158         }
 159     }
 160 
 161     /**
 162      * Those built-in types that print to {@link Pcdata}.
 163      */
 164     private static abstract class PcdataImpl<T> extends RuntimeBuiltinLeafInfoImpl<T> {
 165         protected PcdataImpl(Class type, QName... typeNames) {
 166             super(type,typeNames);
 167         }
 168 
 169         public abstract Pcdata print(T o) throws AccessorException;
 170 
 171         public final void writeText(XMLSerializer w, T o, String fieldName) throws IOException, SAXException, XMLStreamException, AccessorException {
 172             w.text(print(o),fieldName);
 173         }
 174 
 175         public final void writeLeafElement(XMLSerializer w, Name tagName, T o, String fieldName) throws IOException, SAXException, XMLStreamException, AccessorException {
 176             w.leafElement(tagName,print(o),fieldName);
 177         }
 178 
 179     }
 180 
 181     /**
 182      * All instances of {@link RuntimeBuiltinLeafInfoImpl}s keyed by their type.
 183      */
 184     public static final Map<Type,RuntimeBuiltinLeafInfoImpl<?>> LEAVES = new HashMap<Type, RuntimeBuiltinLeafInfoImpl<?>>();
 185 
 186     private static QName createXS(String typeName) {
 187         return new QName(WellKnownNamespace.XML_SCHEMA,typeName);
 188     }
 189 
 190     public static final RuntimeBuiltinLeafInfoImpl<String> STRING;
 191 
 192     private static final String DATE = "date";
 193 
 194     /**
 195      * List of all {@link RuntimeBuiltinLeafInfoImpl}s.
 196      *
 197      * <p>
 198      * This corresponds to the built-in Java classes that are specified to be
 199      * handled differently than ordinary classes. See table 8-2 "Mapping of Standard Java classes".
 200      */
 201     public static final List<RuntimeBuiltinLeafInfoImpl<?>> builtinBeanInfos;
 202 
 203     public static final String MAP_ANYURI_TO_URI = "mapAnyUriToUri";
 204     public static final String USE_OLD_GMONTH_MAPPING = "jaxb.ri.useOldGmonthMapping";
 205 
 206     static {
 207 
 208         QName[] qnames = (System.getProperty(MAP_ANYURI_TO_URI) == null) ? new QName[] {
 209                                 createXS("string"),
 210                                 createXS("anySimpleType"),
 211                                 createXS("normalizedString"),
 212                                 createXS("anyURI"),
 213                                 createXS("token"),
 214                                 createXS("language"),
 215                                 createXS("Name"),
 216                                 createXS("NCName"),
 217                                 createXS("NMTOKEN"),
 218                                 createXS("ENTITY")}
 219                                     :
 220                          new QName[] {
 221                                 createXS("string"),
 222                                 createXS("anySimpleType"),
 223                                 createXS("normalizedString"),
 224                                 createXS("token"),
 225                                 createXS("language"),
 226                                 createXS("Name"),
 227                                 createXS("NCName"),
 228                                 createXS("NMTOKEN"),
 229                                 createXS("ENTITY")};
 230 
 231         STRING = new StringImplImpl(String.class, qnames);
 232 
 233         ArrayList<RuntimeBuiltinLeafInfoImpl<?>> secondaryList = new ArrayList<RuntimeBuiltinLeafInfoImpl<?>>();
 234             /*
 235                 There are cases where more than one Java classes map to the same XML type.
 236                 But when we see the same XML type in an incoming document, we only pick
 237                 one of those Java classes to unmarshal. This Java class is called 'primary'.
 238                 The rest are called 'secondary'.
 239 
 240                 Currently we lack the proper infrastructure to handle those nicely.
 241                 For now, we rely on a hack.
 242 
 243                 We define secondary mappings first, then primary ones later. GrammarInfo
 244                 builds a map from type name to BeanInfo. By defining primary ones later,
 245                 those primary bindings will overwrite the secondary ones.
 246             */
 247 
 248             /*
 249                 secondary bindings
 250             */
 251         secondaryList.add(
 252             new StringImpl<Character>(Character.class, createXS("unsignedShort")) {
 253                 public Character parse(CharSequence text) {
 254                     // TODO.checkSpec("default mapping for char is not defined yet");
 255                     return (char)DatatypeConverterImpl._parseInt(text);
 256                 }
 257                 public String print(Character v) {
 258                     return Integer.toString(v);
 259                 }
 260             });
 261         secondaryList.add(
 262             new StringImpl<Calendar>(Calendar.class, DatatypeConstants.DATETIME) {
 263                 public Calendar parse(CharSequence text) {
 264                     return DatatypeConverterImpl._parseDateTime(text.toString());
 265                 }
 266                 public String print(Calendar v) {
 267                     return DatatypeConverterImpl._printDateTime(v);
 268                 }
 269             });
 270         secondaryList.add(
 271             new StringImpl<GregorianCalendar>(GregorianCalendar.class, DatatypeConstants.DATETIME) {
 272                 public GregorianCalendar parse(CharSequence text) {
 273                     return DatatypeConverterImpl._parseDateTime(text.toString());
 274                 }
 275                 public String print(GregorianCalendar v) {
 276                     return DatatypeConverterImpl._printDateTime(v);
 277                 }
 278             });
 279         secondaryList.add(
 280             new StringImpl<Date>(Date.class, DatatypeConstants.DATETIME) {
 281                 public Date parse(CharSequence text) {
 282                     return DatatypeConverterImpl._parseDateTime(text.toString()).getTime();
 283                 }
 284                 public String print(Date v) {
 285                     XMLSerializer xs = XMLSerializer.getInstance();
 286                     QName type = xs.getSchemaType();
 287                     GregorianCalendar cal = new GregorianCalendar(0,0,0);
 288                     cal.setTime(v);
 289                     if ((type != null) && (WellKnownNamespace.XML_SCHEMA.equals(type.getNamespaceURI())) &&
 290                             DATE.equals(type.getLocalPart())) {
 291                         return DatatypeConverterImpl._printDate(cal);
 292                     } else {
 293                         return DatatypeConverterImpl._printDateTime(cal);
 294                     }
 295                 }
 296             });
 297         secondaryList.add(
 298             new StringImpl<File>(File.class, createXS("string")) {
 299                 public File parse(CharSequence text) {
 300                     return new File(WhiteSpaceProcessor.trim(text).toString());
 301                 }
 302                 public String print(File v) {
 303                     return v.getPath();
 304                 }
 305             });
 306         secondaryList.add(
 307             new StringImpl<URL>(URL.class, createXS("anyURI")) {
 308                 public URL parse(CharSequence text) throws SAXException {
 309                     TODO.checkSpec("JSR222 Issue #42");
 310                     try {
 311                         return new URL(WhiteSpaceProcessor.trim(text).toString());
 312                     } catch (MalformedURLException e) {
 313                         UnmarshallingContext.getInstance().handleError(e);
 314                         return null;
 315                     }
 316                 }
 317                 public String print(URL v) {
 318                     return v.toExternalForm();
 319                 }
 320             });
 321         if (System.getProperty(MAP_ANYURI_TO_URI) == null) {
 322             secondaryList.add(
 323                 new StringImpl<URI>(URI.class, createXS("string")) {
 324                     public URI parse(CharSequence text) throws SAXException {
 325                         try {
 326                             return new URI(text.toString());
 327                         } catch (URISyntaxException e) {
 328                             UnmarshallingContext.getInstance().handleError(e);
 329                             return null;
 330                         }
 331                     }
 332 
 333                     public String print(URI v) {
 334                         return v.toString();
 335                     }
 336                 });
 337         }
 338         secondaryList.add(
 339             new StringImpl<Class>(Class.class, createXS("string")) {
 340                 public Class parse(CharSequence text) throws SAXException {
 341                     TODO.checkSpec("JSR222 Issue #42");
 342                     try {
 343                         String name = WhiteSpaceProcessor.trim(text).toString();
 344                         ClassLoader cl = UnmarshallingContext.getInstance().classLoader;
 345                         if(cl==null)
 346                             cl = Thread.currentThread().getContextClassLoader();
 347 
 348                         if(cl!=null)
 349                             return cl.loadClass(name);
 350                         else
 351                             return Class.forName(name);
 352                     } catch (ClassNotFoundException e) {
 353                         UnmarshallingContext.getInstance().handleError(e);
 354                         return null;
 355                     }
 356                 }
 357                 public String print(Class v) {
 358                     return v.getName();
 359                 }
 360             });
 361 
 362             /*
 363                 classes that map to base64Binary / MTOM related classes.
 364                 a part of the secondary binding.
 365             */
 366         secondaryList.add(
 367             new PcdataImpl<Image>(Image.class, createXS("base64Binary")) {
 368                 public Image parse(CharSequence text) throws SAXException  {
 369                     try {
 370                         InputStream is;
 371                         if(text instanceof Base64Data)
 372                             is = ((Base64Data)text).getInputStream();
 373                         else
 374                             is = new ByteArrayInputStream(decodeBase64(text)); // TODO: buffering is inefficient
 375 
 376                         // technically we should check the MIME type here, but
 377                         // normally images can be content-sniffed.
 378                         // so the MIME type check will only make us slower and draconian, both of which
 379                         // JAXB 2.0 isn't interested.
 380                         try {
 381                             return ImageIO.read(is);
 382                         } finally {
 383                             is.close();
 384                         }
 385                     } catch (IOException e) {
 386                         UnmarshallingContext.getInstance().handleError(e);
 387                         return null;
 388                     }
 389                 }
 390 
 391                 private BufferedImage convertToBufferedImage(Image image) throws IOException {
 392                     if (image instanceof BufferedImage) {
 393                         return (BufferedImage)image;
 394 
 395                     } else {
 396                         MediaTracker tracker = new MediaTracker(new Component(){}); // not sure if this is the right thing to do.
 397                         tracker.addImage(image, 0);
 398                         try {
 399                             tracker.waitForAll();
 400                         } catch (InterruptedException e) {
 401                             throw new IOException(e.getMessage());
 402                         }
 403                         BufferedImage bufImage = new BufferedImage(
 404                                 image.getWidth(null),
 405                                 image.getHeight(null),
 406                                 BufferedImage.TYPE_INT_ARGB);
 407 
 408                         Graphics g = bufImage.createGraphics();
 409                         g.drawImage(image, 0, 0, null);
 410                         return bufImage;
 411                     }
 412                 }
 413 
 414                 public Base64Data print(Image v) {
 415                     ByteArrayOutputStreamEx imageData = new ByteArrayOutputStreamEx();
 416                     XMLSerializer xs = XMLSerializer.getInstance();
 417 
 418                     String mimeType = xs.getXMIMEContentType();
 419                     if(mimeType==null || mimeType.startsWith("image/*"))
 420                         // because PNG is lossless, it's a good default
 421                         //
 422                         // mime type can be a range, in which case we can't just pass that
 423                         // to ImageIO.getImageWritersByMIMEType, so here I'm just assuming
 424                         // the default of PNG. Not sure if this is complete.
 425                         mimeType = "image/png";
 426 
 427                     try {
 428                         Iterator<ImageWriter> itr = ImageIO.getImageWritersByMIMEType(mimeType);
 429                         if(itr.hasNext()) {
 430                             ImageWriter w = itr.next();
 431                             ImageOutputStream os = ImageIO.createImageOutputStream(imageData);
 432                             w.setOutput(os);
 433                             w.write(convertToBufferedImage(v));
 434                             os.close();
 435                             w.dispose();
 436                         } else {
 437                             // no encoder
 438                             xs.handleEvent(new ValidationEventImpl(
 439                                 ValidationEvent.ERROR,
 440                                 Messages.NO_IMAGE_WRITER.format(mimeType),
 441                                 xs.getCurrentLocation(null) ));
 442                             // TODO: proper error reporting
 443                             throw new RuntimeException("no encoder for MIME type "+mimeType);
 444                         }
 445                     } catch (IOException e) {
 446                         xs.handleError(e);
 447                         // TODO: proper error reporting
 448                         throw new RuntimeException(e);
 449                     }
 450                     Base64Data bd = new Base64Data();
 451                     imageData.set(bd,mimeType);
 452                     return bd;
 453                 }
 454             });
 455         secondaryList.add(
 456             new PcdataImpl<DataHandler>(DataHandler.class, createXS("base64Binary")) {
 457                 public DataHandler parse(CharSequence text) {
 458                     if(text instanceof Base64Data)
 459                         return ((Base64Data)text).getDataHandler();
 460                     else
 461                         return new DataHandler(new ByteArrayDataSource(decodeBase64(text),
 462                             UnmarshallingContext.getInstance().getXMIMEContentType()));
 463                 }
 464 
 465                 public Base64Data print(DataHandler v) {
 466                     Base64Data bd = new Base64Data();
 467                     bd.set(v);
 468                     return bd;
 469                 }
 470             });
 471         secondaryList.add(
 472             new PcdataImpl<Source>(Source.class, createXS("base64Binary")) {
 473                 public Source parse(CharSequence text) throws SAXException  {
 474                     try {
 475                         if(text instanceof Base64Data)
 476                             return new DataSourceSource( ((Base64Data)text).getDataHandler() );
 477                         else
 478                             return new DataSourceSource(new ByteArrayDataSource(decodeBase64(text),
 479                                 UnmarshallingContext.getInstance().getXMIMEContentType()));
 480                     } catch (MimeTypeParseException e) {
 481                         UnmarshallingContext.getInstance().handleError(e);
 482                         return null;
 483                     }
 484                 }
 485 
 486                 public Base64Data print(Source v) {
 487                     XMLSerializer xs = XMLSerializer.getInstance();
 488                     Base64Data bd = new Base64Data();
 489 
 490                     String contentType = xs.getXMIMEContentType();
 491                     MimeType mt = null;
 492                     if(contentType!=null)
 493                         try {
 494                             mt = new MimeType(contentType);
 495                         } catch (MimeTypeParseException e) {
 496                             xs.handleError(e);
 497                             // recover by ignoring the content type specification
 498                         }
 499 
 500                     if( v instanceof DataSourceSource ) {
 501                         // if so, we already have immutable DataSource so
 502                         // this can be done efficiently
 503                         DataSource ds = ((DataSourceSource)v).getDataSource();
 504 
 505                         String dsct = ds.getContentType();
 506                         if(dsct!=null && (contentType==null || contentType.equals(dsct))) {
 507                             bd.set(new DataHandler(ds));
 508                             return bd;
 509                         }
 510                     }
 511 
 512                     // general case. slower.
 513 
 514                     // find out the encoding
 515                     String charset=null;
 516                     if(mt!=null)
 517                         charset = mt.getParameter("charset");
 518                     if(charset==null)
 519                         charset = "UTF-8";
 520 
 521                     try {
 522                         ByteArrayOutputStreamEx baos = new ByteArrayOutputStreamEx();
 523                         Transformer tr = xs.getIdentityTransformer();
 524                         String defaultEncoding = tr.getOutputProperty(OutputKeys.ENCODING);
 525                         tr.setOutputProperty(OutputKeys.ENCODING, charset);
 526                         tr.transform(v, new StreamResult(new OutputStreamWriter(baos,charset)));
 527                         tr.setOutputProperty(OutputKeys.ENCODING, defaultEncoding);
 528                         baos.set(bd,"application/xml; charset="+charset);
 529                         return bd;
 530                     } catch (TransformerException e) {
 531                         // TODO: marshaller error handling
 532                         xs.handleError(e);
 533                     } catch (UnsupportedEncodingException e) {
 534                         xs.handleError(e);
 535                     }
 536 
 537                     // error recoverly
 538                     bd.set(new byte[0],"application/xml");
 539                     return bd;
 540                 }
 541             });
 542         secondaryList.add(
 543             new StringImpl<XMLGregorianCalendar>(XMLGregorianCalendar.class,
 544                     createXS("anySimpleType"),
 545                     DatatypeConstants.DATE,
 546                     DatatypeConstants.DATETIME,
 547                     DatatypeConstants.TIME,
 548                     DatatypeConstants.GMONTH,
 549                     DatatypeConstants.GDAY,
 550                     DatatypeConstants.GYEAR,
 551                     DatatypeConstants.GYEARMONTH,
 552                     DatatypeConstants.GMONTHDAY
 553                 ) {
 554                 public String print(XMLGregorianCalendar cal) {
 555                     XMLSerializer xs = XMLSerializer.getInstance();
 556 
 557                     QName type = xs.getSchemaType();
 558                     if (type != null) {
 559                         try {
 560                             checkXmlGregorianCalendarFieldRef(type, cal);
 561                             String format = xmlGregorianCalendarFormatString.get(type);
 562                             if (format != null) {
 563                                 return format(format, cal);
 564                             }
 565                         } catch (javax.xml.bind.MarshalException e) {
 566                             // see issue 649
 567                             xs.handleEvent(new ValidationEventImpl(ValidationEvent.WARNING, e.getMessage(),
 568                                 xs.getCurrentLocation(null) ));
 569                             return "";
 570                         }
 571                     }
 572                     return cal.toXMLFormat();
 573                 }
 574 
 575                 public XMLGregorianCalendar parse(CharSequence lexical) throws SAXException {
 576                     try {
 577                         return datatypeFactory.newXMLGregorianCalendar(lexical.toString().trim()); // (.trim() - issue 396)
 578                     } catch (Exception e) {
 579                         UnmarshallingContext.getInstance().handleError(e);
 580                         return null;
 581                     }
 582                 }
 583 
 584                 // code duplicated from JAXP RI 1.3. See 6277586
 585                 private String format( String format, XMLGregorianCalendar value ) {
 586                     StringBuilder buf = new StringBuilder();
 587                     int fidx=0,flen=format.length();
 588 
 589                     while(fidx<flen) {
 590                         char fch = format.charAt(fidx++);
 591                         if(fch!='%') {// not a meta char
 592                             buf.append(fch);
 593                             continue;
 594                         }
 595 
 596                         switch(format.charAt(fidx++)) {
 597                         case 'Y':
 598                             printNumber(buf,value.getEonAndYear(), 4);
 599                             break;
 600                         case 'M':
 601                             printNumber(buf,value.getMonth(),2);
 602                             break;
 603                         case 'D':
 604                             printNumber(buf,value.getDay(),2);
 605                             break;
 606                         case 'h':
 607                             printNumber(buf,value.getHour(),2);
 608                             break;
 609                         case 'm':
 610                             printNumber(buf,value.getMinute(),2);
 611                             break;
 612                         case 's':
 613                             printNumber(buf,value.getSecond(),2);
 614                     if (value.getFractionalSecond() != null) {
 615                         String frac = value.getFractionalSecond().toPlainString();
 616                         //skip leading zero.
 617                         buf.append(frac.substring(1, frac.length()));
 618                     }
 619                             break;
 620                         case 'z':
 621                     int offset = value.getTimezone();
 622                             if(offset == 0) {
 623                         buf.append('Z');
 624                     } else if (offset != DatatypeConstants.FIELD_UNDEFINED) {
 625                         if(offset<0) {
 626                         buf.append('-');
 627                         offset *= -1;
 628                         } else {
 629                         buf.append('+');
 630                         }
 631                         printNumber(buf,offset/60,2);
 632                                 buf.append(':');
 633                                 printNumber(buf,offset%60,2);
 634                             }
 635                             break;
 636                         default:
 637                             throw new InternalError();  // impossible
 638                         }
 639                     }
 640 
 641                     return buf.toString();
 642                 }
 643                 private void printNumber( StringBuilder out, BigInteger number, int nDigits) {
 644                     String s = number.toString();
 645                     for( int i=s.length(); i<nDigits; i++ )
 646                         out.append('0');
 647                     out.append(s);
 648                 }
 649                 private void printNumber( StringBuilder out, int number, int nDigits ) {
 650                     String s = String.valueOf(number);
 651                     for( int i=s.length(); i<nDigits; i++ )
 652                         out.append('0');
 653                     out.append(s);
 654                 }
 655                 @Override
 656                 public QName getTypeName(XMLGregorianCalendar cal) {
 657                     return cal.getXMLSchemaType();
 658                 }
 659             });
 660 
 661         ArrayList<RuntimeBuiltinLeafInfoImpl<?>> primaryList = new ArrayList<RuntimeBuiltinLeafInfoImpl<?>>();
 662 
 663         /*
 664             primary bindings
 665         */
 666         primaryList.add(STRING);
 667         primaryList.add(new StringImpl<Boolean>(Boolean.class,
 668                 createXS("boolean")
 669                 ) {
 670                 public Boolean parse(CharSequence text) {
 671                     return DatatypeConverterImpl._parseBoolean(text);
 672                 }
 673 
 674                 public String print(Boolean v) {
 675                     return v.toString();
 676                 }
 677             });
 678         primaryList.add(new PcdataImpl<byte[]>(byte[].class,
 679                 createXS("base64Binary"),
 680                 createXS("hexBinary")
 681                 ) {
 682                 public byte[] parse(CharSequence text) {
 683                     return decodeBase64(text);
 684                 }
 685 
 686                 public Base64Data print(byte[] v) {
 687                     XMLSerializer w = XMLSerializer.getInstance();
 688                     Base64Data bd = new Base64Data();
 689                     String mimeType = w.getXMIMEContentType();
 690                     bd.set(v,mimeType);
 691                     return bd;
 692                 }
 693             });
 694         primaryList.add(new StringImpl<Byte>(Byte.class,
 695                 createXS("byte")
 696                 ) {
 697                 public Byte parse(CharSequence text) {
 698                     return DatatypeConverterImpl._parseByte(text);
 699                 }
 700 
 701                 public String print(Byte v) {
 702                     return DatatypeConverterImpl._printByte(v);
 703                 }
 704             });
 705         primaryList.add(new StringImpl<Short>(Short.class,
 706                 createXS("short"),
 707                 createXS("unsignedByte")
 708                 ) {
 709                 public Short parse(CharSequence text) {
 710                     return DatatypeConverterImpl._parseShort(text);
 711                 }
 712 
 713                 public String print(Short v) {
 714                     return DatatypeConverterImpl._printShort(v);
 715                 }
 716             });
 717         primaryList.add(new StringImpl<Integer>(Integer.class,
 718                 createXS("int"),
 719                 createXS("unsignedShort")
 720                 ) {
 721                 public Integer parse(CharSequence text) {
 722                     return DatatypeConverterImpl._parseInt(text);
 723                 }
 724 
 725                 public String print(Integer v) {
 726                     return DatatypeConverterImpl._printInt(v);
 727                 }
 728             });
 729         primaryList.add(
 730             new StringImpl<Long>(Long.class,
 731                 createXS("long"),
 732                 createXS("unsignedInt")
 733                 ) {
 734                 public Long parse(CharSequence text) {
 735                     return DatatypeConverterImpl._parseLong(text);
 736                 }
 737 
 738                 public String print(Long v) {
 739                     return DatatypeConverterImpl._printLong(v);
 740                 }
 741             });
 742         primaryList.add(
 743             new StringImpl<Float>(Float.class,
 744                 createXS("float")
 745                 ) {
 746                 public Float parse(CharSequence text) {
 747                     return DatatypeConverterImpl._parseFloat(text.toString());
 748                 }
 749 
 750                 public String print(Float v) {
 751                     return DatatypeConverterImpl._printFloat(v);
 752                 }
 753             });
 754         primaryList.add(
 755             new StringImpl<Double>(Double.class,
 756                 createXS("double")
 757                 ) {
 758                 public Double parse(CharSequence text) {
 759                     return DatatypeConverterImpl._parseDouble(text);
 760                 }
 761 
 762                 public String print(Double v) {
 763                     return DatatypeConverterImpl._printDouble(v);
 764                 }
 765             });
 766         primaryList.add(
 767             new StringImpl<BigInteger>(BigInteger.class,
 768                 createXS("integer"),
 769                 createXS("positiveInteger"),
 770                 createXS("negativeInteger"),
 771                 createXS("nonPositiveInteger"),
 772                 createXS("nonNegativeInteger"),
 773                 createXS("unsignedLong")
 774                 ) {
 775                 public BigInteger parse(CharSequence text) {
 776                     return DatatypeConverterImpl._parseInteger(text);
 777                 }
 778 
 779                 public String print(BigInteger v) {
 780                     return DatatypeConverterImpl._printInteger(v);
 781                 }
 782             });
 783         primaryList.add(
 784             new StringImpl<BigDecimal>(BigDecimal.class,
 785                 createXS("decimal")
 786                 ) {
 787                 public BigDecimal parse(CharSequence text) {
 788                     return DatatypeConverterImpl._parseDecimal(text.toString());
 789                 }
 790 
 791                 public String print(BigDecimal v) {
 792                     return DatatypeConverterImpl._printDecimal(v);
 793                 }
 794             });
 795         primaryList.add(
 796             new StringImpl<QName>(QName.class,
 797                 createXS("QName")
 798                 ) {
 799                 public QName parse(CharSequence text) throws SAXException {
 800                     try {
 801                         return DatatypeConverterImpl._parseQName(text.toString(),UnmarshallingContext.getInstance());
 802                     } catch (IllegalArgumentException e) {
 803                         UnmarshallingContext.getInstance().handleError(e);
 804                         return null;
 805                     }
 806                 }
 807 
 808                 public String print(QName v) {
 809                     return DatatypeConverterImpl._printQName(v,XMLSerializer.getInstance().getNamespaceContext());
 810                 }
 811 
 812                 @Override
 813                 public boolean useNamespace() {
 814                     return true;
 815                 }
 816 
 817                 @Override
 818                 public void declareNamespace(QName v, XMLSerializer w) {
 819                     w.getNamespaceContext().declareNamespace(v.getNamespaceURI(),v.getPrefix(),false);
 820                 }
 821             });
 822         if (System.getProperty(MAP_ANYURI_TO_URI) != null) {
 823             primaryList.add(
 824                 new StringImpl<URI>(URI.class, createXS("anyURI")) {
 825                     public URI parse(CharSequence text) throws SAXException {
 826                         try {
 827                             return new URI(text.toString());
 828                         } catch (URISyntaxException e) {
 829                             UnmarshallingContext.getInstance().handleError(e);
 830                             return null;
 831                         }
 832                     }
 833 
 834                     public String print(URI v) {
 835                         return v.toString();
 836                     }
 837                 });
 838         }
 839         primaryList.add(
 840             new StringImpl<Duration>(Duration.class,  createXS("duration")) {
 841                 public String print(Duration duration) {
 842                     return duration.toString();
 843                 }
 844 
 845                 public Duration parse(CharSequence lexical) {
 846                     TODO.checkSpec("JSR222 Issue #42");
 847                     return datatypeFactory.newDuration(lexical.toString());
 848                 }
 849             });
 850         primaryList.add(
 851             new StringImpl<Void>(Void.class) {
 852                 // 'void' binding isn't defined by the spec, but when the JAX-RPC processes user-defined
 853                 // methods like "int actionFoo()", they need this pseudo-void property.
 854 
 855                 public String print(Void value) {
 856                     return "";
 857                 }
 858 
 859                 public Void parse(CharSequence lexical) {
 860                     return null;
 861                 }
 862             });
 863 
 864         List<RuntimeBuiltinLeafInfoImpl<?>> l = new ArrayList<RuntimeBuiltinLeafInfoImpl<?>>(secondaryList.size()+primaryList.size()+1);
 865         l.addAll(secondaryList);
 866 
 867         // UUID may fail to load if we are running on JDK 1.4. Handle gracefully
 868         try {
 869             l.add(new UUIDImpl());
 870         } catch (LinkageError e) {
 871             // ignore
 872         }
 873 
 874         l.addAll(primaryList);
 875 
 876         builtinBeanInfos = Collections.unmodifiableList(l);
 877     }
 878 
 879     private static byte[] decodeBase64(CharSequence text) {
 880         if (text instanceof Base64Data) {
 881             Base64Data base64Data = (Base64Data) text;
 882             return base64Data.getExact();
 883         } else {
 884             return DatatypeConverterImpl._parseBase64Binary(text.toString());
 885         }
 886     }
 887 
 888 
 889     /**
 890      * Cached instance of {@link DatatypeFactory} to create
 891      * {@link XMLGregorianCalendar} and {@link Duration}.
 892      */
 893     private static final DatatypeFactory datatypeFactory = init();
 894 
 895     private static DatatypeFactory init() {
 896         try {
 897             return DatatypeFactory.newInstance();
 898         } catch (DatatypeConfigurationException e) {
 899             throw new Error(Messages.FAILED_TO_INITIALE_DATATYPE_FACTORY.format(),e);
 900         }
 901     }
 902 
 903         private static void checkXmlGregorianCalendarFieldRef(QName type,
 904                 XMLGregorianCalendar cal)throws javax.xml.bind.MarshalException{
 905                 StringBuilder buf = new StringBuilder();
 906                 int bitField = xmlGregorianCalendarFieldRef.get(type);
 907                 final int l = 0x1;
 908                 int pos = 0;
 909                 while (bitField != 0x0){
 910                         int bit = bitField & l;
 911                         bitField >>>= 4;
 912                         pos++;
 913 
 914                         if (bit == 1) {
 915                                 switch(pos){
 916                                         case 1:
 917                                                 if (cal.getSecond() == DatatypeConstants.FIELD_UNDEFINED){
 918                                                         buf.append("  ").append(Messages.XMLGREGORIANCALENDAR_SEC);
 919                                                 }
 920                                                 break;
 921                                         case 2:
 922                                                 if (cal.getMinute() == DatatypeConstants.FIELD_UNDEFINED){
 923                                                         buf.append("  ").append(Messages.XMLGREGORIANCALENDAR_MIN);
 924                                                 }
 925                                                 break;
 926                                         case 3:
 927                                                 if (cal.getHour() == DatatypeConstants.FIELD_UNDEFINED){
 928                                                         buf.append("  ").append(Messages.XMLGREGORIANCALENDAR_HR);
 929                                                 }
 930                                                 break;
 931                                         case 4:
 932                                                 if (cal.getDay() == DatatypeConstants.FIELD_UNDEFINED){
 933                                                         buf.append("  ").append(Messages.XMLGREGORIANCALENDAR_DAY);
 934                                                 }
 935                                                 break;
 936                                         case 5:
 937                                                 if (cal.getMonth() == DatatypeConstants.FIELD_UNDEFINED){
 938                                                         buf.append("  ").append(Messages.XMLGREGORIANCALENDAR_MONTH);
 939                                                 }
 940                                                 break;
 941                                         case 6:
 942                                                 if (cal.getYear() == DatatypeConstants.FIELD_UNDEFINED){
 943                                                         buf.append("  ").append(Messages.XMLGREGORIANCALENDAR_YEAR);
 944                                                 }
 945                                                 break;
 946                                         case 7:  // ignore timezone setting
 947                                                 break;
 948                                 }
 949                         }
 950                 }
 951                 if (buf.length() > 0){
 952                         throw new javax.xml.bind.MarshalException(
 953                          Messages.XMLGREGORIANCALENDAR_INVALID.format(type.getLocalPart())
 954                          + buf.toString());
 955                 }
 956         }
 957 
 958     /**
 959      * Format string for the {@link XMLGregorianCalendar}.
 960      */
 961     private static final Map<QName,String> xmlGregorianCalendarFormatString = new HashMap<QName, String>();
 962 
 963     static {
 964         Map<QName,String> m = xmlGregorianCalendarFormatString;
 965         // See 4971612: be careful for SCCS substitution
 966         m.put(DatatypeConstants.DATETIME,   "%Y-%M-%DT%h:%m:%s"+ "%z");
 967         m.put(DatatypeConstants.DATE,       "%Y-%M-%D" +"%z");
 968         m.put(DatatypeConstants.TIME,       "%h:%m:%s"+ "%z");
 969         if (System.getProperty(USE_OLD_GMONTH_MAPPING) == null) {
 970             m.put(DatatypeConstants.GMONTH, "--%M%z");      //  E2-12 Error. http://www.w3.org/2001/05/xmlschema-errata#e2-12
 971         } else {                                            //  backw. compatibility
 972             if (logger.isLoggable(Level.FINE)) {
 973                 logger.log(Level.FINE, "Old GMonth mapping used.");
 974             }
 975             m.put(DatatypeConstants.GMONTH, "--%M--%z");
 976         }
 977         m.put(DatatypeConstants.GDAY,       "---%D" + "%z");
 978         m.put(DatatypeConstants.GYEAR,      "%Y" + "%z");
 979         m.put(DatatypeConstants.GYEARMONTH, "%Y-%M" + "%z");
 980         m.put(DatatypeConstants.GMONTHDAY,  "--%M-%D" +"%z");
 981     }
 982 
 983         /**
 984          * Field designations for XMLGregorianCalendar format string.
 985          * sec          0x0000001
 986          * min          0x0000010
 987          * hrs          0x0000100
 988          * day          0x0001000
 989          * month        0x0010000
 990          * year         0x0100000
 991          * timezone     0x1000000
 992          */
 993         private static final Map<QName, Integer> xmlGregorianCalendarFieldRef =
 994                 new HashMap<QName, Integer>();
 995         static {
 996                 Map<QName, Integer> f = xmlGregorianCalendarFieldRef;
 997                 f.put(DatatypeConstants.DATETIME,   0x1111111);
 998                 f.put(DatatypeConstants.DATE,       0x1111000);
 999                 f.put(DatatypeConstants.TIME,       0x1000111);
1000                 f.put(DatatypeConstants.GDAY,       0x1001000);
1001                 f.put(DatatypeConstants.GMONTH,     0x1010000);
1002                 f.put(DatatypeConstants.GYEAR,      0x1100000);
1003                 f.put(DatatypeConstants.GYEARMONTH, 0x1110000);
1004                 f.put(DatatypeConstants.GMONTHDAY,  0x1011000);
1005         }
1006 
1007     /**
1008      * {@link RuntimeBuiltinLeafInfoImpl} for {@link UUID}.
1009      *
1010      * This class is given a name so that failing to load this class won't cause a fatal problem.
1011      */
1012     private static class UUIDImpl extends StringImpl<UUID> {
1013         public UUIDImpl() {
1014             super(UUID.class, RuntimeBuiltinLeafInfoImpl.createXS("string"));
1015         }
1016 
1017         public UUID parse(CharSequence text) throws SAXException {
1018             TODO.checkSpec("JSR222 Issue #42");
1019             try {
1020                 return UUID.fromString(WhiteSpaceProcessor.trim(text).toString());
1021             } catch (IllegalArgumentException e) {
1022                 UnmarshallingContext.getInstance().handleError(e);
1023                 return null;
1024             }
1025         }
1026 
1027         public String print(UUID v) {
1028             return v.toString();
1029         }
1030     }
1031 
1032     private static class StringImplImpl extends StringImpl<String> {
1033 
1034         public StringImplImpl(Class type, QName[] typeNames) {
1035             super(type, typeNames);
1036         }
1037 
1038         public String parse(CharSequence text) {
1039             return text.toString();
1040         }
1041 
1042         public String print(String s) {
1043             return s;
1044         }
1045 
1046         @Override
1047         public final void writeText(XMLSerializer w, String o, String fieldName) throws IOException, SAXException, XMLStreamException {
1048             w.text(o, fieldName);
1049         }
1050 
1051         @Override
1052         public final void writeLeafElement(XMLSerializer w, Name tagName, String o, String fieldName) throws IOException, SAXException, XMLStreamException {
1053             w.leafElement(tagName, o, fieldName);
1054         }
1055     }
1056 }