com.sun.tools.xjc.addon.xew.XmlElementWrapperPlugin.java Source code

Java tutorial

Introduction

Here is the source code for com.sun.tools.xjc.addon.xew.XmlElementWrapperPlugin.java

Source

/*
 * XmlElementWrapperPlugin.java
 * 
 * Copyright (C) 2009, Bjarne Hansen, http://www.conspicio.dk.
 * All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301  USA
 */
package com.sun.tools.xjc.addon.xew;

import static com.sun.tools.xjc.addon.xew.CommonUtils.addAnnotation;
import static com.sun.tools.xjc.addon.xew.CommonUtils.copyFields;
import static com.sun.tools.xjc.addon.xew.CommonUtils.generableToString;
import static com.sun.tools.xjc.addon.xew.CommonUtils.getAnnotation;
import static com.sun.tools.xjc.addon.xew.CommonUtils.getAnnotationMember;
import static com.sun.tools.xjc.addon.xew.CommonUtils.getAnnotationMemberExpression;
import static com.sun.tools.xjc.addon.xew.CommonUtils.getPrivateField;
import static com.sun.tools.xjc.addon.xew.CommonUtils.getXsdDeclaration;
import static com.sun.tools.xjc.addon.xew.CommonUtils.hasPropertyNameCustomization;
import static com.sun.tools.xjc.addon.xew.CommonUtils.isHiddenClass;
import static com.sun.tools.xjc.addon.xew.CommonUtils.isListedAsParametrisation;
import static com.sun.tools.xjc.addon.xew.CommonUtils.removeAnnotation;
import static com.sun.tools.xjc.addon.xew.CommonUtils.setPrivateField;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlElementRefs;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlMixed;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.namespace.QName;

import com.sun.codemodel.JAnnotatable;
import com.sun.codemodel.JAnnotationArrayMember;
import com.sun.codemodel.JAnnotationUse;
import com.sun.codemodel.JAnnotationValue;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassContainer;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JJavaName;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JPackage;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.addon.xew.config.AbstractConfigurablePlugin;
import com.sun.tools.xjc.addon.xew.config.ClassConfiguration;
import com.sun.tools.xjc.addon.xew.config.CommonConfiguration;
import com.sun.tools.xjc.model.CElementPropertyInfo;
import com.sun.tools.xjc.model.CElementPropertyInfo.CollectionMode;
import com.sun.tools.xjc.model.CPropertyInfo;
import com.sun.tools.xjc.model.CReferencePropertyInfo;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.FieldOutline;
import com.sun.tools.xjc.outline.Outline;
import com.sun.tools.xjc.reader.Ring;
import com.sun.xml.bind.api.impl.NameConverter;
import com.sun.xml.xsom.XSComponent;
import com.sun.xml.xsom.XSDeclaration;

import org.apache.commons.lang3.ObjectUtils;
import org.jvnet.jaxb2_commons.util.CustomizationUtils;

/**
 * The XML Element Wrapper plugin is a JAXB plugin for the XJC compiler enabling generation of "natural" Java classes
 * for handling collection types. The code generated will be annotated with {@link XmlElementWrapper} and
 * {@link XmlElement} annotations and will have no extra inner classes representing the immediate collection type.
 * 
 * @see <a href="https://github.com/dmak/jaxb-xew-plugin">plugin site</a>
 * @see <a href="http://www.conspicio.dk/blog/bjarne/jaxb-xmlelementwrapper-plugin">original plugin site</a>
 * @see <a href="http://www.conspicio.dk/projects/overview">source code and binary packages</a>
 * 
 * @author Bjarne Hansen
 * @author Dmitry Katsubo
 */
public class XmlElementWrapperPlugin extends AbstractConfigurablePlugin {

    private JClass xmlElementDeclModelClass;

    static final String FACTORY_CLASS_NAME = "ObjectFactory";

    @Override
    protected void runInternal(Outline outline) throws ClassNotFoundException, IOException {
        JCodeModel codeModel = outline.getCodeModel();
        JClass xmlElementWrapperModelClass = codeModel.ref(XmlElementWrapper.class);
        JClass xmlElementModelClass = codeModel.ref(XmlElement.class);
        JClass xmlAnyElementModelClass = codeModel.ref(XmlAnyElement.class);
        JClass xmlMixedModelClass = codeModel.ref(XmlMixed.class);
        JClass xmlElementRefModelClass = codeModel.ref(XmlElementRef.class);
        JClass xmlElementRefsModelClass = codeModel.ref(XmlElementRefs.class);
        JClass xmlElementsModelClass = codeModel.ref(XmlElements.class);
        JClass xmlJavaTypeAdapterModelClass = codeModel.ref(XmlJavaTypeAdapter.class);
        JClass xmlTypeModelClass = codeModel.ref(XmlType.class);
        xmlElementDeclModelClass = codeModel.ref(XmlElementDecl.class);

        Ring.begin();
        Ring.add(outline.getModel());

        logger.debug("JAXB Process Model (run)...");

        applyConfigurationFromCustomizations(globalConfiguration,
                CustomizationUtils.getCustomizations(outline.getModel()), false);

        // Write summary information on the option for this compilation.
        writeSummary("Compilation:");
        writeSummary("  JAXB version         : " + Options.getBuildID());
        writeSummary("  Control file         : "
                + ObjectUtils.defaultIfNull(globalConfiguration.getControlFileName(), "<none>"));
        writeSummary("  Summary file         : "
                + ObjectUtils.defaultIfNull(globalConfiguration.getSummaryFileName(), "<none>"));
        writeSummary("  Instantiation mode   : " + globalConfiguration.getInstantiationMode());
        writeSummary("  Collection impl      : " + globalConfiguration.getCollectionImplClass().getName());
        writeSummary("  Collection interface : " + globalConfiguration.getCollectionInterfaceClass().getName());
        writeSummary("  Plural form          : " + globalConfiguration.isApplyPluralForm());
        writeSummary("");

        // Visit all classes generated by JAXB and find candidate classes for transformation.
        Map<String, Candidate> candidatesMap = new HashMap<String, Candidate>();

        // Write information on candidate classes to summary file.
        writeSummary("Candidates:");

        for (Iterator<Candidate> iter = findCandidateClasses(outline).iterator(); iter.hasNext();) {
            Candidate candidate = iter.next();

            if (globalConfiguration.isClassIncluded(candidate.getClassName())) {
                if (globalConfiguration.isClassUnmarkedForRemoval(candidate.getClassName())) {
                    candidate.unmarkForRemoval();
                    writeSummary("\t[!]: " + candidate.getClassName());
                } else {
                    writeSummary("\t[+]: " + candidate.getClassName());
                }

                candidatesMap.put(candidate.getClassName(), candidate);
            } else {
                writeSummary("\t[-]: " + candidate.getClassName());
            }
        }

        writeSummary("\t" + candidatesMap.size() + " candidate(s) being considered.");
        writeSummary("");

        writeSummary("Modifications:");

        int modificationCount = 0;

        // Visit all classes again to check if the candidate is not eligible for removal:
        // * If there are classes that extend the candidate
        // * If there are class fields, that refer the candidate by e.g. @XmlElementRef annotation
        for (ClassOutline outlineClass : outline.getClasses()) {
            // Get the implementation class for the current class.
            JDefinedClass targetClass = outlineClass.implClass;

            ClassConfiguration classConfiguration = applyConfigurationFromCustomizations(globalConfiguration,
                    CustomizationUtils.getCustomizations(outlineClass), true);

            // We cannot remove candidates that have parent classes, but we can still substitute them:
            Candidate parentCandidate = candidatesMap.get(targetClass._extends().fullName());

            if (parentCandidate != null) {
                logger.debug("Candidate " + parentCandidate.getClassName() + " is a parent of " + targetClass.name()
                        + " and hence won't be removed.");
                parentCandidate.unmarkForRemoval();
            }

            // Visit all fields in this class.
            for (FieldOutline field : outlineClass.getDeclaredFields()) {
                // Only non-primitive fields are interesting.
                // Consider only PropertyKind.ELEMENT as (for example) PropertyKind.ATTRIBUTE (stands for XSD attribute) is always simple type:
                if (!(field.getRawType() instanceof JClass)
                        || !(field.getPropertyInfo() instanceof CElementPropertyInfo)) {
                    continue;
                }

                final JClass fieldType = (JClass) field.getRawType();
                final CPropertyInfo fieldPropertyInfo = field.getPropertyInfo();
                String fieldName = fieldPropertyInfo.getName(false);
                Candidate candidate = null;

                for (Candidate c : candidatesMap.values()) {
                    // Skip fields with basic types as for example any class can be casted to Object.
                    if (fieldType.isAssignableFrom(c.getClazz()) && !isHiddenClass(fieldType)) {
                        // If the given field has type T, it cannot be also in the list of parametrisations (e.g. T<T>).
                        candidate = c;
                        break;
                    }
                    // If the candidate T is referred from list of parametrisations (e.g. List<T>), it cannot be removed.
                    // However field substitutions will take place.
                    else if (isListedAsParametrisation(c.getClazz(), fieldType)) {
                        logger.debug("Candidate " + c.getClassName() + " is listed as parametrisation of "
                                + targetClass.fullName() + "#" + fieldName + " and hence won't be removed.");
                        c.unmarkForRemoval();
                    }
                }

                final JFieldVar originalImplField = targetClass.fields().get(fieldName);

                if (candidate == null || !classConfiguration.isAnnotatable()) {
                    checkAnnotationReference(candidatesMap, originalImplField);

                    continue;
                }

                ClassConfiguration fieldConfiguration = applyConfigurationFromCustomizations(classConfiguration,
                        CustomizationUtils.getCustomizations(field), true);

                if (!fieldConfiguration.isAnnotatable()) {
                    logger.debug("Field " + fieldName + " is excluded for processing.");
                    candidate.unmarkForRemoval();

                    continue;
                }

                // We have a candidate field to be replaced with a wrapped version. Report finding to summary file.
                writeSummary("\tReplacing field [" + fieldType.name() + " " + targetClass.fullName() + "#"
                        + fieldName + "]");
                candidate.incrementSubstitutions();
                modificationCount++;

                // The container class has to be deleted. Check that inner class has to be moved to it's parent.
                if (moveInnerClassToParent(outline, candidate)) {
                    modificationCount++;
                }

                List<JClass> fieldTypeParametrisations = candidate.getFieldClass().getTypeParameters();

                // Create the new interface and collection classes using the specified interface and
                // collection classes (configuration) with an element type corresponding to
                // the element type from the collection present in the candidate class (narrowing).
                JClass collectionInterfaceClass = codeModel.ref(fieldConfiguration.getCollectionInterfaceClass())
                        .narrow(fieldTypeParametrisations);
                JClass collectionImplClass = codeModel.ref(fieldConfiguration.getCollectionImplClass())
                        .narrow(fieldTypeParametrisations);

                boolean pluralFormWasApplied = false;

                // Apply the plural form if there are no customizations. Assuming that customization is correct as may define the
                // plural form in more correct way, e.g. "field[s]OfScience" instead of "fieldOfScience[s]".
                if (fieldConfiguration.isApplyPluralForm() && !hasPropertyNameCustomization(fieldPropertyInfo)) {
                    String oldFieldName = fieldName;

                    // Taken from com.sun.tools.xjc.reader.xmlschema.ParticleBinder#makeJavaName():
                    fieldName = JJavaName.getPluralForm(fieldName);

                    // The field e.g. "return" was escaped as "_return", but after conversion to plural
                    // it became valid Java identifier, so we remove the leading "_":
                    if (fieldName.startsWith("_") && JJavaName.isJavaIdentifier(fieldName.substring(1))) {
                        fieldName = fieldName.substring(1);
                    }

                    if (!fieldName.equals(oldFieldName)) {
                        pluralFormWasApplied = true;

                        originalImplField.name(fieldName);

                        // Correct the @XmlType class-level annotation:
                        JAnnotationArrayMember propOrderValue = (JAnnotationArrayMember) getAnnotation(targetClass,
                                xmlTypeModelClass).getAnnotationMembers().get("propOrder");

                        if (propOrderValue != null) {
                            for (JAnnotationValue annotationValue : propOrderValue.annotations()) {
                                if (oldFieldName.equals(generableToString(annotationValue))) {
                                    setPrivateField(annotationValue, "value", JExpr.lit(fieldName));
                                    break;
                                }
                            }
                        }
                    }
                }

                // Transform the field accordingly.
                originalImplField.type(collectionInterfaceClass);

                // If instantiation is specified to be "early", add code for creating new instance of the collection class.
                if (fieldConfiguration.getInstantiationMode() == CommonConfiguration.InstantiationMode.EARLY) {
                    logger.debug("Applying EARLY instantiation...");
                    // GENERATED CODE: ... fieldName = new C<T>();
                    originalImplField.init(JExpr._new(collectionImplClass));
                }

                // Annotate the field with the @XmlElementWrapper annotation using the original field name.
                JAnnotationUse xmlElementWrapperAnnotation = originalImplField
                        .annotate(xmlElementWrapperModelClass);
                JAnnotationUse xmlElementOriginalAnnotation = getAnnotation(originalImplField,
                        xmlElementModelClass);

                // xmlElementOriginalAnnotation can be null:
                JExpression wrapperXmlName = getAnnotationMemberExpression(xmlElementOriginalAnnotation, "name");
                if (wrapperXmlName != null) {
                    xmlElementWrapperAnnotation.param("name", wrapperXmlName);
                } else if (fieldConfiguration.isApplyPluralForm()) {
                    xmlElementWrapperAnnotation.param("name", getXsdDeclaration(fieldPropertyInfo).getName());
                }

                JExpression wrapperXmlRequired = getAnnotationMemberExpression(xmlElementOriginalAnnotation,
                        "required");
                if (wrapperXmlRequired != null) {
                    xmlElementWrapperAnnotation.param("required", wrapperXmlRequired);
                }

                JExpression wrapperXmlNillable = getAnnotationMemberExpression(xmlElementOriginalAnnotation,
                        "nillable");
                if (wrapperXmlNillable != null) {
                    xmlElementWrapperAnnotation.param("nillable", wrapperXmlNillable);
                }

                // Namespace of the wrapper element
                JExpression wrapperXmlNamespace = getAnnotationMemberExpression(xmlElementOriginalAnnotation,
                        "namespace");
                if (wrapperXmlNamespace != null) {
                    xmlElementWrapperAnnotation.param("namespace", wrapperXmlNamespace);
                }

                if (xmlElementOriginalAnnotation != null) {
                    removeAnnotation(originalImplField, xmlElementOriginalAnnotation);
                }

                boolean xmlElementInfoWasTransferred = false;

                // Transfer @XmlAnyElement, @XmlElementRefs, @XmlElements:
                for (JClass annotationModelClass : new JClass[] { xmlAnyElementModelClass, xmlMixedModelClass,
                        xmlElementRefModelClass, xmlElementRefsModelClass, xmlElementsModelClass }) {
                    JAnnotationUse annotation = getAnnotation(candidate.getField(), annotationModelClass);

                    if (annotation != null) {
                        if (candidate.getFieldTargetNamespace() != null) {
                            JAnnotationArrayMember annotationArrayMember = (JAnnotationArrayMember) getAnnotationMember(
                                    annotation, "value");

                            if (annotationArrayMember != null) {
                                for (JAnnotationUse subAnnotation : annotationArrayMember.annotations()) {
                                    if (getAnnotationMemberExpression(subAnnotation, "namespace") == null) {
                                        subAnnotation.param("namespace", candidate.getFieldTargetNamespace());
                                    }
                                }
                            }
                        }

                        xmlElementInfoWasTransferred = true;

                        addAnnotation(originalImplField, annotation);
                    }
                }

                if (!xmlElementInfoWasTransferred) {
                    // Annotate the field with the @XmlElement annotation using the field name from the wrapped type as name.
                    // We cannot just re-use the same annotation object instance, as for example, we need to set XML name and this
                    // will impact the candidate field annotation in case candidate is unmarked from removal.
                    JAnnotationUse xmlElementAnnotation = originalImplField.annotate(xmlElementModelClass);
                    JAnnotationUse xmlElementCandidateAnnotation = getAnnotation(candidate.getField(),
                            xmlElementModelClass);

                    // xmlElementOriginalAnnotation can be null:
                    JExpression xmlName = getAnnotationMemberExpression(xmlElementCandidateAnnotation, "name");
                    if (xmlName != null) {
                        xmlElementAnnotation.param("name", xmlName);
                    } else {
                        xmlElementAnnotation.param("name", candidate.getFieldName());
                    }

                    JExpression xmlNamespace = getAnnotationMemberExpression(xmlElementCandidateAnnotation,
                            "namespace");
                    if (xmlNamespace != null) {
                        xmlElementAnnotation.param("namespace", xmlNamespace);
                    } else if (candidate.getFieldTargetNamespace() != null) {
                        xmlElementAnnotation.param("namespace", candidate.getFieldTargetNamespace());
                    }

                    JExpression type = getAnnotationMemberExpression(xmlElementCandidateAnnotation, "type");
                    if (type != null) {
                        xmlElementAnnotation.param("type", type);
                    }

                    JExpression required = getAnnotationMemberExpression(xmlElementCandidateAnnotation,
                            "defaultValue");
                    if (required != null) {
                        xmlElementAnnotation.param("defaultValue", required);
                    }

                    JExpression nillable = getAnnotationMemberExpression(xmlElementCandidateAnnotation, "nillable");
                    if (nillable != null) {
                        xmlElementAnnotation.param("nillable", nillable);
                    }
                }

                JAnnotationUse adapterAnnotation = getAnnotation(candidate.getField(),
                        xmlJavaTypeAdapterModelClass);

                if (adapterAnnotation != null) {
                    addAnnotation(originalImplField, adapterAnnotation);
                }

                // Same as fieldName, but used as getter/setter method name:
                String propertyName = fieldPropertyInfo.getName(true);

                JDefinedClass implementationInterface = null;

                for (Iterator<JClass> iter = targetClass._implements(); iter.hasNext();) {
                    JClass interfaceClass = iter.next();

                    // If value class implements some JVM interface it is not considered as such interface cannot be modified:
                    if (interfaceClass instanceof JDefinedClass
                            && deleteSettersGetters((JDefinedClass) interfaceClass, propertyName)) {
                        implementationInterface = (JDefinedClass) interfaceClass;
                        break;
                    }
                }

                // Find original getter and setter methods to remove.
                deleteSettersGetters(targetClass, propertyName);

                // The type in property info should correspond to field type. For that we clone the candidate property info:
                CPropertyInfo candidateFieldPropertyInfo = candidate.getFieldPropertyInfo();
                CPropertyInfo propertyInfoClone = null;

                if (candidateFieldPropertyInfo instanceof CElementPropertyInfo) {
                    propertyInfoClone = new CElementPropertyInfo("", CollectionMode.NOT_REPEATED, null, null, null,
                            null, null, false);
                } else if (candidateFieldPropertyInfo instanceof CReferencePropertyInfo) {
                    propertyInfoClone = new CReferencePropertyInfo("", false, false, false, null, null, null, false,
                            false, false);
                } else {
                    // There could be no other option as candidate field is a collection, hence not simple property.
                    assert false;
                    propertyInfoClone = candidateFieldPropertyInfo;
                }

                copyFields(candidateFieldPropertyInfo, propertyInfoClone);

                if (pluralFormWasApplied) {
                    propertyName = JJavaName.getPluralForm(propertyName);
                }

                propertyInfoClone.setName(false, fieldName);
                propertyInfoClone.setName(true, propertyName);

                setPrivateField(field, "prop", propertyInfoClone);

                // Add a new getter method returning the (wrapped) field added.
                // GENERATED CODE: public I<T> getFieldName() { ... return fieldName; }
                JMethod getterMethod = targetClass.method(JMod.PUBLIC, collectionInterfaceClass,
                        "get" + propertyName);

                if (fieldConfiguration.getInstantiationMode() == CommonConfiguration.InstantiationMode.LAZY) {
                    logger.debug("Applying LAZY instantiation...");
                    // GENERATED CODE: if (fieldName == null) fieldName = new C<T>();
                    getterMethod.body()._if(JExpr.ref(fieldName).eq(JExpr._null()))._then()
                            .assign(JExpr.ref(fieldName), JExpr._new(collectionImplClass));
                }

                // GENERATED CODE: return "fieldName";
                getterMethod.body()._return(JExpr.ref(fieldName));

                // Add a new setter method:
                // GENERATED CODE: public void setFieldName(I<T> fieldName) { this.fieldName = fieldName; }
                JMethod setterMethod = targetClass.method(JMod.PUBLIC, codeModel.VOID, "set" + propertyName);

                setterMethod.body().assign(JExpr._this().ref(fieldName),
                        setterMethod.param(collectionInterfaceClass, fieldName));

                // Modify interface as well:
                if (implementationInterface != null) {
                    writeSummary("\tCorrecting interface " + implementationInterface.fullName());

                    implementationInterface.method(JMod.PUBLIC, collectionInterfaceClass, "get" + propertyName);
                    setterMethod = implementationInterface.method(JMod.PUBLIC, codeModel.VOID,
                            "set" + propertyName);
                    setterMethod.param(collectionInterfaceClass, fieldName);
                }

                // Adapt factory class:
                for (JDefinedClass objectFactoryClass : candidate.getObjectFactoryClasses()) {
                    modificationCount += createScopedFactoryMethods(codeModel, objectFactoryClass,
                            candidate.getScopedElementInfos().values(), targetClass);
                }

                candidate.addObjectFactoryForClass(targetClass);
            }
        }

        writeSummary("\t" + modificationCount + " modification(s) to original code.");
        writeSummary("");

        int deletionCount = deleteCandidates(outline, candidatesMap.values());

        writeSummary("\t" + deletionCount + " deletion(s) from original code.");
        writeSummary("");

        globalConfiguration.closeSummary();

        Ring.end(null);

        logger.debug("Done");
    }

    /**
     * If candidate class contains the inner class which is collection parametrisation (type), then this inner class has
     * to be moved to top class. For example from<br>
     * {@code TypeClass (is a collection type) -> ContainerClass (marked for removal) -> ElementClass}<br>
     * we need to get<br>
     * {@code TypeClass -> ElementClass}.<br>
     * Also this move should be reflected on factory method names.
     */
    private boolean moveInnerClassToParent(Outline outline, Candidate candidate) {
        // Skip basic parametrisations like "List<String>":
        if (candidate.getFieldParametrisationClass() == null) {
            return false;
        }

        JDefinedClass fieldParametrisationImpl = candidate.getFieldParametrisationImpl();

        if (candidate.getClazz() != fieldParametrisationImpl.parentContainer()) {
            // Field parametrisation class is not inner class of the candidate:
            return false;
        }

        JDefinedClass fieldParametrisationClass = candidate.getFieldParametrisationClass();

        String oldFactoryMethodName = fieldParametrisationClass.outer().name() + fieldParametrisationClass.name();

        moveClassLevelUp(outline, fieldParametrisationImpl);

        renameFactoryMethod(fieldParametrisationImpl._package()._getClass(FACTORY_CLASS_NAME), oldFactoryMethodName,
                fieldParametrisationClass.name());

        if (candidate.isValueObjectDisabled()) {
            moveClassLevelUp(outline, fieldParametrisationClass);

            renameFactoryMethod(fieldParametrisationClass._package()._getClass(FACTORY_CLASS_NAME),
                    oldFactoryMethodName, fieldParametrisationClass.name());
        }

        return true;
    }

    /**
     * Create additional factory methods with a new scope for elements that should be scoped.
     * 
     * @param targetClass
     *            the class that is applied the transformation of properties
     * @return number of created methods
     * @see com.sun.tools.xjc.generator.bean.ObjectFactoryGenerator
     */
    private int createScopedFactoryMethods(JCodeModel codeModel, JDefinedClass factoryClass,
            Collection<ScopedElementInfo> scopedElementInfos, JDefinedClass targetClass) {
        int createdMethods = 0;

        NEXT: for (ScopedElementInfo info : scopedElementInfos) {
            String dotClazz = targetClass.fullName() + ".class";

            // First check that such factory method has not yet been created. It can be the case if target class
            // is substituted with e.g. two candidates, each candidate having a field with the same name.
            // FIXME: Could it be the case that these two fields have different namespaces?
            for (JMethod method : factoryClass.methods()) {
                JAnnotationUse xmlElementDeclAnnotation = getAnnotation(method, xmlElementDeclModelClass);

                JExpression scope = getAnnotationMemberExpression(xmlElementDeclAnnotation, "scope");
                JExpression name = getAnnotationMemberExpression(xmlElementDeclAnnotation, "name");

                if (scope != null && dotClazz.equals(generableToString(scope))
                        && generableToString(info.name).equals(generableToString(name))) {
                    continue NEXT;
                }
            }

            // Generate the scoped factory method:
            //   @XmlElementDecl(..., scope = T.class)
            //   public JAXBElement<X> createT...(X value) { return new JAXBElement<...>(QNAME, X.class, T.class, value); }
            StringBuilder methodName = new StringBuilder();

            JDefinedClass container = targetClass;

            // To avoid potential name conflicts method name starts with scope class name:
            while (true) {
                methodName.insert(0, container.name());

                if (container.parentContainer().isClass()) {
                    container = (JDefinedClass) container.parentContainer();
                } else {
                    break;
                }
            }

            methodName.insert(0, "create")
                    .append(NameConverter.standard.toPropertyName(generableToString(info.name)));

            JClass wrapperType = codeModel.ref(JAXBElement.class).narrow(info.type);

            JMethod method = factoryClass.method(JMod.PUBLIC, wrapperType, methodName.toString());

            method.annotate(xmlElementDeclModelClass).param("namespace", info.namespace).param("name", info.name)
                    .param("scope", targetClass);

            // FIXME: Make a try to load constants and (a) rename it appropriately (b) use it?
            JInvocation qname = JExpr._new(codeModel.ref(QName.class)).arg(info.namespace).arg(info.name);

            method.body()._return(JExpr._new(wrapperType).arg(qname).arg(info.type.boxify().dotclass())
                    .arg(targetClass.dotclass()).arg(method.param(info.type, "value")));

            createdMethods++;
        }

        return createdMethods;
    }

    /**
     * Locate the candidates classes for substitution/removal.
     * 
     * @return a map className -> Candidate
     */
    private Collection<Candidate> findCandidateClasses(Outline outline) {
        Map<String, ClassOutline> interfaceImplementations = new HashMap<String, ClassOutline>();

        // Visit all classes to create a map "interfaceName -> ClassOutline".
        // This map is later used to resolve implementations from interfaces.
        for (ClassOutline classOutline : outline.getClasses()) {
            for (Iterator<JClass> iter = classOutline.implClass._implements(); iter.hasNext();) {
                JClass interfaceClass = iter.next();

                if (interfaceClass instanceof JDefinedClass) {
                    // Don't care if some interfaces collide: value classes have exactly one implementation
                    interfaceImplementations.put(interfaceClass.fullName(), classOutline);
                }
            }
        }

        Collection<Candidate> candidates = new ArrayList<Candidate>();

        JClass collectionModelClass = outline.getCodeModel().ref(Collection.class);
        JClass xmlSchemaModelClass = outline.getCodeModel().ref(XmlSchema.class);

        // Visit all classes created by JAXB processing to collect all potential wrapper classes to be removed:
        for (ClassOutline classOutline : outline.getClasses()) {
            JDefinedClass candidateClass = classOutline.implClass;

            // * The candidate class should not extend any other model class (as the total number of properties in this case will be more than 1)
            if (!isHiddenClass(candidateClass._extends())) {
                continue;
            }

            JFieldVar field = null;

            // * The candidate class should have exactly one property
            for (JFieldVar f : candidateClass.fields().values()) {
                if ((f.mods().getValue() & JMod.STATIC) == JMod.STATIC) {
                    continue;
                }

                // If there are at least two non-static fields, we discard this candidate:
                if (field != null) {
                    field = null;
                    break;
                }

                field = f;
            }

            // "field" is null if there are no fields (or all fields are static) or there are more then two fields.
            // The only property should be a collection, hence it should be class:
            if (field == null || !(field.type() instanceof JClass)) {
                continue;
            }

            JClass fieldType = (JClass) field.type();

            // * The property should be a collection
            if (!collectionModelClass.isAssignableFrom(fieldType)) {
                continue;
            }

            List<JClass> fieldParametrisations = fieldType.getTypeParameters();

            // FIXME: All known collections have exactly one parametrisation type.
            assert fieldParametrisations.size() == 1;

            JDefinedClass fieldParametrisationClass = null;
            JDefinedClass fieldParametrisationImpl = null;

            // Parametrisations like "List<String>" or "List<Serialazable>" are not considered.
            // They are substituted as is and do not require moving of classes.
            if (fieldParametrisations.get(0) instanceof JDefinedClass) {
                fieldParametrisationClass = (JDefinedClass) fieldParametrisations.get(0);

                ClassOutline fieldParametrisationClassOutline = interfaceImplementations
                        .get(fieldParametrisationClass.fullName());

                if (fieldParametrisationClassOutline != null) {
                    assert fieldParametrisationClassOutline.ref == fieldParametrisationClass;

                    fieldParametrisationImpl = fieldParametrisationClassOutline.implClass;
                } else {
                    fieldParametrisationImpl = fieldParametrisationClass;
                }
            }

            // We have a candidate class:
            Candidate candidate = new Candidate(candidateClass, classOutline.target, field,
                    fieldParametrisationClass, fieldParametrisationImpl, xmlElementDeclModelClass,
                    xmlSchemaModelClass);
            candidates.add(candidate);

            logger.debug("Found " + candidate);
        }

        return candidates;
    }

    /**
     * Delete all candidate classes together with setter/getter methods and helper methods from
     * <code>ObjectFactory</code>.
     * 
     * @return the number of deletions performed
     */
    private int deleteCandidates(Outline outline, Collection<Candidate> candidates) {
        int deletionCount = 0;

        writeSummary("Deletions:");

        // Visit all candidate classes.
        for (Candidate candidate : candidates) {
            if (!candidate.canBeRemoved()) {
                continue;
            }

            // Get the defined class for candidate class.
            JDefinedClass candidateClass = candidate.getClazz();

            deleteClass(outline, candidateClass);
            deletionCount++;

            for (JDefinedClass objectFactoryClass : candidate.getObjectFactoryClasses()) {
                deletionCount += deleteFactoryMethod(objectFactoryClass, candidate);
            }

            // Replay the same for interface:
            if (candidate.isValueObjectDisabled()) {
                for (Iterator<JClass> iter = candidateClass._implements(); iter.hasNext();) {
                    JClass interfaceClass = iter.next();

                    if (!isHiddenClass(interfaceClass)) {
                        deleteClass(outline, (JDefinedClass) interfaceClass);
                        deletionCount++;
                    }
                }
            }
        }

        return deletionCount;
    }

    //
    // Model factory manipulation helpers.
    //

    /**
     * Rename methods in factory class: {@code createABC() -> createAC()}.
     */
    private void renameFactoryMethod(JDefinedClass factoryClass, String oldMethodName, String newMethodName) {
        for (JMethod method : factoryClass.methods()) {
            String methodName = method.name();

            if (!methodName.contains(oldMethodName)) {
                continue;
            }

            method.name(methodName.replace(oldMethodName, newMethodName));

            writeSummary("\tRenamed " + methodName + " -> " + method.name() + " in " + factoryClass.fullName());
        }
    }

    /**
     * Remove method {@code ObjectFactory} that creates an object of a given {@code clazz}.
     * 
     * @return {@code 1} if such method was successfully located and removed
     */
    private int deleteFactoryMethod(JDefinedClass factoryClass, Candidate candidate) {
        int deletedMethods = 0;

        for (Iterator<JMethod> iter = factoryClass.methods().iterator(); iter.hasNext();) {
            JMethod method = iter.next();

            // Remove the methods:
            // * public T createT() { return new T(); }
            // * public JAXBElement<T> createT(T value) { return new JAXBElement<T>(QNAME, T.class, null, value); }
            // * @XmlElementDecl(..., scope = X.class)
            //   public JAXBElement<T> createT...(T value) { return new JAXBElement<...>(QNAME, T.class, X.class, value); }
            if ((method.type() instanceof JDefinedClass
                    && ((JDefinedClass) method.type()).isAssignableFrom(candidate.getClazz()))
                    || isListedAsParametrisation(candidate.getClazz(), method.type())
                    || candidate.getScopedElementInfos().containsKey(method.name())) {
                writeSummary("\tRemoving factory method [" + method.type().fullName() + "#" + method.name()
                        + "()] from " + factoryClass.fullName());
                iter.remove();

                deletedMethods++;
            }
        }

        return deletedMethods;
    }

    //
    // Model manipulation helpers.
    //

    /**
     * Returns {@code true} if setter/getter with given public name was successfully removed from given class/interface.
     */
    private boolean deleteSettersGetters(JDefinedClass clazz, String fieldPublicName) {
        boolean result = false;

        for (Iterator<JMethod> iter = clazz.methods().iterator(); iter.hasNext();) {
            JMethod m = iter.next();

            if (m.name().equals("set" + fieldPublicName) || m.name().equals("get" + fieldPublicName)) {
                iter.remove();
                result = true;
            }
        }

        return result;
    }

    /**
     * Move the given class to his grandparent (either class or package). The given {@code clazz} should be inner class.
     */
    private void moveClassLevelUp(Outline outline, JDefinedClass clazz) {
        // Modify the container so it now refers the class. Container can be a class or package.
        JDefinedClass parent = (JDefinedClass) clazz.parentContainer();
        JClassContainer grandParent = parent.parentContainer();
        Map<String, JDefinedClass> classes;

        // FIXME: Pending https://java.net/jira/browse/JAXB-957
        if (grandParent.isClass()) {
            // Element class should be added as its container child:
            JDefinedClass grandParentClass = (JDefinedClass) grandParent;

            writeSummary("\tMoving inner class " + clazz.fullName() + " to class " + grandParentClass.fullName());

            classes = getPrivateField(grandParentClass, "classes");
        } else {
            JPackage grandParentPackage = (JPackage) grandParent;

            writeSummary("\tMoving inner class " + clazz.fullName() + " to package " + grandParentPackage.name());

            classes = getPrivateField(grandParentPackage, "classes");

            // In this scenario class should have "static" modifier reset otherwise it won't compile:
            setPrivateField(clazz.mods(), "mods", Integer.valueOf(clazz.mods().getValue() & ~JMod.STATIC));

            for (ClassOutline classOutline : outline.getClasses()) {
                if (classOutline.implClass == clazz) {
                    XSComponent sc = classOutline.target.getSchemaComponent();

                    // FIXME: Inner class is always a local declaration.
                    assert (sc instanceof XSDeclaration && ((XSDeclaration) sc).isLocal());

                    setPrivateField(sc, "anonymous", Boolean.FALSE);

                    break;
                }
            }
        }

        if (classes.containsKey(clazz.name())) {
            writeSummary("\tRenaming class " + clazz.fullName() + " to class " + parent.name() + clazz.name());
            setPrivateField(clazz, "name", parent.name() + clazz.name());
        }

        classes.put(clazz.name(), clazz);

        // Finally modify the class so that it refers back the container:
        setPrivateField(clazz, "outer", grandParent);
    }

    /**
     * Remove the given class from it's parent class or package it is defined in.
     */
    private void deleteClass(Outline outline, JDefinedClass clazz) {
        if (clazz.parentContainer().isClass()) {
            // The candidate class is an inner class. Remove the class from its parent class.
            JDefinedClass parentClass = (JDefinedClass) clazz.parentContainer();

            writeSummary("\tRemoving class " + clazz.fullName() + " from class " + parentClass.fullName());

            for (Iterator<JDefinedClass> iter = parentClass.classes(); iter.hasNext();) {
                if (iter.next().equals(clazz)) {
                    iter.remove();
                    break;
                }
            }
        } else {
            // The candidate class is in a package. Remove the class from the package.
            JPackage parentPackage = (JPackage) clazz.parentContainer();

            writeSummary("\tRemoving class " + clazz.fullName() + " from package " + parentPackage.name());

            parentPackage.remove(clazz);

            // And also remove the class from model.
            for (Iterator<? extends ClassOutline> iter = outline.getClasses().iterator(); iter.hasNext();) {
                ClassOutline classOutline = iter.next();
                if (classOutline.implClass == clazz) {
                    outline.getModel().beans().remove(classOutline.target);
                    Set<Object> packageClasses = getPrivateField(classOutline._package(), "classes");
                    packageClasses.remove(classOutline);
                    iter.remove();
                    break;
                }
            }
        }
    }

    /**
     * For the given annotatable check that all annotations (and all annotations within annotations recursively) do not
     * refer any candidate for removal.
     */
    private void checkAnnotationReference(Map<String, Candidate> candidatesMap, JAnnotatable annotatable) {
        for (JAnnotationUse annotation : annotatable.annotations()) {
            JAnnotationValue annotationMember = getAnnotationMember(annotation, "value");

            if (annotationMember instanceof JAnnotationArrayMember) {
                checkAnnotationReference(candidatesMap, (JAnnotationArrayMember) annotationMember);

                continue;
            }

            JExpression type = getAnnotationMemberExpression(annotation, "type");

            if (type == null) {
                // Can be the case for @XmlElement(name = "publication-reference", namespace = "http://mycompany.org/exchange")
                // or any other annotation without "type" 
                continue;
            }

            Candidate candidate = candidatesMap.get(generableToString(type).replace(".class", ""));

            if (candidate != null) {
                logger.debug("Candidate " + candidate.getClassName()
                        + " is used in XmlElements/XmlElementRef and hence won't be removed.");
                candidate.unmarkForRemoval();
            }
        }
    }
}