/* * Copyright (c) 1997, 2014, 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.) * *

* All the getter methods (such as getBaseType or * getBindStyle) 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. * *

* 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 getChildren() { BIConversion conv = getConv(); if(conv==null) return super.getChildren(); else return Collections.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 NOT 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. * *

* 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 * if an attribute use carries a fixed value. * *

* 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 MUST * 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 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; // according to the spec we should bahave as in jaxb1. So we ignore possiblity that property could be nullable switch(opm) { // the property type can be primitive type if we are to ignore absence case PRIMITIVE: r = frf.getRequiredUnboxed(); break; case WRAPPER: // force the wrapper type r = prop.isOptionalPrimitive() ? frf.getSingle() : frf.getDefault(); break; case ISSET: r = prop.isOptionalPrimitive() ? frf.getSinglePrimitiveAccess() : frf.getDefault(); break; default: throw new Error(); } } 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: // // // // // // 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: // // // // // // // 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.) * *

* 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. * *

* This method can be think of the method that returns this implied * property customization. * *

* 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 defaultCustomizationFinder = new XSFunction() { 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; } }