1 /*
   2  * Copyright (c) 1997, 2012, 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 
  26 package com.sun.tools.internal.xjc.reader.xmlschema.bindinfo;
  27 
  28 import java.util.Collections;
  29 import java.util.HashMap;
  30 import java.util.Map;
  31 import java.util.Set;
  32 
  33 import javax.xml.bind.annotation.XmlAttribute;
  34 import javax.xml.bind.annotation.XmlElement;
  35 import javax.xml.bind.annotation.XmlEnumValue;
  36 import javax.xml.bind.annotation.XmlRootElement;
  37 import javax.xml.bind.annotation.XmlTransient;
  38 import javax.xml.namespace.QName;
  39 
  40 import com.sun.codemodel.internal.ClassType;
  41 import com.sun.codemodel.internal.JClassAlreadyExistsException;
  42 import com.sun.codemodel.internal.JCodeModel;
  43 import com.sun.codemodel.internal.JDefinedClass;
  44 import com.sun.tools.internal.xjc.ErrorReceiver;
  45 import com.sun.tools.internal.xjc.generator.bean.ImplStructureStrategy;
  46 import static com.sun.tools.internal.xjc.generator.bean.ImplStructureStrategy.BEAN_ONLY;
  47 import com.sun.tools.internal.xjc.model.Model;
  48 import com.sun.tools.internal.xjc.reader.Const;
  49 import com.sun.tools.internal.xjc.reader.Ring;
  50 import com.sun.tools.internal.xjc.reader.xmlschema.SimpleTypeBuilder;
  51 import com.sun.tools.internal.xjc.util.ReadOnlyAdapter;
  52 import com.sun.xml.internal.bind.api.impl.NameConverter;
  53 import com.sun.xml.internal.bind.v2.WellKnownNamespace;
  54 import com.sun.xml.internal.xsom.XSDeclaration;
  55 import com.sun.xml.internal.xsom.XSSchemaSet;
  56 import com.sun.xml.internal.xsom.XSSimpleType;
  57 
  58 /**
  59  * Global binding customization. The code is highly temporary.
  60  *
  61  * <p>
  62  * One of the information contained in a global customization
  63  * is the default binding for properties. This object contains a
  64  * BIProperty object to keep this information.
  65  *
  66  * @author
  67  *  Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
  68  */
  69 @XmlRootElement(name="globalBindings")
  70 public final class BIGlobalBinding extends AbstractDeclarationImpl {
  71 
  72 
  73     /**
  74      * Gets the name converter that will govern the XML->Java
  75      * name conversion process for this compilation.
  76      *
  77      * <p>
  78      * The "underscoreBinding" customization will determine
  79      * the exact object returned from this method. The rest of XJC
  80      * should just use the NameConverter interface.
  81      *
  82      * <p>
  83      * Always non-null.
  84      */
  85     @XmlTransient
  86     public NameConverter nameConverter = NameConverter.standard;
  87 
  88     // JAXB will use this property to set nameConverter
  89     @XmlAttribute
  90     void setUnderscoreBinding( UnderscoreBinding ub ) {
  91         nameConverter = ub.nc;
  92     }
  93 
  94     UnderscoreBinding getUnderscoreBinding() {
  95         throw new IllegalStateException();  // no need for this
  96     }
  97 
  98     public JDefinedClass getSuperClass() {
  99         if(superClass==null)    return null;
 100         return superClass.getClazz(ClassType.CLASS);
 101     }
 102 
 103     public JDefinedClass getSuperInterface() {
 104         if(superInterface==null)    return null;
 105         return superInterface.getClazz(ClassType.INTERFACE);
 106     }
 107 
 108     public BIProperty getDefaultProperty() {
 109         return defaultProperty;
 110     }
 111 
 112     public boolean isJavaNamingConventionEnabled() {
 113         return isJavaNamingConventionEnabled;
 114     }
 115 
 116     public BISerializable getSerializable() {
 117         return serializable;
 118     }
 119 
 120     public boolean isGenerateElementClass() {
 121         return generateElementClass;
 122     }
 123 
 124     public boolean isGenerateMixedExtensions() {
 125         return generateMixedExtensions;
 126     }
 127 
 128     public boolean isChoiceContentPropertyEnabled() {
 129         return choiceContentProperty;
 130     }
 131 
 132     public int getDefaultEnumMemberSizeCap() {
 133         return defaultEnumMemberSizeCap;
 134     }
 135 
 136     public boolean isSimpleMode() {
 137         return simpleMode!=null;
 138     }
 139 
 140     public boolean isRestrictionFreshType() {
 141         return treatRestrictionLikeNewType !=null;
 142     }
 143 
 144     public EnumMemberMode getEnumMemberMode() {
 145         return generateEnumMemberName;
 146     }
 147 
 148     public boolean isSimpleTypeSubstitution() {
 149         return simpleTypeSubstitution;
 150     }
 151 
 152     public ImplStructureStrategy getCodeGenerationStrategy() {
 153         return codeGenerationStrategy;
 154     }
 155 
 156     public LocalScoping getFlattenClasses() {
 157         return flattenClasses;
 158     }
 159 
 160     /**
 161      * Performs error check
 162      */
 163     public void errorCheck() {
 164         ErrorReceiver er = Ring.get(ErrorReceiver.class);
 165         for (QName n : enumBaseTypes) {
 166             XSSchemaSet xs = Ring.get(XSSchemaSet.class);
 167             XSSimpleType st = xs.getSimpleType(n.getNamespaceURI(), n.getLocalPart());
 168             if(st==null) {
 169                 er.error(loc,Messages.ERR_UNDEFINED_SIMPLE_TYPE.format(n));
 170                 continue;
 171             }
 172 
 173             if(!SimpleTypeBuilder.canBeMappedToTypeSafeEnum(st)) {
 174                 er.error(loc,Messages.ERR_CANNOT_BE_BOUND_TO_SIMPLETYPE.format(n));
 175                 continue;
 176             }
 177         }
 178     }
 179 
 180     private static enum UnderscoreBinding {
 181         @XmlEnumValue("asWordSeparator")
 182         WORD_SEPARATOR(NameConverter.standard),
 183         @XmlEnumValue("asCharInWord")
 184         CHAR_IN_WORD(NameConverter.jaxrpcCompatible);
 185 
 186         final NameConverter nc;
 187 
 188         UnderscoreBinding(NameConverter nc) {
 189             this.nc = nc;
 190         }
 191     }
 192 
 193     /**
 194      * Returns true if the "isJavaNamingConventionEnabled" option is turned on.
 195      *
 196      * In this mode, the compiler is expected to apply XML-to-Java name
 197      * conversion algorithm even to names given by customizations.
 198      *
 199      * This method is intended to be called by other BIXXX classes.
 200      * The effect of this switch should be hidden inside this package.
 201      * IOW, the reader.xmlschema package shouldn't be aware of this switch.
 202      */
 203     @XmlAttribute(name="enableJavaNamingConventions")
 204     /*package*/ boolean isJavaNamingConventionEnabled = true;
 205 
 206     /**
 207      * True to generate classes for every simple type.
 208      */
 209     @XmlAttribute(name="mapSimpleTypeDef")
 210     boolean simpleTypeSubstitution = false;
 211 
 212     /**
 213      * Gets the default defaultProperty customization.
 214      */
 215     @XmlTransient
 216     private BIProperty defaultProperty;
 217 
 218     /*
 219         Three properties used to construct a default property
 220     */
 221     @XmlAttribute
 222     private boolean fixedAttributeAsConstantProperty = false;
 223     @XmlAttribute
 224     private CollectionTypeAttribute collectionType = new CollectionTypeAttribute();
 225     @XmlAttribute
 226     void setGenerateIsSetMethod(boolean b) {
 227         optionalProperty = b ? OptionalPropertyMode.ISSET : OptionalPropertyMode.WRAPPER;
 228     }
 229 
 230 
 231     /**
 232      * Returns true if the compiler needs to generate type-safe enum
 233      * member names when enumeration values cannot be used as constant names.
 234      */
 235     @XmlAttribute(name="typesafeEnumMemberName")
 236     EnumMemberMode generateEnumMemberName = EnumMemberMode.SKIP;
 237 
 238     /**
 239      * The code generation strategy.
 240      */
 241     @XmlAttribute(name="generateValueClass")
 242     ImplStructureStrategy codeGenerationStrategy = BEAN_ONLY;
 243 
 244     /**
 245      * Set of datatype names. For a type-safe enum class
 246      * to be generated, the underlying XML datatype must be derived from
 247      * one of the types in this set.
 248      */
 249     // default value is set in the post-init action
 250     @XmlAttribute(name="typesafeEnumBase")
 251     private Set<QName> enumBaseTypes;
 252 
 253     /**
 254      * Returns {@link BISerializable} if the extension is specified,
 255      * or null otherwise.
 256      */
 257     @XmlElement
 258     private BISerializable serializable = null;
 259 
 260     /**
 261      * If &lt;xjc:superClass> extension is specified,
 262      * returns the specified root class. Otherwise null.
 263      */
 264     @XmlElement(namespace=Const.XJC_EXTENSION_URI)
 265     ClassNameBean superClass = null;
 266 
 267     /**
 268      * If &lt;xjc:superInterface> extension is specified,
 269      * returns the specified root class. Otherwise null.
 270      */
 271     @XmlElement(namespace=Const.XJC_EXTENSION_URI)
 272     ClassNameBean superInterface = null;
 273 
 274     /**
 275      * Generate the simpler optimized code, but not necessarily
 276      * conforming to the spec.
 277      */
 278     @XmlElement(name="simple",namespace=Const.XJC_EXTENSION_URI)
 279     String simpleMode = null;
 280 
 281     /**
 282      * Handles complex type restriction as if it were a new type.
 283      */
 284     @XmlElement(name="treatRestrictionLikeNewType",namespace=Const.XJC_EXTENSION_URI)
 285     String treatRestrictionLikeNewType = null;
 286 
 287     /**
 288      * True to generate a class for elements by default.
 289      */
 290     @XmlAttribute
 291     boolean generateElementClass = false;
 292 
 293     @XmlAttribute
 294     boolean generateMixedExtensions = false;
 295 
 296     @XmlElement(namespace=Const.XJC_EXTENSION_URI)
 297     Boolean generateElementProperty = null;
 298 
 299     @XmlAttribute(name="generateElementProperty")     // for JAXB unmarshaller
 300     private void setGenerateElementPropertyStd(boolean value) {
 301         generateElementProperty = value;
 302     }
 303 
 304     @XmlAttribute
 305     boolean choiceContentProperty = false;
 306 
 307     @XmlAttribute
 308     OptionalPropertyMode optionalProperty = OptionalPropertyMode.WRAPPER;
 309 
 310     /**
 311      * Default cap to the number of constants in the enum.
 312      * We won't attempt to produce a type-safe enum by default
 313      * if there are more enumeration facets than specified in this field.
 314      */
 315     @XmlAttribute(name="typesafeEnumMaxMembers")
 316     int defaultEnumMemberSizeCap = 256;
 317 
 318     /**
 319      * If true, interfaces/classes that are normally generated as a nested interface/class
 320      * will be generated into the package, allowing the generated classes to be flat.
 321      *
 322      * See <a href="http://monaco.sfbay/detail.jsf?cr=4969415">Bug 4969415</a> for the motivation.
 323      */
 324     @XmlAttribute(name="localScoping")
 325     LocalScoping flattenClasses = LocalScoping.NESTED;
 326 
 327     /**
 328      * Globally-defined conversion customizations.
 329      *
 330      * @see #setGlobalConversions
 331      */
 332     @XmlTransient
 333     private final Map<QName,BIConversion> globalConversions = new HashMap<QName, BIConversion>();
 334 
 335     // method for JAXB unmarshaller
 336     @XmlElement(name="javaType")
 337     private void setGlobalConversions(GlobalStandardConversion[] convs) {
 338         for (GlobalStandardConversion u : convs) {
 339             globalConversions.put(u.xmlType,u);
 340         }
 341     }
 342 
 343     @XmlElement(name="javaType",namespace=Const.XJC_EXTENSION_URI)
 344     private void setGlobalConversions2(GlobalVendorConversion[] convs) {
 345         for (GlobalVendorConversion u : convs) {
 346             globalConversions.put(u.xmlType,u);
 347         }
 348     }
 349 
 350     //
 351     // these customizations were valid in 1.0, but in 2.0 we don't
 352     // use them. OTOH, we don't want to issue an error for them,
 353     // so we just define a mapping and ignore the value.
 354     //
 355     @XmlElement(namespace=Const.XJC_EXTENSION_URI)
 356     String noMarshaller = null;
 357     @XmlElement(namespace=Const.XJC_EXTENSION_URI)
 358     String noUnmarshaller = null;
 359     @XmlElement(namespace=Const.XJC_EXTENSION_URI)
 360     String noValidator = null;
 361     @XmlElement(namespace=Const.XJC_EXTENSION_URI)
 362     String noValidatingUnmarshaller = null;
 363     @XmlElement(namespace=Const.XJC_EXTENSION_URI)
 364     TypeSubstitutionElement typeSubstitution = null;
 365 
 366     /**
 367      * Another 1.0 compatibility customization (but we accept it
 368      * and treat it as {@link #serializable})
 369      */
 370     @XmlElement(name="serializable",namespace=Const.XJC_EXTENSION_URI)
 371     void setXjcSerializable(BISerializable s) {
 372         this.serializable = s;
 373     }
 374 
 375 
 376 
 377     private static final class TypeSubstitutionElement {
 378         @XmlAttribute
 379         String type;
 380     }
 381 
 382     public void onSetOwner() {
 383         super.onSetOwner();
 384         // if one is given by options, use that
 385         NameConverter nc = Ring.get(Model.class).options.getNameConverter();
 386         if(nc!=null)
 387             nameConverter = nc;
 388     }
 389 
 390     /**
 391      * Creates a bind info object with the default values
 392      */
 393     public BIGlobalBinding() {
 394     }
 395 
 396     public void setParent(BindInfo parent) {
 397         super.setParent(parent);
 398         // fill in the remaining default values
 399         if(enumBaseTypes==null)
 400             enumBaseTypes = Collections.singleton(new QName(WellKnownNamespace.XML_SCHEMA,"string"));
 401 
 402         this.defaultProperty = new BIProperty(getLocation(),null,null,null,
 403                 collectionType, fixedAttributeAsConstantProperty, optionalProperty, generateElementProperty );
 404         defaultProperty.setParent(parent); // don't forget to initialize the defaultProperty
 405     }
 406 
 407     /**
 408      * Moves global BIConversion to the right object.
 409      */
 410     public void dispatchGlobalConversions( XSSchemaSet schema ) {
 411         // also set parent to the global conversions
 412         for( Map.Entry<QName,BIConversion> e : globalConversions.entrySet() ) {
 413 
 414             QName name = e.getKey();
 415             BIConversion conv = e.getValue();
 416 
 417             XSSimpleType st = schema.getSimpleType(name.getNamespaceURI(),name.getLocalPart());
 418             if(st==null) {
 419                 Ring.get(ErrorReceiver.class).error(
 420                     getLocation(),
 421                     Messages.ERR_UNDEFINED_SIMPLE_TYPE.format(name)
 422                 );
 423                 continue; // abort
 424             }
 425 
 426             getBuilder().getOrCreateBindInfo(st).addDecl(conv);
 427         }
 428     }
 429 
 430 
 431     /**
 432      * Checks if the given XML Schema built-in type can be mapped to
 433      * a type-safe enum class.
 434      *
 435      * @param typeName
 436      */
 437     public boolean canBeMappedToTypeSafeEnum( QName typeName ) {
 438         return enumBaseTypes.contains(typeName);
 439     }
 440 
 441     public boolean canBeMappedToTypeSafeEnum( String nsUri, String localName ) {
 442         return canBeMappedToTypeSafeEnum(new QName(nsUri,localName));
 443     }
 444 
 445     public boolean canBeMappedToTypeSafeEnum( XSDeclaration decl ) {
 446         return canBeMappedToTypeSafeEnum( decl.getTargetNamespace(), decl.getName() );
 447     }
 448 
 449 
 450     public QName getName() { return NAME; }
 451     public static final QName NAME = new QName(
 452         Const.JAXB_NSURI, "globalBindings" );
 453 
 454 
 455     /**
 456      * Used to unmarshal
 457      * <pre>{@code
 458      * <[element] name="className" />
 459      * }</pre>
 460      */
 461     static final class ClassNameBean {
 462         @XmlAttribute(required=true)
 463         String name;
 464 
 465         /**
 466          * Computed from {@link #name} on demand.
 467          */
 468         @XmlTransient
 469         JDefinedClass clazz;
 470 
 471         JDefinedClass getClazz(ClassType t) {
 472             if (clazz != null) return clazz;
 473             try {
 474                 JCodeModel codeModel = Ring.get(JCodeModel.class);
 475                 clazz = codeModel._class(name, t);
 476                 clazz.hide();
 477                 return clazz;
 478             } catch (JClassAlreadyExistsException e) {
 479                 return e.getExistingClass();
 480             }
 481         }
 482     }
 483 
 484     static final class ClassNameAdapter extends ReadOnlyAdapter<ClassNameBean,String> {
 485         public String unmarshal(ClassNameBean bean) throws Exception {
 486             return bean.name;
 487         }
 488     }
 489 
 490     /**
 491      * Global &lt;jaxb:javaType>.
 492      */
 493     static final class GlobalStandardConversion extends BIConversion.User {
 494         @XmlAttribute
 495         QName xmlType;
 496 
 497         @Override
 498         public boolean equals(Object obj) {
 499             if(obj instanceof GlobalStandardConversion) {
 500                 return ((GlobalStandardConversion)obj).xmlType.equals(xmlType);
 501     }
 502 
 503             return false;
 504         }
 505 
 506         @Override
 507         public int hashCode() {
 508             int hash = 7;
 509             hash = 73 * hash + (this.xmlType != null ? this.xmlType.hashCode() : 0);
 510             return hash;
 511         }
 512     }
 513 
 514     /**
 515      * Global &lt;xjc:javaType>.
 516      */
 517     static final class GlobalVendorConversion extends BIConversion.UserAdapter {
 518         @XmlAttribute
 519         QName xmlType;
 520 
 521         @Override
 522         public boolean equals(Object obj) {
 523             if(obj instanceof GlobalVendorConversion) {
 524                 return ((GlobalVendorConversion)obj).xmlType.equals(xmlType);
 525     }
 526 
 527             return false;
 528         }
 529 
 530         @Override
 531         public int hashCode() {
 532             int hash = 7;
 533             hash = 73 * hash + (this.xmlType != null ? this.xmlType.hashCode() : 0);
 534             return hash;
 535         }
 536     }
 537 
 538     /* don't want to override equals to avoid overriding hashcode for this complex object, too */
 539     public boolean isEqual(BIGlobalBinding b) {
 540         boolean equal =
 541             this.isJavaNamingConventionEnabled == b.isJavaNamingConventionEnabled &&
 542             this.simpleTypeSubstitution == b.simpleTypeSubstitution &&
 543             this.fixedAttributeAsConstantProperty == b.fixedAttributeAsConstantProperty &&
 544             this.generateEnumMemberName == b.generateEnumMemberName &&
 545             this.codeGenerationStrategy == b.codeGenerationStrategy &&
 546             this.serializable == b.serializable &&
 547             this.superClass == b.superClass &&
 548             this.superInterface == b.superInterface &&
 549             this.generateElementClass == b.generateElementClass &&
 550             this.generateMixedExtensions == b.generateMixedExtensions &&
 551             this.generateElementProperty == b.generateElementProperty &&
 552             this.choiceContentProperty == b.choiceContentProperty &&
 553             this.optionalProperty == b.optionalProperty &&
 554             this.defaultEnumMemberSizeCap == b.defaultEnumMemberSizeCap &&
 555             this.flattenClasses == b.flattenClasses;
 556 
 557         if (!equal) return false;
 558 
 559         return isEqual(this.nameConverter, b.nameConverter) &&
 560                isEqual(this.noMarshaller, b.noMarshaller) &&
 561                isEqual(this.noUnmarshaller, b.noUnmarshaller) &&
 562                isEqual(this.noValidator, b.noValidator) &&
 563                isEqual(this.noValidatingUnmarshaller, b.noValidatingUnmarshaller) &&
 564                isEqual(this.typeSubstitution, b.typeSubstitution) &&
 565                isEqual(this.simpleMode, b.simpleMode) &&
 566                isEqual(this.enumBaseTypes, b.enumBaseTypes) &&
 567                isEqual(this.treatRestrictionLikeNewType, b.treatRestrictionLikeNewType) &&
 568                isEqual(this.globalConversions, b.globalConversions);
 569     }
 570 
 571     private boolean isEqual(Object a, Object b) {
 572         if (a != null) {
 573             return a.equals(b);
 574         }
 575         return (b == null);
 576     }
 577 }