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