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
26package com.sun.xml.internal.bind.v2.model.impl;
27
28import java.awt.Component;
29import java.awt.Graphics;
30import java.awt.Image;
31import java.awt.MediaTracker;
32import java.awt.image.BufferedImage;
33import java.io.ByteArrayInputStream;
34import java.io.File;
35import java.io.IOException;
36import java.io.InputStream;
37import java.io.OutputStreamWriter;
38import java.io.UnsupportedEncodingException;
39import java.lang.reflect.Type;
40import java.math.BigDecimal;
41import java.math.BigInteger;
42import java.net.MalformedURLException;
43import java.net.URI;
44import java.net.URISyntaxException;
45import java.net.URL;
46import java.security.AccessController;
47import java.security.PrivilegedAction;
48import java.util.ArrayList;
49import java.util.Calendar;
50import java.util.Collections;
51import java.util.Date;
52import java.util.GregorianCalendar;
53import java.util.HashMap;
54import java.util.Iterator;
55import java.util.List;
56import java.util.Map;
57import java.util.UUID;
58
59import javax.activation.DataHandler;
60import javax.activation.DataSource;
61import javax.activation.MimeType;
62import javax.activation.MimeTypeParseException;
63import javax.imageio.ImageIO;
64import javax.imageio.ImageWriter;
65import javax.imageio.stream.ImageOutputStream;
66import javax.xml.bind.ValidationEvent;
67import javax.xml.bind.helpers.ValidationEventImpl;
68import javax.xml.datatype.DatatypeConstants;
69import javax.xml.datatype.Duration;
70import javax.xml.datatype.XMLGregorianCalendar;
71import javax.xml.namespace.QName;
72import javax.xml.stream.XMLStreamException;
73import javax.xml.transform.OutputKeys;
74import javax.xml.transform.Source;
75import javax.xml.transform.Transformer;
76import javax.xml.transform.TransformerException;
77import javax.xml.transform.stream.StreamResult;
78
79import com.sun.istack.internal.ByteArrayDataSource;
80import com.sun.xml.internal.bind.DatatypeConverterImpl;
81import com.sun.xml.internal.bind.WhiteSpaceProcessor;
82import com.sun.xml.internal.bind.api.AccessorException;
83import com.sun.xml.internal.bind.v2.TODO;
84import com.sun.xml.internal.bind.v2.WellKnownNamespace;
85import com.sun.xml.internal.bind.v2.model.runtime.RuntimeBuiltinLeafInfo;
86import com.sun.xml.internal.bind.v2.runtime.Name;
87import com.sun.xml.internal.bind.v2.runtime.Transducer;
88import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
89import com.sun.xml.internal.bind.v2.runtime.output.Pcdata;
90import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data;
91import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext;
92import com.sun.xml.internal.bind.v2.util.ByteArrayOutputStreamEx;
93import com.sun.xml.internal.bind.v2.util.DataSourceSource;
94import java.util.logging.Logger;
95import com.sun.xml.internal.bind.Util;
96import java.util.logging.Level;
97
98import 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 */
108public 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}
1059