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