/*
* Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.internal.xjc.reader.xmlschema.bindinfo;
import javax.xml.bind.DatatypeConverter;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.namespace.QName;
import com.sun.codemodel.internal.JClass;
import com.sun.codemodel.internal.JClassAlreadyExistsException;
import com.sun.codemodel.internal.JCodeModel;
import com.sun.codemodel.internal.JDefinedClass;
import com.sun.codemodel.internal.JExpr;
import com.sun.codemodel.internal.JExpression;
import com.sun.codemodel.internal.JMethod;
import com.sun.codemodel.internal.JMod;
import com.sun.codemodel.internal.JPackage;
import com.sun.codemodel.internal.JType;
import com.sun.codemodel.internal.JVar;
import com.sun.codemodel.internal.JConditional;
import com.sun.tools.internal.xjc.ErrorReceiver;
import com.sun.tools.internal.xjc.model.CAdapter;
import com.sun.tools.internal.xjc.model.CBuiltinLeafInfo;
import com.sun.tools.internal.xjc.model.TypeUse;
import com.sun.tools.internal.xjc.model.TypeUseFactory;
import com.sun.tools.internal.xjc.reader.Const;
import com.sun.tools.internal.xjc.reader.Ring;
import com.sun.tools.internal.xjc.reader.TypeUtil;
import com.sun.tools.internal.xjc.reader.xmlschema.ClassSelector;
import com.sun.xml.internal.bind.v2.WellKnownNamespace;
import com.sun.xml.internal.xsom.XSSimpleType;
import org.xml.sax.Locator;
Conversion declaration.
A conversion declaration specifies how an XML type gets mapped
to a Java type.
Author:
Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
/**
* Conversion declaration.
*
* <p>
* A conversion declaration specifies how an XML type gets mapped
* to a Java type.
*
* @author
* Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
*/
public abstract class BIConversion extends AbstractDeclarationImpl {
@Deprecated
public BIConversion( Locator loc ) {
super(loc);
}
protected BIConversion() {
}
Gets the TypeUse
object that this conversion represents. The returned TypeUse
object is properly adapted.
Params: - owner – A
BIConversion
is always associated with one XSSimpleType
, but that's not always available when a BIConversion
is built. So we pass this as a parameter to this method.
/**
* Gets the {@link TypeUse} object that this conversion represents.
* <p>
* The returned {@link TypeUse} object is properly adapted.
*
* @param owner
* A {@link BIConversion} is always associated with one
* {@link XSSimpleType}, but that's not always available
* when a {@link BIConversion} is built. So we pass this
* as a parameter to this method.
*/
public abstract TypeUse getTypeUse( XSSimpleType owner );
public QName getName() { return NAME; }
Name of the conversion declaration. /** Name of the conversion declaration. */
public static final QName NAME = new QName(
Const.JAXB_NSURI, "conversion" );
Implementation that returns a statically-determined constant TypeUse
. /**
* Implementation that returns a statically-determined constant {@link TypeUse}.
*/
public static final class Static extends BIConversion {
Always non-null.
/**
* Always non-null.
*/
private final TypeUse transducer;
public Static(Locator loc, TypeUse transducer) {
super(loc);
this.transducer = transducer;
}
public TypeUse getTypeUse(XSSimpleType owner) {
return transducer;
}
}
User-specified <javaType>
customization. The parse/print methods are allowed to be null, and their default values are determined based on the owner of the token. /**
* User-specified {@code <javaType>} customization.
*
* The parse/print methods are allowed to be null,
* and their default values are determined based on the
* owner of the token.
*/
@XmlRootElement(name="javaType")
public static class User extends BIConversion {
@XmlAttribute
private String parseMethod;
@XmlAttribute
private String printMethod;
@XmlAttribute(name="name")
private String type = "java.lang.String";
/**
* If null, computed from {@link #type}.
* Sometimes this can be set instead of {@link #type}.
*/
private JType inMemoryType;
public User(Locator loc, String parseMethod, String printMethod, JType inMemoryType) {
super(loc);
this.parseMethod = parseMethod;
this.printMethod = printMethod;
this.inMemoryType = inMemoryType;
}
public User() {
}
Cache used by getTypeUse(XSSimpleType)
to improve the performance. /**
* Cache used by {@link #getTypeUse(XSSimpleType)} to improve the performance.
*/
private TypeUse typeUse;
public TypeUse getTypeUse(XSSimpleType owner) {
if(typeUse!=null)
return typeUse;
JCodeModel cm = getCodeModel();
if(inMemoryType==null)
inMemoryType = TypeUtil.getType(cm,type,Ring.get(ErrorReceiver.class),getLocation());
JDefinedClass adapter = generateAdapter(parseMethodFor(owner),printMethodFor(owner),owner);
// XmlJavaType customization always converts between string and an user-defined type.
typeUse = TypeUseFactory.adapt(CBuiltinLeafInfo.STRING,new CAdapter(adapter));
return typeUse;
}
generate the adapter class.
/**
* generate the adapter class.
*/
private JDefinedClass generateAdapter(String parseMethod, String printMethod,XSSimpleType owner) {
JDefinedClass adapter = null;
int id = 1;
while(adapter==null) {
try {
JPackage pkg = Ring.get(ClassSelector.class).getClassScope().getOwnerPackage();
adapter = pkg._class("Adapter"+id);
} catch (JClassAlreadyExistsException e) {
// try another name in search for an unique name.
// this isn't too efficient, but we expect people to usually use
// a very small number of adapters.
id++;
}
}
JClass bim = inMemoryType.boxify();
adapter._extends(getCodeModel().ref(XmlAdapter.class).narrow(String.class).narrow(bim));
JMethod unmarshal = adapter.method(JMod.PUBLIC, bim, "unmarshal");
JVar $value = unmarshal.param(String.class, "value");
JExpression inv;
if( parseMethod.equals("new") ) {
// "new" indicates that the constructor of the target type
// will do the unmarshalling.
// RESULT: new <type>()
inv = JExpr._new(bim).arg($value);
} else {
int idx = parseMethod.lastIndexOf('.');
if(idx<0) {
// parseMethod specifies the static method of the target type
// which will do the unmarshalling.
// because of an error check at the constructor,
// we can safely assume that this cast works.
inv = bim.staticInvoke(parseMethod).arg($value);
} else {
inv = JExpr.direct(parseMethod+"(value)");
}
}
unmarshal.body()._return(inv);
JMethod marshal = adapter.method(JMod.PUBLIC, String.class, "marshal");
$value = marshal.param(bim,"value");
if(printMethod.startsWith("javax.xml.bind.DatatypeConverter.")) {
// UGLY: if this conversion is the system-driven conversion,
// check for null
marshal.body()._if($value.eq(JExpr._null()))._then()._return(JExpr._null());
}
int idx = printMethod.lastIndexOf('.');
if(idx<0) {
// printMethod specifies a method in the target type
// which performs the serialization.
// RESULT: <value>.<method>()
inv = $value.invoke(printMethod);
// check value is not null ... if(value == null) return null;
JConditional jcon = marshal.body()._if($value.eq(JExpr._null()));
jcon._then()._return(JExpr._null());
} else {
// RESULT: <className>.<method>(<value>)
if(this.printMethod==null) {
// HACK HACK HACK
JType t = inMemoryType.unboxify();
inv = JExpr.direct(printMethod+"(("+findBaseConversion(owner).toLowerCase()+")("+t.fullName()+")value)");
} else
inv = JExpr.direct(printMethod+"(value)");
}
marshal.body()._return(inv);
return adapter;
}
private String printMethodFor(XSSimpleType owner) {
if(printMethod!=null) return printMethod;
if(inMemoryType.unboxify().isPrimitive()) {
String method = getConversionMethod("print",owner);
if(method!=null)
return method;
}
return "toString";
}
private String parseMethodFor(XSSimpleType owner) {
if(parseMethod!=null) return parseMethod;
if(inMemoryType.unboxify().isPrimitive()) {
String method = getConversionMethod("parse", owner);
if(method!=null) {
// this cast is necessary for conversion between primitive Java types
return '('+inMemoryType.unboxify().fullName()+')'+method;
}
}
return "new";
}
private static final String[] knownBases = new String[]{
"Float", "Double", "Byte", "Short", "Int", "Long", "Boolean"
};
private String getConversionMethod(String methodPrefix, XSSimpleType owner) {
String bc = findBaseConversion(owner);
if(bc==null) return null;
return DatatypeConverter.class.getName()+'.'+methodPrefix+bc;
}
private String findBaseConversion(XSSimpleType owner) {
// find the base simple type mapping.
for( XSSimpleType st=owner; st!=null; st = st.getSimpleBaseType() ) {
if( !WellKnownNamespace.XML_SCHEMA.equals(st.getTargetNamespace()) )
continue; // user-defined type
String name = st.getName().intern();
for( String s : knownBases )
if(name.equalsIgnoreCase(s))
return s;
}
return null;
}
public QName getName() { return NAME; }
Name of the conversion declaration. /** Name of the conversion declaration. */
public static final QName NAME = new QName(
Const.JAXB_NSURI, "javaType" );
}
@XmlRootElement(name="javaType",namespace=Const.XJC_EXTENSION_URI)
public static class UserAdapter extends BIConversion {
@XmlAttribute(name="name")
private String type = null;
@XmlAttribute
private String adapter = null;
private TypeUse typeUse;
public TypeUse getTypeUse(XSSimpleType owner) {
if(typeUse!=null)
return typeUse;
JCodeModel cm = getCodeModel();
JDefinedClass a;
try {
a = cm._class(adapter);
a.hide(); // we assume this is given by the user
a._extends(cm.ref(XmlAdapter.class).narrow(String.class).narrow(
cm.ref(type)));
} catch (JClassAlreadyExistsException e) {
a = e.getExistingClass();
}
// TODO: it's not correct to say that it adapts from String,
// but OTOH I don't think we can compute that.
typeUse = TypeUseFactory.adapt(
CBuiltinLeafInfo.STRING,
new CAdapter(a));
return typeUse;
}
}
}