/* * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the license at * https://glassfish.dev.java.net/public/CDDLv1.0.html or * glassfish/bootstrap/legal/CDDLv1.0.txt. * See the License for the specific language governing * permissions and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at glassfish/bootstrap/legal/CDDLv1.0.txt. * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * you own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Copyright 2006 Sun Microsystems, Inc. All rights reserved. */ package com.sun.tools.xjc.addon.property_listener_injector; import static com.sun.tools.xjc.outline.Aspect.IMPLEMENTATION; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import javax.xml.bind.annotation.W3CDomHandler; import javax.xml.bind.annotation.XmlList; import javax.xml.bind.annotation.XmlMixed; import javax.xml.bind.annotation.XmlNsForm; import javax.xml.bind.annotation.XmlValue; import javax.xml.namespace.QName; import com.sun.codemodel.JAnnotatable; import com.sun.codemodel.JBlock; import com.sun.codemodel.JCatchBlock; import com.sun.codemodel.JClass; import com.sun.codemodel.JCodeModel; import com.sun.codemodel.JConditional; import com.sun.codemodel.JExpr; import com.sun.codemodel.JExpression; import com.sun.codemodel.JFieldRef; import com.sun.codemodel.JFieldVar; import com.sun.codemodel.JMethod; import com.sun.codemodel.JMod; import com.sun.codemodel.JTryBlock; import com.sun.codemodel.JType; import com.sun.codemodel.JVar; import com.sun.tools.xjc.generator.annotation.spec.XmlAnyElementWriter; import com.sun.tools.xjc.generator.annotation.spec.XmlAttributeWriter; import com.sun.tools.xjc.generator.annotation.spec.XmlElementRefWriter; import com.sun.tools.xjc.generator.annotation.spec.XmlElementRefsWriter; import com.sun.tools.xjc.generator.annotation.spec.XmlElementWriter; import com.sun.tools.xjc.generator.annotation.spec.XmlElementsWriter; import com.sun.tools.xjc.generator.bean.ClassOutlineImpl; import com.sun.tools.xjc.generator.bean.MethodWriter; import com.sun.tools.xjc.model.CAttributePropertyInfo; import com.sun.tools.xjc.model.CCustomizations; import com.sun.tools.xjc.model.CElement; import com.sun.tools.xjc.model.CElementInfo; import com.sun.tools.xjc.model.CElementPropertyInfo; import com.sun.tools.xjc.model.CPluginCustomization; import com.sun.tools.xjc.model.CPropertyInfo; import com.sun.tools.xjc.model.CReferencePropertyInfo; import com.sun.tools.xjc.model.CTypeInfo; import com.sun.tools.xjc.model.CTypeRef; import com.sun.tools.xjc.model.CValuePropertyInfo; import com.sun.tools.xjc.model.nav.NClass; import com.sun.tools.xjc.outline.Aspect; import com.sun.tools.xjc.outline.ClassOutline; import com.sun.tools.xjc.outline.FieldAccessor; import com.sun.tools.xjc.outline.FieldOutline; import com.sun.tools.xjc.reader.TypeUtil; import com.sun.tools.xjc.util.DOMUtils; import com.sun.xml.bind.api.impl.NameConverter; /** * Realizes a property through one getter and one setter. * This renders: * * <pre> * T' field; * T getXXX() { ... } * void setXXX(T value) { ... } * </pre> * * <p> * Normally T'=T, but under some tricky circumstances they could be different (like T'=Integer, T=int.) * * This realization is only applicable to fields with (1,1) or (0,1) multiplicity. * * @author Jerome Dochez * @author Martin Pecka */ public class SingleField implements FieldOutline { protected final ClassOutlineImpl outline; protected final CPropertyInfo prop; protected final JType implType; protected final JType exposedType; protected final JCodeModel codeModel; private enum Kind { BOUND, VETOABLE } /** * Field declaration of the actual list object that we use * to store data. */ private JFieldVar field; public SingleField(ClassOutlineImpl context, CPropertyInfo prop) { this(context, prop, false); } /** * * @param forcePrimitiveAccess * forces the setter/getter to expose the primitive type. * it's a pointless customization, but it's nevertheless in the spec. */ public SingleField(ClassOutlineImpl context, CPropertyInfo prop, boolean forcePrimitiveAccess) { outline = context; this.prop = prop; Kind boundType = null; implType = getType(IMPLEMENTATION); exposedType = getType(Aspect.EXPOSED); codeModel = outline.parent().getCodeModel(); String interfaceName = null; CCustomizations customizations = prop.getCustomizations(); CPluginCustomization pluginCust = customizations.find(Const.NS, "listener"); if (pluginCust != null) { interfaceName = DOMUtils.getElementText(pluginCust.element); if (interfaceName.equals("java.beans.PropertyChangeListener")) { boundType = Kind.BOUND; } else if (interfaceName.equals("java.beans.VetoableChangeListener")) { boundType = Kind.VETOABLE; } else { boundType = null; } pluginCust.markAsAcknowledged(); } else { customizations = context.target.getCustomizations(); pluginCust = customizations.find(Const.NS, "listener"); if (pluginCust != null) { interfaceName = DOMUtils.getElementText(pluginCust.element); if (interfaceName.equals("java.beans.PropertyChangeListener")) { boundType = Kind.BOUND; } else if (interfaceName.equals("java.beans.VetoableChangeListener")) { boundType = Kind.VETOABLE; } else { boundType = null; } pluginCust.markAsAcknowledged(); } else { customizations = context.parent().getModel().getCustomizations(); pluginCust = customizations.find(Const.NS, "listener"); if (pluginCust != null) { interfaceName = DOMUtils.getElementText(pluginCust.element); if (interfaceName.equals("java.beans.PropertyChangeListener")) { boundType = Kind.BOUND; } else if (interfaceName.equals("java.beans.VetoableChangeListener")) { boundType = Kind.VETOABLE; } else { boundType = null; } pluginCust.markAsAcknowledged(); } } } assert !exposedType.isPrimitive() && !implType.isPrimitive(); // create the field field = outline.implClass.field(JMod.PROTECTED, getFieldType(), prop.getName(false)); annotate(field); JFieldVar propertyConstant = null; if (boundType != null) { // the constant for the property String propertyConstantName = prop.getName(false).replaceAll("([A-Z])", "_$1").toUpperCase() + "_PROPERTY"; propertyConstant = outline.implClass.field(JMod.PUBLIC | JMod.STATIC | JMod.FINAL, String.class, propertyConstantName, JExpr.lit(prop.getName(false) + ":" + outline.implClass.fullName())); propertyConstant.javadoc().clear(); propertyConstant.javadoc().add("Property " + prop.getName(false)); } MethodWriter writer = context.createMethodWriter(); NameConverter nc = context.parent().getModel().getNameConverter(); // [RESULT] // Type getXXX() { // #ifdef default value // if(value==null) // return defaultValue; // #endif // return value; // } JExpression defaultValue = null; if (prop.defaultValue != null) defaultValue = prop.defaultValue.compute(outline.parent()); // if Type is a wrapper and we have a default value, // we can use the primitive type. JType getterType; if (defaultValue != null || forcePrimitiveAccess) getterType = exposedType.unboxify(); else getterType = exposedType; JMethod $get = writer.declareMethod(getterType, getGetterMethod()); String javadoc = prop.javadoc; if (javadoc.length() == 0) javadoc = Messages.DEFAULT_GETTER_JAVADOC.format(nc.toVariableName(prop.getName(true))); writer.javadoc().append(javadoc); if (defaultValue == null) { $get.body()._return(ref()); } else { JConditional cond = $get.body()._if(ref().eq(JExpr._null())); cond._then()._return(defaultValue); cond._else()._return(ref()); } List<Object> possibleTypes = listPossibleTypes(prop); writer.javadoc().addReturn().append("possible object is\n").append(possibleTypes); // [RESULT] // void setXXX(Type newVal) { // this.value = newVal; // } JMethod $set = writer.declareMethod(codeModel.VOID, "set" + prop.getName(true)); JType setterType = exposedType; if (forcePrimitiveAccess) setterType = setterType.unboxify(); JVar $value = writer.addParameter(setterType, "value"); JBlock body = $set.body(); JVar oldVal = null; if (boundType != null) { oldVal = body.decl(implType, "old", JExpr.refthis(ref().name())); } body.assign(JExpr._this().ref(ref()), castToImplType($value)); if (boundType != null) { assert oldVal != null; assert propertyConstant != null; outline.ref.javadoc().add(outline.ref.javadoc().size(), "<p>Provided property: " + prop.getName(false) + "\n"); JExpression cond; if (implType.unboxify().isPrimitive()) { cond = oldVal.ne($value); } else if (implType.isArray()) { cond = codeModel.ref(Arrays.class).staticInvoke("equals").arg(oldVal).arg($value).not(); } else { cond = oldVal.ne($value).cand(oldVal.eq(JExpr._null()).cor($value.eq(JExpr._null()))) .cor(oldVal.ne(JExpr._null()).cand(oldVal.invoke("equals").arg($value).not())); } JBlock ifBlock = body._if(cond)._then(); if (boundType == Kind.VETOABLE) { JTryBlock tryBlock = ifBlock._try(); JClass vetoableClass = (JClass) codeModel._ref(java.beans.PropertyVetoException.class); tryBlock.body().add( JExpr.ref("support").invoke("fireVetoableChange") .arg(outline.implClass.staticRef(propertyConstant)).arg(oldVal).arg($value)); // tryBlock.body().directStatement("support.fireVetoableChange(\"" + prop.getName(false)+ "\"," + // ref().name() + ", value);"); JCatchBlock catchBlock = tryBlock._catch(vetoableClass); catchBlock.body().directStatement("return;"); } else { ifBlock.add(JExpr.ref("support").invoke("firePropertyChange") .arg(outline.implClass.staticRef(propertyConstant)).arg(oldVal).arg($value)); } } javadoc = prop.javadoc; if (javadoc.length() == 0) javadoc = Messages.DEFAULT_SETTER_JAVADOC.format(nc.toVariableName(prop.getName(true))); writer.javadoc().append(javadoc); writer.javadoc().addParam($value).append("allowed object is\n").append(possibleTypes); } protected JFieldVar ref() { return field; } public final JType getFieldType() { return implType; } public FieldAccessor create(JExpression targetObject) { return new Accessor(targetObject); } /** * Useful base class for implementing {@link FieldAccessor}. */ protected class Accessor implements FieldAccessor { /** * Evaluates to the target object this accessor should access. */ protected final JExpression $target; protected Accessor(JExpression $target) { this.$target = $target; this.$ref = $target.ref(SingleField.this.ref()); } /** * Reference to the field bound by the target object. */ protected final JFieldRef $ref; public final FieldOutline owner() { return SingleField.this; } public final CPropertyInfo getPropertyInfo() { return prop; } public void unsetValues(JBlock body) { body.assign($ref, JExpr._null()); } public JExpression hasSetValue() { return $ref.ne(JExpr._null()); } public final void toRawValue(JBlock block, JVar $var) { block.assign($var, $target.invoke(getGetterMethod())); } public final void fromRawValue(JBlock block, String uniqueName, JExpression $var) { block.invoke($target, ("set" + prop.getName(true))).arg($var); } } /** * Gets the name of the getter method. * * <p> * This encapsulation is necessary because sometimes we use {@code isXXXX} as the method name. */ protected String getGetterMethod() { return (getFieldType().boxify().getPrimitiveType() == codeModel.BOOLEAN ? "is" : "get") + prop.getName(true); } public CPropertyInfo getPropertyInfo() { return prop; } public final JType getRawType() { return exposedType; } /** * Compute the type of a {@link CPropertyInfo} * * @param aspect */ protected JType getType(final Aspect aspect) { if (prop.getAdapter() != null) return prop.getAdapter().customType.toType(outline.parent(), aspect); final class TypeList extends ArrayList<JType> { /** */ private static final long serialVersionUID = 8817002762839577541L; void add(CTypeInfo t) { add(t.getType().toType(outline.parent(), aspect)); if (t instanceof CElementInfo) { // UGLY. element substitution is implemented in a way that // the derived elements are not assignable to base elements. // so when we compute the signature, we have to take derived types // into account add(((CElementInfo) t).getSubstitutionMembers()); } } void add(Collection<? extends CTypeInfo> col) { for (CTypeInfo typeInfo : col) add(typeInfo); } } TypeList r = new TypeList(); r.add(prop.ref()); JType t; if (prop.baseType != null) t = prop.baseType; else t = TypeUtil.getCommonBaseType(codeModel, r); // if item type is unboxable, convert t=Integer -> t=int // the in-memory data structure can't have primitives directly, // but this guarantees that items cannot legal hold null, // which helps us improve the boundary signature between our // data structure and user code if (prop.isUnboxable()) t = t.unboxify(); return t; } /** * Returns contents to be added to javadoc. */ protected final List<Object> listPossibleTypes(CPropertyInfo prop) { List<Object> r = new ArrayList<Object>(); for (CTypeInfo tt : prop.ref()) { JType t = tt.getType().toType(outline.parent(), Aspect.EXPOSED); if (t.isPrimitive() || t.isArray()) r.add(t.fullName()); else { r.add(t); r.add("\n"); } } return r; } /** * Case from {@link #exposedType} to {@link #implType} if necessary. */ protected final JExpression castToImplType(JExpression exp) { if (implType == exposedType) return exp; else return JExpr.cast(implType, exp); } public ClassOutline parent() { return outline; } /** * Annotate the field according to the recipes given as {@link CPropertyInfo}. */ protected void annotate(JAnnotatable field) { assert (field != null); /* * TODO: consider moving this logic to somewhere else * so that it can be better shared, for how a field gets * annotated doesn't really depend on how we generate accessors. * * so perhaps we should separate those two. */ // TODO: consider a visitor if (prop instanceof CAttributePropertyInfo) { annotateAttribute(field); } else if (prop instanceof CElementPropertyInfo) { annotateElement(field); } else if (prop instanceof CValuePropertyInfo) { field.annotate(XmlValue.class); } else if (prop instanceof CReferencePropertyInfo) { annotateReference(field); } outline.parent().generateAdapterIfNecessary(prop, field); } private void annotateReference(JAnnotatable field) { CReferencePropertyInfo rp = (CReferencePropertyInfo) prop; // this is just a quick hack to get the basic test working Collection<CElement> elements = rp.getElements(); XmlElementRefWriter refw; if (elements.size() == 1) { refw = field.annotate2(XmlElementRefWriter.class); CElement e = elements.iterator().next(); refw.name(e.getElementName().getLocalPart()).namespace(e.getElementName().getNamespaceURI()) .type(e.getType().toType(outline.parent(), IMPLEMENTATION)); } else if (elements.size() > 1) { XmlElementRefsWriter refsw = field.annotate2(XmlElementRefsWriter.class); for (CElement e : elements) { refw = refsw.value(); refw.name(e.getElementName().getLocalPart()).namespace(e.getElementName().getNamespaceURI()) .type(e.getType().toType(outline.parent(), IMPLEMENTATION)); } } if (rp.isMixed()) field.annotate(XmlMixed.class); NClass dh = rp.getDOMHandler(); if (dh != null) { XmlAnyElementWriter xaew = field.annotate2(XmlAnyElementWriter.class); xaew.lax(rp.getWildcard().allowTypedObject); final JClass value = dh.toType(outline.parent(), IMPLEMENTATION); if (!value.equals(codeModel.ref(W3CDomHandler.class))) { xaew.value(value); } } } /** * Annotate the element property 'field' */ @SuppressWarnings("deprecation") private void annotateElement(JAnnotatable field) { CElementPropertyInfo ep = (CElementPropertyInfo) prop; List<CTypeRef> types = ep.getTypes(); if (ep.isValueList()) { field.annotate(XmlList.class); } assert ep.getXmlName() == null; // if( eName!=null ) { // wrapper // XmlElementWrapperWriter xcw = field.annotate2(XmlElementWrapperWriter.class); // xcw.name(eName.getLocalPart()) // .namespace(eName.getNamespaceURI()); // } if (types.size() == 1) { CTypeRef t = types.get(0); writeXmlElementAnnotation(field, t, resolve(t, IMPLEMENTATION), false); } else { for (CTypeRef t : types) { // generate @XmlElements writeXmlElementAnnotation(field, t, resolve(t, IMPLEMENTATION), true); } xesw = null; } } /** * Generate the simplest XmlElement annotation possible taking all semantic optimizations * into account. This method is essentially equivalent to: * * xew.name(ctype.getTagName().getLocalPart()) * .namespace(ctype.getTagName().getNamespaceURI()) * .type(jtype) * .defaultValue(ctype.getDefaultValue()); * * @param field * @param ctype * @param jtype * @param checkWrapper true if the method might need to generate XmlElements */ private void writeXmlElementAnnotation(JAnnotatable field, CTypeRef ctype, JType jType, boolean checkWrapper) { JType jtype = jType; // lazily create - we don't know if we need to generate anything yet XmlElementWriter xew = null; // these values are used to determine how to optimize the generated annotation XmlNsForm formDefault = parent()._package().getElementFormDefault(); String mostUsedURI = parent()._package().getMostUsedNamespaceURI(); String propName = prop.getName(false); // generate the name property? String generatedName = ctype.getTagName().getLocalPart(); if (!generatedName.equals(propName)) { if (xew == null) xew = getXew(checkWrapper, field); xew.name(generatedName); } // generate the namespace property? String generatedNS = ctype.getTagName().getNamespaceURI(); if (((formDefault == XmlNsForm.QUALIFIED) && !generatedNS.equals(mostUsedURI)) || ((formDefault == XmlNsForm.UNQUALIFIED) && !generatedNS.equals(""))) { if (xew == null) xew = getXew(checkWrapper, field); xew.namespace(generatedNS); } // generate the required() property? CElementPropertyInfo ep = (CElementPropertyInfo) prop; if (ep.isRequired() && exposedType.isReference()) { if (xew == null) xew = getXew(checkWrapper, field); xew.required(true); } // generate the type property? // I'm not too sure if this is the right place to handle this, but // if the schema definition is requiring this element, we should point to a primitive type, // not wrapper type (to correctly carry forward the required semantics.) // if it's a collection, we can't use a primitive, however. if (ep.isRequired() && !prop.isCollection()) jtype = jtype.unboxify(); // when generating code for 1.4, the runtime can't infer that ArrayList<Foo> derives // from Collection<Foo> (because List isn't parameterized), so always expclitly // generate @XmlElement(type=...) if (!jtype.equals(exposedType) || (parent().parent().getModel().options.runtime14 && prop.isCollection())) { if (xew == null) xew = getXew(checkWrapper, field); xew.type(jtype); } // generate defaultValue property? final String defaultValue = ctype.getDefaultValue(); if (defaultValue != null) { if (xew == null) xew = getXew(checkWrapper, field); xew.defaultValue(defaultValue); } // generate the nillable property? if (ctype.isNillable()) { if (xew == null) xew = getXew(checkWrapper, field); xew.nillable(true); } } // ugly hack to lazily create private XmlElementsWriter xesw = null; private XmlElementWriter getXew(boolean checkWrapper, JAnnotatable field) { XmlElementWriter xew; if (checkWrapper) { if (xesw == null) { xesw = field.annotate2(XmlElementsWriter.class); } xew = xesw.value(); } else { xew = field.annotate2(XmlElementWriter.class); } return xew; } /** * Annotate the attribute property 'field' */ private void annotateAttribute(JAnnotatable field) { CAttributePropertyInfo ap = (CAttributePropertyInfo) prop; QName attName = ap.getXmlName(); // [RESULT] // @XmlAttribute(name="foo", required=true, namespace="bar://baz") XmlAttributeWriter xaw = field.annotate2(XmlAttributeWriter.class); final String generatedName = attName.getLocalPart(); final String generatedNS = attName.getNamespaceURI(); // generate name property? if (!generatedName.equals(ap.getName(false))) { xaw.name(generatedName); } // generate namespace property? if (!generatedNS.equals("")) { // assume attributeFormDefault == unqualified xaw.namespace(generatedNS); } // generate required property? if (ap.isRequired()) { xaw.required(true); } } /** * return the Java type for the given type reference in the model. */ private JType resolve(CTypeRef typeRef, Aspect a) { return outline.parent().resolve(typeRef, a); } }