blob: e5caba63acc6e3c141c217f0f715706563db4d41 [file] [log] [blame]
/*
* Copyright (c) 1997, 2011, 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 java.util.Collection;
import java.util.Collections;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.namespace.QName;
import com.sun.codemodel.internal.JJavaName;
import com.sun.codemodel.internal.JType;
import com.sun.tools.internal.xjc.ErrorReceiver;
import com.sun.tools.internal.xjc.generator.bean.field.FieldRenderer;
import com.sun.tools.internal.xjc.generator.bean.field.FieldRendererFactory;
import com.sun.tools.internal.xjc.generator.bean.field.IsSetFieldRenderer;
import com.sun.tools.internal.xjc.model.CAttributePropertyInfo;
import com.sun.tools.internal.xjc.model.CCustomizations;
import com.sun.tools.internal.xjc.model.CElementPropertyInfo;
import com.sun.tools.internal.xjc.model.CPropertyInfo;
import com.sun.tools.internal.xjc.model.CReferencePropertyInfo;
import com.sun.tools.internal.xjc.model.CValuePropertyInfo;
import com.sun.tools.internal.xjc.model.TypeUse;
import com.sun.tools.internal.xjc.reader.Const;
import com.sun.tools.internal.xjc.reader.RawTypeSet;
import com.sun.tools.internal.xjc.reader.Ring;
import com.sun.tools.internal.xjc.reader.TypeUtil;
import com.sun.tools.internal.xjc.reader.xmlschema.BGMBuilder;
import com.sun.xml.internal.bind.api.impl.NameConverter;
import com.sun.xml.internal.xsom.XSAnnotation;
import com.sun.xml.internal.xsom.XSAttGroupDecl;
import com.sun.xml.internal.xsom.XSAttributeDecl;
import com.sun.xml.internal.xsom.XSAttributeUse;
import com.sun.xml.internal.xsom.XSComplexType;
import com.sun.xml.internal.xsom.XSComponent;
import com.sun.xml.internal.xsom.XSContentType;
import com.sun.xml.internal.xsom.XSElementDecl;
import com.sun.xml.internal.xsom.XSFacet;
import com.sun.xml.internal.xsom.XSIdentityConstraint;
import com.sun.xml.internal.xsom.XSModelGroup;
import com.sun.xml.internal.xsom.XSModelGroupDecl;
import com.sun.xml.internal.xsom.XSNotation;
import com.sun.xml.internal.xsom.XSParticle;
import com.sun.xml.internal.xsom.XSSchema;
import com.sun.xml.internal.xsom.XSSimpleType;
import com.sun.xml.internal.xsom.XSWildcard;
import com.sun.xml.internal.xsom.XSXPath;
import com.sun.xml.internal.xsom.util.XSFinder;
import com.sun.xml.internal.xsom.visitor.XSFunction;
import org.xml.sax.Locator;
/**
* Property customization.
*
* This customization turns an arbitrary schema component
* into a Java property (some restrictions apply.)
*
* <p>
* All the getter methods (such as <code>getBaseType</code> or
* <code>getBindStyle</code>) honors the delegation chain of
* property customization specified in the spec. Namely,
* if two property customizations are attached to an attribute
* use and an attribute decl, then anything unspecified in the
* attribute use defaults to attribute decl.
*
* <p>
* Property customizations are acknowledged
* (1) when they are actually used, and
* (2) when they are given at the component, which is mapped to a class.
* (so-called "point of declaration" customization)
*
* @author
* Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
*/
@XmlRootElement(name="property")
public final class BIProperty extends AbstractDeclarationImpl {
// can be null
@XmlAttribute
private String name = null;
// can be null
@XmlElement
private String javadoc = null;
// can be null
@XmlElement
private BaseTypeBean baseType = null;
// TODO: report 'unsupported' error if this is true
@XmlAttribute
private boolean generateFailFastSetterMethod = false;
public BIProperty(Locator loc, String _propName, String _javadoc,
BaseTypeBean _baseType, CollectionTypeAttribute collectionType, Boolean isConst,
OptionalPropertyMode optionalProperty, Boolean genElemProp) {
super(loc);
this.name = _propName;
this.javadoc = _javadoc;
this.baseType = _baseType;
this.collectionType = collectionType;
this.isConstantProperty = isConst;
this.optionalProperty = optionalProperty;
this.generateElementProperty = genElemProp;
}
protected BIProperty() {}
@Override
public Collection<BIDeclaration> getChildren() {
BIConversion conv = getConv();
if(conv==null)
return super.getChildren();
else
return Collections.<BIDeclaration>singleton(conv);
}
public void setParent( BindInfo parent ) {
super.setParent(parent);
if(baseType!=null && baseType.conv!=null)
baseType.conv.setParent(parent);
}
/**
* Returns the customized property name.
*
* This method honors the "enableJavaNamingConvention" customization
* and formats the property name accordingly if necessary.
*
* Thus the caller should <em>NOT</em> apply the XML-to-Java name
* conversion algorithm to the value returned from this method.
*
* @param forConstant
* If the property name is intended for a constant property name,
* set to true. This will change the result
*
* @return
* This method can return null if the customization doesn't
* specify the name.
*/
public String getPropertyName( boolean forConstant ) {
if(name!=null) {
BIGlobalBinding gb = getBuilder().getGlobalBinding();
NameConverter nc = getBuilder().model.getNameConverter();
if( gb.isJavaNamingConventionEnabled() && !forConstant )
// apply XML->Java conversion
return nc.toPropertyName(name);
else
return name; // ... or don't change the value
}
BIProperty next = getDefault();
if(next!=null) return next.getPropertyName(forConstant);
else return null;
}
/**
* Gets the associated javadoc.
*
* @return
* null if none is specfieid.
*/
public String getJavadoc() {
return javadoc;
}
// can be null
public JType getBaseType() {
if(baseType!=null && baseType.name!=null) {
return TypeUtil.getType(getCodeModel(),
baseType.name,
Ring.get(ErrorReceiver.class),getLocation());
}
BIProperty next = getDefault();
if(next!=null) return next.getBaseType();
else return null;
}
// can be null
@XmlAttribute
private CollectionTypeAttribute collectionType = null;
/**
* Gets the realization of this field.
* @return Always return non-null.
*/
CollectionTypeAttribute getCollectionType() {
if(collectionType!=null) return collectionType;
return getDefault().getCollectionType();
}
@XmlAttribute
private OptionalPropertyMode optionalProperty = null;
// virtual property for @generateIsSetMethod
@XmlAttribute
void setGenerateIsSetMethod(boolean b) {
optionalProperty = b ? OptionalPropertyMode.ISSET : OptionalPropertyMode.WRAPPER;
}
public OptionalPropertyMode getOptionalPropertyMode() {
if(optionalProperty!=null) return optionalProperty;
return getDefault().getOptionalPropertyMode();
}
// null if delegated
@XmlAttribute
private Boolean generateElementProperty = null;
/**
* If true, the property will automatically be a reference property.
* (Talk about confusing names!)
*/
private Boolean generateElementProperty() {
if(generateElementProperty!=null) return generateElementProperty;
BIProperty next = getDefault();
if(next!=null) return next.generateElementProperty();
return null;
}
// true, false, or null (which means the value should be inherited.)
@XmlAttribute(name="fixedAttributeAsConstantProperty")
private Boolean isConstantProperty;
/**
* Gets the inherited value of the "fixedAttrToConstantProperty" customization.
*
* <p>
* Note that returning true from this method doesn't necessarily mean
* that a property needs to be mapped to a constant property.
* It just means that it's mapped to a constant property
* <b>if an attribute use carries a fixed value.</b>
*
* <p>
* I don't like this semantics but that's what the spec implies.
*/
public boolean isConstantProperty() {
if(isConstantProperty!=null) return isConstantProperty;
BIProperty next = getDefault();
if(next!=null) return next.isConstantProperty();
// globalBinding always has true or false in this property,
// so this can't happen
throw new AssertionError();
}
public CValuePropertyInfo createValueProperty(String defaultName,boolean forConstant,
XSComponent source,TypeUse tu, QName typeName) {
markAsAcknowledged();
constantPropertyErrorCheck();
String name = getPropertyName(forConstant);
if(name==null) {
name = defaultName;
if(tu.isCollection() && getBuilder().getGlobalBinding().isSimpleMode())
name = JJavaName.getPluralForm(name);
}
CValuePropertyInfo prop = wrapUp(new CValuePropertyInfo(name, source, getCustomizations(source), source.getLocator(), tu, typeName), source);
BIInlineBinaryData.handle(source, prop);
return prop;
}
public CAttributePropertyInfo createAttributeProperty( XSAttributeUse use, TypeUse tu ) {
boolean forConstant =
getCustomization(use).isConstantProperty() &&
use.getFixedValue()!=null;
String name = getPropertyName(forConstant);
if(name==null) {
NameConverter conv = getBuilder().getNameConverter();
if(forConstant)
name = conv.toConstantName(use.getDecl().getName());
else
name = conv.toPropertyName(use.getDecl().getName());
if(tu.isCollection() && getBuilder().getGlobalBinding().isSimpleMode())
name = JJavaName.getPluralForm(name);
}
markAsAcknowledged();
constantPropertyErrorCheck();
return wrapUp(new CAttributePropertyInfo(name,use,getCustomizations(use),use.getLocator(),
BGMBuilder.getName(use.getDecl()), tu,
BGMBuilder.getName(use.getDecl().getType()), use.isRequired() ),use);
}
/**
*
*
* @param defaultName
* If the name is not customized, this name will be used
* as the default. Note that the name conversion <b>MUST</b>
* be applied before this method is called if necessary.
* @param source
* Source schema component from which a field is built.
*/
public CElementPropertyInfo createElementProperty(String defaultName, boolean forConstant, XSParticle source,
RawTypeSet types) {
if(!types.refs.isEmpty())
// if this property is empty, don't acknowleedge the customization
// this allows pointless property customization to be reported as an error
markAsAcknowledged();
constantPropertyErrorCheck();
String name = getPropertyName(forConstant);
if(name==null)
name = defaultName;
CElementPropertyInfo prop = wrapUp(
new CElementPropertyInfo(
name, types.getCollectionMode(),
types.id(),
types.getExpectedMimeType(),
source, getCustomizations(source),
source.getLocator(), types.isRequired()),
source);
types.addTo(prop);
BIInlineBinaryData.handle(source.getTerm(), prop);
return prop;
}
public CReferencePropertyInfo createDummyExtendedMixedReferenceProperty(
String defaultName, XSComponent source, RawTypeSet types) {
return createReferenceProperty(
defaultName,
false,
source,
types,
true,
true,
false,
true);
}
public CReferencePropertyInfo createContentExtendedMixedReferenceProperty(
String defaultName, XSComponent source, RawTypeSet types) {
return createReferenceProperty(
defaultName,
false,
source,
types,
true,
false,
true,
true);
}
public CReferencePropertyInfo createReferenceProperty(
String defaultName, boolean forConstant, XSComponent source,
RawTypeSet types, boolean isMixed, boolean dummy, boolean content, boolean isMixedExtended) {
if (types == null) { // this is a special case where we need to generate content because potential subtypes would need to be able to override what's store inside
content = true;
} else {
if(!types.refs.isEmpty())
// if this property is empty, don't acknowleedge the customization
// this allows pointless property customization to be reported as an error
markAsAcknowledged();
}
constantPropertyErrorCheck();
String name = getPropertyName(forConstant);
if(name==null)
name = defaultName;
CReferencePropertyInfo prop = wrapUp(
new CReferencePropertyInfo(
name,
(types == null) ? true : types.getCollectionMode().isRepeated()||isMixed,
(types == null) ? false : types.isRequired(),
isMixed,
source,
getCustomizations(source), source.getLocator(), dummy, content, isMixedExtended),
source);
if (types != null) {
types.addTo(prop);
}
BIInlineBinaryData.handle(source, prop);
return prop;
}
public CPropertyInfo createElementOrReferenceProperty(
String defaultName, boolean forConstant, XSParticle source,
RawTypeSet types) {
boolean generateRef;
switch(types.canBeTypeRefs) {
case CAN_BE_TYPEREF:
case SHOULD_BE_TYPEREF:
// it's up to the use
Boolean b = generateElementProperty();
if(b==null) // follow XJC recommendation
generateRef = types.canBeTypeRefs== RawTypeSet.Mode.CAN_BE_TYPEREF;
else // use the value user gave us
generateRef = b;
break;
case MUST_BE_REFERENCE:
generateRef = true;
break;
default:
throw new AssertionError();
}
if(generateRef) {
return createReferenceProperty(defaultName,forConstant,source,types, false, false, false, false);
} else {
return createElementProperty(defaultName,forConstant,source,types);
}
}
/**
* Common finalization of {@link CPropertyInfo} for the create***Property methods.
*/
private <T extends CPropertyInfo> T wrapUp(T prop, XSComponent source) {
prop.javadoc = concat(javadoc,
getBuilder().getBindInfo(source).getDocumentation());
if(prop.javadoc==null)
prop.javadoc="";
// decide the realization.
FieldRenderer r;
OptionalPropertyMode opm = getOptionalPropertyMode();
if(prop.isCollection()) {
CollectionTypeAttribute ct = getCollectionType();
r = ct.get(getBuilder().model);
} else {
FieldRendererFactory frf = getBuilder().fieldRendererFactory;
if(prop.isOptionalPrimitive()) {
// the property type can be primitive type if we are to ignore absence
switch(opm) {
case PRIMITIVE:
r = frf.getRequiredUnboxed();
break;
case WRAPPER:
// force the wrapper type
r = frf.getSingle();
break;
case ISSET:
r = frf.getSinglePrimitiveAccess();
break;
default:
throw new Error();
}
} else {
r = frf.getDefault();
}
}
if(opm==OptionalPropertyMode.ISSET) {
// only isSet is allowed on a collection. these 3 modes aren't really symmetric.
// if the property is a primitive type, we need an explicit unset because
// we can't overload the meaning of set(null).
// if it's a collection, we need to be able to unset it so that we can distinguish
// null list and empty list.
r = new IsSetFieldRenderer( r, prop.isOptionalPrimitive()||prop.isCollection(), true );
}
prop.realization = r;
JType bt = getBaseType();
if(bt!=null)
prop.baseType = bt;
return prop;
}
private CCustomizations getCustomizations( XSComponent src ) {
return getBuilder().getBindInfo(src).toCustomizationList();
}
private CCustomizations getCustomizations( XSComponent... src ) {
CCustomizations c = null;
for (XSComponent s : src) {
CCustomizations r = getCustomizations(s);
if(c==null) c = r;
else c = CCustomizations.merge(c,r);
}
return c;
}
private CCustomizations getCustomizations( XSAttributeUse src ) {
// customizations for an attribute use should include those defined in the local attribute.
// this is so that the schema like:
//
// <xs:attribute name="foo" type="xs:int">
// <xs:annotation><xs:appinfo>
// <hyperjaxb:... />
//
// would be picked up
if(src.getDecl().isLocal())
return getCustomizations(src,src.getDecl());
else
return getCustomizations((XSComponent)src);
}
private CCustomizations getCustomizations( XSParticle src ) {
// customizations for a particle should include those defined in the term unless it's global
// this is so that the schema like:
//
// <xs:sequence>
// <xs:element name="foo" type="xs:int">
// <xs:annotation><xs:appinfo>
// <hyperjaxb:... />
//
// would be picked up
if(src.getTerm().isElementDecl()) {
XSElementDecl xed = src.getTerm().asElementDecl();
if(xed.isGlobal())
return getCustomizations((XSComponent)src);
}
return getCustomizations(src,src.getTerm());
}
public void markAsAcknowledged() {
if( isAcknowledged() ) return;
// mark the parent as well.
super.markAsAcknowledged();
BIProperty def = getDefault();
if(def!=null) def.markAsAcknowledged();
}
private void constantPropertyErrorCheck() {
if( isConstantProperty!=null && getOwner()!=null ) {
// run additional check on the isCOnstantProperty value.
// this value is not allowed if the schema component doesn't have
// a fixed value constraint.
//
// the setParent method associates a customization with the rest of
// XSOM object graph, so this is the earliest possible moment where
// we can test this.
if( !hasFixedValue.find(getOwner()) ) {
Ring.get(ErrorReceiver.class).error(
getLocation(),
Messages.ERR_ILLEGAL_FIXEDATTR.format()
);
// set this value to null to avoid the same error to be reported more than once.
isConstantProperty = null;
}
}
}
/**
* Function object that returns true if a component has
* a fixed value constraint.
*/
private final XSFinder hasFixedValue = new XSFinder() {
public Boolean attributeDecl(XSAttributeDecl decl) {
return decl.getFixedValue()!=null;
}
public Boolean attributeUse(XSAttributeUse use) {
return use.getFixedValue()!=null;
}
public Boolean schema(XSSchema s) {
// we allow globalBindings to have isConstantProperty==true,
// so this method returns true to allow this.
return true;
}
};
/**
* Finds a BIProperty which this object should delegate to.
*
* @return
* always return non-null for normal BIProperties.
* If this object is contained in the BIGlobalBinding, then
* this method returns null to indicate that there's no more default.
*/
protected BIProperty getDefault() {
if(getOwner()==null) return null;
BIProperty next = getDefault(getBuilder(),getOwner());
if(next==this) return null; // global.
else return next;
}
private static BIProperty getDefault( BGMBuilder builder, XSComponent c ) {
while(c!=null) {
c = c.apply(defaultCustomizationFinder);
if(c!=null) {
BIProperty prop = builder.getBindInfo(c).get(BIProperty.class);
if(prop!=null) return prop;
}
}
// default to the global one
return builder.getGlobalBinding().getDefaultProperty();
}
/**
* Finds a property customization that describes how the given
* component should be mapped to a property (if it's mapped to
* a property at all.)
*
* <p>
* Consider an attribute use that does NOT carry a property
* customization. This schema component is nonetheless considered
* to carry a (sort of) implicit property customization, whose values
* are defaulted.
*
* <p>
* This method can be think of the method that returns this implied
* property customization.
*
* <p>
* Note that this doesn't mean the given component needs to be
* mapped to a property. But if it does map to a property, it needs
* to follow this customization.
*
* I think this semantics is next to non-sense but I couldn't think
* of any other way to follow the spec.
*
* @param c
* A customization effective on this component will be returned.
* Can be null just to get the global customization.
* @return
* Always return non-null valid object.
*/
public static BIProperty getCustomization( XSComponent c ) {
BGMBuilder builder = Ring.get(BGMBuilder.class);
// look for a customization on this component
if( c!=null ) {
BIProperty prop = builder.getBindInfo(c).get(BIProperty.class);
if(prop!=null) return prop;
}
// if no such thing exists, defeault.
return getDefault(builder,c);
}
private final static XSFunction<XSComponent> defaultCustomizationFinder = new XSFunction<XSComponent>() {
public XSComponent attributeUse(XSAttributeUse use) {
return use.getDecl(); // inherit from the declaration
}
public XSComponent particle(XSParticle particle) {
return particle.getTerm(); // inherit from the term
}
public XSComponent schema(XSSchema schema) {
// no more delegation
return null;
}
// delegates to the context schema object
public XSComponent attributeDecl(XSAttributeDecl decl) { return decl.getOwnerSchema(); }
public XSComponent wildcard(XSWildcard wc) { return wc.getOwnerSchema(); }
public XSComponent modelGroupDecl(XSModelGroupDecl decl) { return decl.getOwnerSchema(); }
public XSComponent modelGroup(XSModelGroup group) { return group.getOwnerSchema(); }
public XSComponent elementDecl(XSElementDecl decl) { return decl.getOwnerSchema(); }
public XSComponent complexType(XSComplexType type) { return type.getOwnerSchema(); }
public XSComponent simpleType(XSSimpleType st) { return st.getOwnerSchema(); }
// property customizations are not allowed on these components.
public XSComponent attGroupDecl(XSAttGroupDecl decl) { throw new IllegalStateException(); }
public XSComponent empty(XSContentType empty) { throw new IllegalStateException(); }
public XSComponent annotation(XSAnnotation xsAnnotation) { throw new IllegalStateException(); }
public XSComponent facet(XSFacet xsFacet) { throw new IllegalStateException(); }
public XSComponent notation(XSNotation xsNotation) { throw new IllegalStateException(); }
public XSComponent identityConstraint(XSIdentityConstraint x) { throw new IllegalStateException(); }
public XSComponent xpath(XSXPath xsxPath) { throw new IllegalStateException(); }
};
private static String concat( String s1, String s2 ) {
if(s1==null) return s2;
if(s2==null) return s1;
return s1+"\n\n"+s2;
}
public QName getName() { return NAME; }
/** Name of this declaration. */
public static final QName NAME = new QName(
Const.JAXB_NSURI, "property" );
public BIConversion getConv() {
if(baseType!=null)
return baseType.conv;
else
return null;
}
private static final class BaseTypeBean {
/**
* If there's a nested javaType customization, this field
* will keep that customization. Otherwise null.
*
* This customization, if present, is used to customize
* the simple type mapping at the point of reference.
*/
@XmlElementRef
BIConversion conv;
/**
* Java type name.
*/
@XmlAttribute
String name;
}
}