alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (ClassInfoImpl.java)

This example Java source code file (ClassInfoImpl.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

annotation, classinfoimpl, duplicateexception, element, hashmap, illegalannotationexception, list, map, propertyinfoimpl, propertyseed, reflection, secondaryannotation, string, util

The ClassInfoImpl.java Java example source code

/*
 * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.xml.internal.bind.v2.model.impl;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.AbstractList;

import javax.xml.bind.annotation.XmlAccessOrder;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorOrder;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAnyAttribute;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlAttachmentRef;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
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.XmlID;
import javax.xml.bind.annotation.XmlIDREF;
import javax.xml.bind.annotation.XmlInlineBinaryData;
import javax.xml.bind.annotation.XmlList;
import javax.xml.bind.annotation.XmlMimeType;
import javax.xml.bind.annotation.XmlMixed;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSchemaType;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.namespace.QName;

import com.sun.istack.internal.FinalArrayList;
import com.sun.xml.internal.bind.annotation.OverrideAnnotationOf;
import com.sun.xml.internal.bind.v2.model.annotation.Locatable;
import com.sun.xml.internal.bind.v2.model.annotation.MethodLocatable;
import com.sun.xml.internal.bind.v2.model.core.ClassInfo;
import com.sun.xml.internal.bind.v2.model.core.Element;
import com.sun.xml.internal.bind.v2.model.core.ID;
import com.sun.xml.internal.bind.v2.model.core.NonElement;
import com.sun.xml.internal.bind.v2.model.core.PropertyInfo;
import com.sun.xml.internal.bind.v2.model.core.PropertyKind;
import com.sun.xml.internal.bind.v2.model.core.ValuePropertyInfo;
import com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationException;
import com.sun.xml.internal.bind.v2.runtime.Location;
import com.sun.xml.internal.bind.v2.util.EditDistance;


/**
 * A part of the {@link ClassInfo} that doesn't depend on a particular
 * reflection library.
 *
 * @author Kohsuke Kawaguchi (kk@kohsuke.org)
 */
public class ClassInfoImpl<T,C,F,M> extends TypeInfoImpl
    implements ClassInfo<T,C>, Element {

    protected final C clazz;

    /**
     * @see #getElementName()
     */
    private final QName elementName;

    /**
     * @see #getTypeName()
     */
    private final QName typeName;

    /**
     * Lazily created.
     *
     * @see #getProperties()
     */
    private FinalArrayList<PropertyInfoImpl properties;

    /**
     * The property order.
     *
     * null if unordered. {@link #DEFAULT_ORDER} if ordered but the order is defaulted
     *
     * @see #isOrdered()
     */
    private /*final*/ String[] propOrder;

    /**
     * Lazily computed.
     *
     * To avoid the cyclic references of the form C1 --base--> C2 --property--> C1.
     */
    private ClassInfoImpl<T,C,F,M> baseClass;

    private boolean baseClassComputed = false;

    private boolean hasSubClasses = false;

    /**
     * If this class has a declared (not inherited) attribute wildcard,  keep the reference
     * to it.
     *
     * This parameter is initialized at the construction time and never change.
     */
    protected /*final*/ PropertySeed<T,C,F,M> attributeWildcard;


    /**
     * @see #getFactoryMethod()
     */
    private M factoryMethod = null;

    ClassInfoImpl(ModelBuilder<T,C,F,M> builder, Locatable upstream, C clazz) {
        super(builder,upstream);
        this.clazz = clazz;
        assert clazz!=null;

        // compute the element name
        elementName = parseElementName(clazz);

        // compute the type name
        XmlType t = reader().getClassAnnotation(XmlType.class,clazz,this);
        typeName = parseTypeName(clazz,t);

        if(t!=null) {
            String[] propOrder = t.propOrder();
            if(propOrder.length==0)
                this.propOrder = null;   // unordered
            else {
                if(propOrder[0].length()==0)
                    this.propOrder = DEFAULT_ORDER;
                else
                    this.propOrder = propOrder;
            }
        } else {
            propOrder = DEFAULT_ORDER;
        }

        // obtain XmlAccessorOrder and  set proporder (XmlAccessorOrder can be defined for whole package)
        // (<xs:all> vs )
        XmlAccessorOrder xao = reader().getPackageAnnotation(XmlAccessorOrder.class, clazz, this);
        if((xao != null) && (xao.value() == XmlAccessOrder.UNDEFINED)) {
            propOrder = null;
        }

        // obtain XmlAccessorOrder and  set proporder (<xs:all> vs )
        xao = reader().getClassAnnotation(XmlAccessorOrder.class, clazz, this);
        if((xao != null) && (xao.value() == XmlAccessOrder.UNDEFINED)) {
            propOrder = null;
        }

        if(nav().isInterface(clazz)) {
            builder.reportError(new IllegalAnnotationException(
                Messages.CANT_HANDLE_INTERFACE.format(nav().getClassName(clazz)), this ));
        }

        // the class must have the default constructor
        if (!hasFactoryConstructor(t)){
            if(!nav().hasDefaultConstructor(clazz)){
                if(nav().isInnerClass(clazz)) {
                    builder.reportError(new IllegalAnnotationException(
                        Messages.CANT_HANDLE_INNER_CLASS.format(nav().getClassName(clazz)), this ));
                } else if (elementName != null) {
                    builder.reportError(new IllegalAnnotationException(
                        Messages.NO_DEFAULT_CONSTRUCTOR.format(nav().getClassName(clazz)), this ));
                }
            }
        }
    }

    public ClassInfoImpl<T,C,F,M> getBaseClass() {
        if (!baseClassComputed) {
            // compute the base class
            C s = nav().getSuperClass(clazz);
            if(s==null || s==nav().asDecl(Object.class)) {
                baseClass = null;
            } else {
                NonElement<T,C> b = builder.getClassInfo(s, true, this);
                if(b instanceof ClassInfoImpl) {
                    baseClass = (ClassInfoImpl<T,C,F,M>) b;
                    baseClass.hasSubClasses = true;
                } else {
                    baseClass = null;
                }
            }
            baseClassComputed = true;
        }
        return baseClass;
    }

    /**
     * {@inheritDoc}
     *
     * The substitution hierarchy is the same as the inheritance hierarchy.
     */
    public final Element<T,C> getSubstitutionHead() {
        ClassInfoImpl<T,C,F,M> c = getBaseClass();
        while(c!=null && !c.isElement())
            c = c.getBaseClass();
        return c;
    }

    public final C getClazz() {
        return clazz;
    }

    /**
     * When a bean binds to an element, it's always through {@link XmlRootElement},
     * so this method always return null.
     *
     * @deprecated
     *      you shouldn't be invoking this method on {@link ClassInfoImpl}.
     */
    public ClassInfoImpl<T,C,F,M> getScope() {
        return null;
    }

    public final T getType() {
        return nav().use(clazz);
    }

    /**
     * A {@link ClassInfo} can be referenced by {@link XmlIDREF} if
     * it has an ID property.
     */
    public boolean canBeReferencedByIDREF() {
        for (PropertyInfo<T,C> p : getProperties()) {
            if(p.id()== ID.ID)
                return true;
        }
        ClassInfoImpl<T,C,F,M> base = getBaseClass();
        if(base!=null)
            return base.canBeReferencedByIDREF();
        else
            return false;
    }

    public final String getName() {
        return nav().getClassName(clazz);
    }

    public <A extends Annotation> A readAnnotation(Class a) {
        return reader().getClassAnnotation(a,clazz,this);
    }

    public Element<T,C> asElement() {
        if(isElement())
            return this;
        else
            return null;
    }

    public List<? extends PropertyInfo getProperties() {
        if(properties!=null)    return properties;

        // check the access type first
        XmlAccessType at = getAccessType();

        properties = new FinalArrayList<PropertyInfoImpl();

        findFieldProperties(clazz,at);

        findGetterSetterProperties(at);

        if(propOrder==DEFAULT_ORDER || propOrder==null) {
            XmlAccessOrder ao = getAccessorOrder();
            if(ao==XmlAccessOrder.ALPHABETICAL)
                Collections.sort(properties);
        } else {
            //sort them as specified
            PropertySorter sorter = new PropertySorter();
            for (PropertyInfoImpl p : properties) {
                sorter.checkedGet(p);   // have it check for errors
            }
            Collections.sort(properties,sorter);
            sorter.checkUnusedProperties();
        }

        {// additional error checks
            PropertyInfoImpl vp=null; // existing value property
            PropertyInfoImpl ep=null; // existing element property

            for (PropertyInfoImpl p : properties) {
                switch(p.kind()) {
                case ELEMENT:
                case REFERENCE:
                case MAP:
                    ep = p;
                    break;
                case VALUE:
                    if(vp!=null) {
                        // can't have multiple value properties.
                        builder.reportError(new IllegalAnnotationException(
                            Messages.MULTIPLE_VALUE_PROPERTY.format(),
                            vp, p ));
                    }
                    if(getBaseClass()!=null) {
                        builder.reportError(new IllegalAnnotationException(
                            Messages.XMLVALUE_IN_DERIVED_TYPE.format(), p ));
                    }
                    vp = p;
                    break;
                case ATTRIBUTE:
                    break;  // noop
                default:
                    assert false;
                }
            }

            if(ep!=null && vp!=null) {
                // can't have element and value property at the same time
                builder.reportError(new IllegalAnnotationException(
                    Messages.ELEMENT_AND_VALUE_PROPERTY.format(),
                    vp, ep
                ));
            }
        }

        return properties;
    }

    private void findFieldProperties(C c, XmlAccessType at) {

        // always find properties from the super class first
        C sc = nav().getSuperClass(c);
        if (shouldRecurseSuperClass(sc)) {
            findFieldProperties(sc,at);
        }

        for( F f : nav().getDeclaredFields(c) ) {
            Annotation[] annotations = reader().getAllFieldAnnotations(f,this);
            boolean isDummy = reader().hasFieldAnnotation(OverrideAnnotationOf.class, f);

            if( nav().isTransient(f) ) {
                // it's an error for transient field to have any binding annotation
                if(hasJAXBAnnotation(annotations))
                    builder.reportError(new IllegalAnnotationException(
                        Messages.TRANSIENT_FIELD_NOT_BINDABLE.format(nav().getFieldName(f)),
                            getSomeJAXBAnnotation(annotations)));
            } else
            if( nav().isStaticField(f) ) {
                // static fields are bound only when there's explicit annotation.
                if(hasJAXBAnnotation(annotations))
                    addProperty(createFieldSeed(f),annotations, false);
            } else {
                if(at==XmlAccessType.FIELD
                ||(at==XmlAccessType.PUBLIC_MEMBER && nav().isPublicField(f))
                || hasJAXBAnnotation(annotations)) {
                    if (isDummy) {
                        ClassInfo<T, C> top = getBaseClass();
                        while ((top != null) && (top.getProperty("content") == null)) {
                            top = top.getBaseClass();
                        }
                        DummyPropertyInfo prop = (DummyPropertyInfo) top.getProperty("content");
                        PropertySeed seed = createFieldSeed(f);
                        ((DummyPropertyInfo)prop).addType(createReferenceProperty(seed));
                    } else {
                        addProperty(createFieldSeed(f), annotations, false);
                    }
                }
                checkFieldXmlLocation(f);
            }
        }
    }

    public final boolean hasValueProperty() {
        ClassInfoImpl<T, C, F, M> bc = getBaseClass();
        if(bc!=null && bc.hasValueProperty())
            return true;

        for (PropertyInfo p : getProperties()) {
            if (p instanceof ValuePropertyInfo) return true;
        }

        return false;
        }

    public PropertyInfo<T,C> getProperty(String name) {
        for( PropertyInfo<T,C> p: getProperties() ) {
            if(p.getName().equals(name))
                return p;
        }
        return null;
    }

    /**
     * This hook is used by {@link RuntimeClassInfoImpl} to look for {@link XmlLocation}.
     */
    protected void checkFieldXmlLocation(F f) {
    }

    /**
     * Gets an annotation that are allowed on both class and type.
     */
    private <T extends Annotation> T getClassOrPackageAnnotation(Class type) {
        T t = reader().getClassAnnotation(type,clazz,this);
        if(t!=null)
            return t;
        // defaults to the package level
        return reader().getPackageAnnotation(type,clazz,this);
    }

    /**
     * Computes the {@link XmlAccessType} on this class by looking at {@link XmlAccessorType}
     * annotations.
     */
    private XmlAccessType getAccessType() {
        XmlAccessorType xat = getClassOrPackageAnnotation(XmlAccessorType.class);
        if(xat!=null)
            return xat.value();
        else
            return XmlAccessType.PUBLIC_MEMBER;
    }

    /**
     * Gets the accessor order for this class by consulting {@link XmlAccessorOrder}.
     */
    private XmlAccessOrder getAccessorOrder() {
        XmlAccessorOrder xao = getClassOrPackageAnnotation(XmlAccessorOrder.class);
        if(xao!=null)
            return xao.value();
        else
            return XmlAccessOrder.UNDEFINED;
    }

    /**
     * Compares orders among {@link PropertyInfoImpl} according to {@link ClassInfoImpl#propOrder}.
     *
     * <p>
     * extends {@link HashMap} to save memory.
     */
    private final class PropertySorter extends HashMap<String,Integer> implements Comparator {
        /**
         * Mark property names that are used, so that we can report unused property names in the propOrder array.
         */
        PropertyInfoImpl[] used = new PropertyInfoImpl[propOrder.length];

        /**
         * If any name collides, it will be added to this set.
         * This is used to avoid repeating the same error message.
         */
        private Set<String> collidedNames;

        PropertySorter() {
            super(propOrder.length);
            for( String name : propOrder )
                if(put(name,size())!=null) {
                    // two properties with the same name
                    builder.reportError(new IllegalAnnotationException(
                        Messages.DUPLICATE_ENTRY_IN_PROP_ORDER.format(name),ClassInfoImpl.this));
                }
        }

        public int compare(PropertyInfoImpl o1, PropertyInfoImpl o2) {
            int lhs = checkedGet(o1);
            int rhs = checkedGet(o2);

            return lhs-rhs;
        }

        private int checkedGet(PropertyInfoImpl p) {
            Integer i = get(p.getName());
            if(i==null) {
                // missing
                if (p.kind().isOrdered)
                    builder.reportError(new IllegalAnnotationException(
                        Messages.PROPERTY_MISSING_FROM_ORDER.format(p.getName()),p));

                // give it an order to recover from an error
                i = size();
                put(p.getName(),i);
            }

            // mark the used field
            int ii = i;
            if(ii<used.length) {
                if(used[ii]!=null && used[ii]!=p) {
                    if(collidedNames==null) collidedNames = new HashSet<String>();

                    if(collidedNames.add(p.getName()))
                        // report the error only on the first time
                        builder.reportError(new IllegalAnnotationException(
                            Messages.DUPLICATE_PROPERTIES.format(p.getName()),p,used[ii]));
                }
                used[ii] = p;
            }

            return i;
        }

        /**
         * Report errors for unused propOrder entries.
         */
        public void checkUnusedProperties() {
            for( int i=0; i<used.length; i++ )
                if(used[i]==null) {
                    String unusedName = propOrder[i];
                    String nearest = EditDistance.findNearest(unusedName, new AbstractList<String>() {
                        public String get(int index) {
                            return properties.get(index).getName();
                        }

                        public int size() {
                            return properties.size();
                        }
                    });
                    boolean isOverriding = (i > (properties.size()-1)) ? false : properties.get(i).hasAnnotation(OverrideAnnotationOf.class);
                    if (!isOverriding) {
                        builder.reportError(new IllegalAnnotationException(
                        Messages.PROPERTY_ORDER_CONTAINS_UNUSED_ENTRY.format(unusedName,nearest),ClassInfoImpl.this));
                    }
                }
        }
    }

    public boolean hasProperties() {
        return !properties.isEmpty();
    }


    /**
     * Picks the first non-null argument, or null if all arguments are null.
     */
    private static <T> T pickOne( T... args ) {
        for( T arg : args )
            if(arg!=null)
                return arg;
        return null;
    }

    private static <T> List makeSet( T... args ) {
        List<T> l = new FinalArrayList();
        for( T arg : args )
            if(arg!=null)   l.add(arg);
        return l;
    }

    private static final class ConflictException extends Exception {
        final List<Annotation> annotations;

        public ConflictException(List<Annotation> one) {
            this.annotations = one;
        }
    }

    private static final class DuplicateException extends Exception {
        final Annotation a1,a2;
        public DuplicateException(Annotation a1, Annotation a2) {
            this.a1 = a1;
            this.a2 = a2;
        }
    }

    /**
     * Represents 6 groups of secondary annotations
     */
    private static enum SecondaryAnnotation {
        JAVA_TYPE       (0x01, XmlJavaTypeAdapter.class),
        ID_IDREF        (0x02, XmlID.class, XmlIDREF.class),
        BINARY          (0x04, XmlInlineBinaryData.class, XmlMimeType.class, XmlAttachmentRef.class),
        ELEMENT_WRAPPER (0x08, XmlElementWrapper.class),
        LIST            (0x10, XmlList.class),
        SCHEMA_TYPE     (0x20, XmlSchemaType.class);

        /**
         * Each constant gets an unique bit mask so that the presence/absence
         * of them can be represented in a single byte.
         */
        final int bitMask;
        /**
         * List of annotations that belong to this member.
         */
        final Class<? extends Annotation>[] members;

        SecondaryAnnotation(int bitMask, Class<? extends Annotation>... members) {
            this.bitMask = bitMask;
            this.members = members;
        }
    }

    private static final SecondaryAnnotation[] SECONDARY_ANNOTATIONS = SecondaryAnnotation.values();

    /**
     * Represents 7 groups of properties.
     *
     * Each instance is also responsible for rejecting annotations
     * that are not allowed on that kind.
     */
    private static enum PropertyGroup {
        TRANSIENT       (false,false,false,false,false,false),
        ANY_ATTRIBUTE   (true, false,false,false,false,false),
        ATTRIBUTE       (true, true, true, false,true, true ),
        VALUE           (true, true, true, false,true, true ),
        ELEMENT         (true, true, true, true, true, true ),
        ELEMENT_REF     (true, false,false,true, false,false),
        MAP             (false,false,false,true, false,false);

        /**
         * Bit mask that represents secondary annotations that are allowed on this group.
         *
         * T = not allowed, F = allowed
         */
        final int allowedsecondaryAnnotations;

        PropertyGroup(boolean... bits) {
            int mask = 0;
            assert bits.length==SECONDARY_ANNOTATIONS.length;
            for( int i=0; i<bits.length; i++ ) {
                if(bits[i])
                    mask |= SECONDARY_ANNOTATIONS[i].bitMask;
            }
            allowedsecondaryAnnotations = ~mask;
        }

        boolean allows(SecondaryAnnotation a) {
            return (allowedsecondaryAnnotations&a.bitMask)==0;
        }
    }

    private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];

    /**
     * All the annotations in JAXB to their internal index.
     */
    private static final HashMap<Class,Integer> ANNOTATION_NUMBER_MAP = new HashMap();
    static {
        Class[] annotations = {
            XmlTransient.class,     // 0
            XmlAnyAttribute.class,  // 1
            XmlAttribute.class,     // 2
            XmlValue.class,         // 3
            XmlElement.class,       // 4
            XmlElements.class,      // 5
            XmlElementRef.class,    // 6
            XmlElementRefs.class,   // 7
            XmlAnyElement.class,    // 8
            XmlMixed.class,         // 9
            OverrideAnnotationOf.class,// 10
        };

        HashMap<Class,Integer> m = ANNOTATION_NUMBER_MAP;

        // characterizing annotations
        for( Class c : annotations )
            m.put(c, m.size() );

        // secondary annotations
        int index = 20;
        for( SecondaryAnnotation sa : SECONDARY_ANNOTATIONS ) {
            for( Class member : sa.members )
                m.put(member,index);
            index++;
        }
    }

    private void checkConflict(Annotation a, Annotation b) throws DuplicateException {
        assert b!=null;
        if(a!=null)
            throw new DuplicateException(a,b);
    }

    /**
     * Called only from {@link #getProperties()}.
     *
     * <p>
     * This is where we decide the type of the property and checks for annotations
     * that are not allowed.
     *
     * @param annotations
     *      all annotations on this property. It's the same as
     *      {@code seed.readAllAnnotation()}, but taken as a parameter
     *      because the caller should know it already.
     */
    private void addProperty( PropertySeed<T,C,F,M> seed, Annotation[] annotations, boolean dummy ) {
        // since typically there's a very few annotations on a method,
        // this runs faster than checking for each annotation via readAnnotation(A)


        // characterizing annotations. these annotations (or lack thereof) decides
        // the kind of the property it goes to.
        // I wish I could use an array...
        XmlTransient t = null;
        XmlAnyAttribute aa = null;
        XmlAttribute a = null;
        XmlValue v = null;
        XmlElement e1 = null;
        XmlElements e2 = null;
        XmlElementRef r1 = null;
        XmlElementRefs r2 = null;
        XmlAnyElement xae = null;
        XmlMixed mx = null;
        OverrideAnnotationOf ov = null;

        // encountered secondary annotations are accumulated into a bit mask
        int secondaryAnnotations = 0;

        try {
            for( Annotation ann : annotations ) {
                Integer index = ANNOTATION_NUMBER_MAP.get(ann.annotationType());
                if(index==null) continue;
                switch(index) {
                case 0:     checkConflict(t  ,ann); t   = (XmlTransient) ann; break;
                case 1:     checkConflict(aa ,ann); aa  = (XmlAnyAttribute) ann; break;
                case 2:     checkConflict(a  ,ann); a   = (XmlAttribute) ann; break;
                case 3:     checkConflict(v  ,ann); v   = (XmlValue) ann; break;
                case 4:     checkConflict(e1 ,ann); e1  = (XmlElement) ann; break;
                case 5:     checkConflict(e2 ,ann); e2  = (XmlElements) ann; break;
                case 6:     checkConflict(r1 ,ann); r1  = (XmlElementRef) ann; break;
                case 7:     checkConflict(r2 ,ann); r2  = (XmlElementRefs) ann; break;
                case 8:     checkConflict(xae,ann); xae = (XmlAnyElement) ann; break;
                case 9:     checkConflict(mx, ann); mx  = (XmlMixed) ann; break;
                case 10:    checkConflict(ov, ann); ov  = (OverrideAnnotationOf) ann; break;
                default:
                    // secondary annotations
                    secondaryAnnotations |= (1<<(index-20));
                    break;
                }
            }

            // determine the group kind, and also count the numbers, since
            // characterizing annotations are mutually exclusive.
            PropertyGroup group = null;
            int groupCount = 0;

            if(t!=null) {
                group = PropertyGroup.TRANSIENT;
                groupCount++;
            }
            if(aa!=null) {
                group = PropertyGroup.ANY_ATTRIBUTE;
                groupCount++;
            }
            if(a!=null) {
                group = PropertyGroup.ATTRIBUTE;
                groupCount++;
            }
            if(v!=null) {
                group = PropertyGroup.VALUE;
                groupCount++;
            }
            if(e1!=null || e2!=null) {
                group = PropertyGroup.ELEMENT;
                groupCount++;
            }
            if(r1!=null || r2!=null || xae!=null || mx!=null || ov != null) {
                group = PropertyGroup.ELEMENT_REF;
                groupCount++;
            }

            if(groupCount>1) {
                // collision between groups
                List<Annotation> err = makeSet(t,aa,a,v,pickOne(e1,e2),pickOne(r1,r2,xae));
                throw new ConflictException(err);
            }

            if(group==null) {
                // if no characterizing annotation was found, it's either element or map
                // sniff the signature and then decide.
                assert groupCount==0;

                // UGLY: the presence of XmlJavaTypeAdapter makes it an element property. ARGH.
                if(nav().isSubClassOf( seed.getRawType(), nav().ref(Map.class) )
                && !seed.hasAnnotation(XmlJavaTypeAdapter.class))
                    group = PropertyGroup.MAP;
                else
                    group = PropertyGroup.ELEMENT;
            } else if (group.equals(PropertyGroup.ELEMENT)) { // see issue 791 - make sure @XmlElement annotated map property is mapped to map
                if (nav().isSubClassOf( seed.getRawType(), nav().ref(Map.class)) && !seed.hasAnnotation(XmlJavaTypeAdapter.class)) {
                    group = PropertyGroup.MAP;
                }
            }

            // group determined by now
            // make sure that there are no prohibited secondary annotations
            if( (secondaryAnnotations&group.allowedsecondaryAnnotations)!=0 ) {
                // uh oh. find the offending annotation
                for( SecondaryAnnotation sa : SECONDARY_ANNOTATIONS ) {
                    if(group.allows(sa))
                        continue;
                    for( Class<? extends Annotation> m : sa.members ) {
                        Annotation offender = seed.readAnnotation(m);
                        if(offender!=null) {
                            // found it
                            builder.reportError(new IllegalAnnotationException(
                                Messages.ANNOTATION_NOT_ALLOWED.format(m.getSimpleName()),offender));
                            return;
                        }
                    }
                }
                // there must have been an offender
                assert false;
            }

            // actually create annotations
            switch(group) {
            case TRANSIENT:
                return;
            case ANY_ATTRIBUTE:
                // an attribute wildcard property
                if(attributeWildcard!=null) {
                    builder.reportError(new IllegalAnnotationException(
                        Messages.TWO_ATTRIBUTE_WILDCARDS.format(
                            nav().getClassName(getClazz())),aa,attributeWildcard));
                    return; // recover by ignore
                }
                attributeWildcard = seed;

                if(inheritsAttributeWildcard()) {
                    builder.reportError(new IllegalAnnotationException(
                        Messages.SUPER_CLASS_HAS_WILDCARD.format(),
                            aa,getInheritedAttributeWildcard()));
                    return;
                }

                // check the signature and make sure it's assignable to Map
                if(!nav().isSubClassOf(seed.getRawType(),nav().ref(Map.class))) {
                    builder.reportError(new IllegalAnnotationException(
                        Messages.INVALID_ATTRIBUTE_WILDCARD_TYPE.format(nav().getTypeName(seed.getRawType())),
                            aa,getInheritedAttributeWildcard()));
                    return;
                }


                return;
            case ATTRIBUTE:
                properties.add(createAttributeProperty(seed));
                return;
            case VALUE:
                properties.add(createValueProperty(seed));
                return;
            case ELEMENT:
                properties.add(createElementProperty(seed));
                return;
            case ELEMENT_REF:
                properties.add(createReferenceProperty(seed));
                return;
            case MAP:
                properties.add(createMapProperty(seed));
                return;
            default:
                assert false;
            }
        } catch( ConflictException x ) {
            // report a conflicting annotation
            List<Annotation> err = x.annotations;

            builder.reportError(new IllegalAnnotationException(
                Messages.MUTUALLY_EXCLUSIVE_ANNOTATIONS.format(
                    nav().getClassName(getClazz())+'#'+seed.getName(),
                    err.get(0).annotationType().getName(), err.get(1).annotationType().getName()),
                    err.get(0), err.get(1) ));

            // recover by ignoring this property
        } catch( DuplicateException e ) {
            // both are present
            builder.reportError(new IllegalAnnotationException(
                Messages.DUPLICATE_ANNOTATIONS.format(e.a1.annotationType().getName()),
                e.a1, e.a2 ));
            // recover by ignoring this property

        }
    }

    protected ReferencePropertyInfoImpl<T,C,F,M> createReferenceProperty(PropertySeed seed) {
        return new ReferencePropertyInfoImpl<T,C,F,M>(this,seed);
    }

    protected AttributePropertyInfoImpl<T,C,F,M> createAttributeProperty(PropertySeed seed) {
        return new AttributePropertyInfoImpl<T,C,F,M>(this,seed);
    }

    protected ValuePropertyInfoImpl<T,C,F,M> createValueProperty(PropertySeed seed) {
        return new ValuePropertyInfoImpl<T,C,F,M>(this,seed);
    }

    protected ElementPropertyInfoImpl<T,C,F,M> createElementProperty(PropertySeed seed) {
        return new ElementPropertyInfoImpl<T,C,F,M>(this,seed);
    }

    protected MapPropertyInfoImpl<T,C,F,M> createMapProperty(PropertySeed seed) {
        return new MapPropertyInfoImpl<T,C,F,M>(this,seed);
    }


    /**
     * Adds properties that consists of accessors.
     */
    private void findGetterSetterProperties(XmlAccessType at) {
        // in the first step we accumulate getters and setters
        // into this map keyed by the property name.
        Map<String,M> getters = new LinkedHashMap();
        Map<String,M> setters = new LinkedHashMap();

        C c = clazz;
        do {
            collectGetterSetters(clazz, getters, setters);

            // take super classes into account if they have @XmlTransient
            c = nav().getSuperClass(c);
        } while(shouldRecurseSuperClass(c));


        // compute the intersection
        Set<String> complete = new TreeSet(getters.keySet());
        complete.retainAll(setters.keySet());

        resurrect(getters, complete);
        resurrect(setters, complete);

        // then look for read/write properties.
        for (String name : complete) {
            M getter = getters.get(name);
            M setter = setters.get(name);

            Annotation[] ga = getter!=null ? reader().getAllMethodAnnotations(getter,new MethodLocatable<M>(this,getter,nav())) : EMPTY_ANNOTATIONS;
            Annotation[] sa = setter!=null ? reader().getAllMethodAnnotations(setter,new MethodLocatable<M>(this,setter,nav())) : EMPTY_ANNOTATIONS;

            boolean hasAnnotation = hasJAXBAnnotation(ga) || hasJAXBAnnotation(sa);
            boolean isOverriding = false;
            if(!hasAnnotation) {
                // checking if the method is overriding others isn't free,
                // so we don't compute it if it's not necessary.
                isOverriding = (getter!=null && nav().isOverriding(getter,c))
                            && (setter!=null && nav().isOverriding(setter,c));
            }

            if((at==XmlAccessType.PROPERTY && !isOverriding)
                || (at==XmlAccessType.PUBLIC_MEMBER && isConsideredPublic(getter) && isConsideredPublic(setter) && !isOverriding)
            || hasAnnotation) {
                // make sure that the type is consistent
                if(getter!=null && setter!=null
                && !nav().isSameType(nav().getReturnType(getter), nav().getMethodParameters(setter)[0])) {
                    // inconsistent
                    builder.reportError(new IllegalAnnotationException(
                        Messages.GETTER_SETTER_INCOMPATIBLE_TYPE.format(
                            nav().getTypeName(nav().getReturnType(getter)),
                            nav().getTypeName(nav().getMethodParameters(setter)[0])
                        ),
                        new MethodLocatable<M>( this, getter, nav()),
                        new MethodLocatable<M>( this, setter, nav())));
                    continue;
                }

                // merge annotations from two list
                Annotation[] r;
                if(ga.length==0) {
                    r = sa;
                } else
                if(sa.length==0) {
                    r = ga;
                } else {
                    r = new Annotation[ga.length+sa.length];
                    System.arraycopy(ga,0,r,0,ga.length);
                    System.arraycopy(sa,0,r,ga.length,sa.length);
                }

                addProperty(createAccessorSeed(getter, setter), r, false);
            }
        }
        // done with complete pairs
        getters.keySet().removeAll(complete);
        setters.keySet().removeAll(complete);

        // TODO: think about
        // class Foo {
        //   int getFoo();
        // }
        // class Bar extends Foo {
        //   void setFoo(int x);
        // }
        // and how it will be XML-ized.
    }

    private void collectGetterSetters(C c, Map<String,M> getters, Map setters) {
        // take super classes into account if they have @XmlTransient.
        // always visit them first so that
        //   1) order is right
        //   2) overriden properties are handled accordingly
        C sc = nav().getSuperClass(c);
        if(shouldRecurseSuperClass(sc))
            collectGetterSetters(sc,getters,setters);

        Collection<? extends M> methods = nav().getDeclaredMethods(c);
        Map<String,List allSetters = new LinkedHashMap>();
        for( M method : methods ) {
            boolean used = false;   // if this method is added to getters or setters

            if(nav().isBridgeMethod(method))
                continue;   // ignore

            String name = nav().getMethodName(method);
            int arity = nav().getMethodParameters(method).length;

            if(nav().isStaticMethod(method)) {
                ensureNoAnnotation(method);
                continue;
            }

            // is this a get method?
            String propName = getPropertyNameFromGetMethod(name);
            if(propName!=null && arity==0) {
                    getters.put(propName,method);
                used = true;
            }

            // is this a set method?
            propName = getPropertyNameFromSetMethod(name);
            if(propName!=null && arity==1) {
                    List<M> propSetters = allSetters.get(propName);
                    if(null == propSetters){
                        propSetters = new ArrayList<M>();
                        allSetters.put(propName, propSetters);
                    }
                    propSetters.add(method);
                used = true; // used check performed later
            }

            if(!used)
                ensureNoAnnotation(method);
        }

        // Match getter with setters by comparing getter return type to setter param
        for (Map.Entry<String,M> entry : getters.entrySet()) {
            String propName = entry.getKey();
            M getter = entry.getValue();
            List<M> propSetters = allSetters.remove(propName);
            if (null == propSetters) {
                //no matching setter
                continue;
            }
            T getterType = nav().getReturnType(getter);
            for (M setter : propSetters) {
                T setterType = nav().getMethodParameters(setter)[0];
                if (nav().isSameType(setterType, getterType)) {
                    setters.put(propName, setter);
                    break;
                }
            }
        }

        // also allow set-only properties
        for (Map.Entry<String,List e : allSetters.entrySet()) {
            setters.put(e.getKey(),e.getValue().get(0));
        }
    }

    /**
     * Checks if the properties in this given super class should be aggregated into this class.
     */
    private boolean shouldRecurseSuperClass(C sc) {
        return sc!=null
            && (builder.isReplaced(sc) || reader().hasClassAnnotation(sc, XmlTransient.class));
    }

    /**
     * Returns true if the method is considered 'public'.
     */
    private boolean isConsideredPublic(M m) {
        return m ==null || nav().isPublicMethod(m);
    }

    /**
     * If the method has an explicit annotation, allow it to participate
     * to the processing even if it lacks the setter or the getter.
     */
    private void resurrect(Map<String, M> methods, Set complete) {
        for (Map.Entry<String, M> e : methods.entrySet()) {
            if(complete.contains(e.getKey()))
                continue;
            if(hasJAXBAnnotation(reader().getAllMethodAnnotations(e.getValue(),this)))
                complete.add(e.getKey());
        }
    }

    /**
     * Makes sure that the method doesn't have any annotation, if it does,
     * report it as an error
     */
    private void ensureNoAnnotation(M method) {
        Annotation[] annotations = reader().getAllMethodAnnotations(method,this);
        for( Annotation a : annotations ) {
            if(isJAXBAnnotation(a)) {
                builder.reportError(new IllegalAnnotationException(
                    Messages.ANNOTATION_ON_WRONG_METHOD.format(),
                    a));
                return;
            }
        }
    }

    /**
     * Returns true if a given annotation is a JAXB annotation.
     */
    private static boolean isJAXBAnnotation(Annotation a) {
        return ANNOTATION_NUMBER_MAP.containsKey(a.annotationType());
    }

    /**
     * Returns true if the array contains a JAXB annotation.
     */
    private static boolean hasJAXBAnnotation(Annotation[] annotations) {
        return getSomeJAXBAnnotation(annotations)!=null;
    }

    private static Annotation getSomeJAXBAnnotation(Annotation[] annotations) {
        for( Annotation a : annotations )
            if(isJAXBAnnotation(a))
                return a;
        return null;
    }


    /**
     * Returns "Foo" from "getFoo" or "isFoo".
     *
     * @return null
     *      if the method name doesn't look like a getter.
     */
    private static String getPropertyNameFromGetMethod(String name) {
        if(name.startsWith("get") && name.length()>3)
            return name.substring(3);
        if(name.startsWith("is") && name.length()>2)
            return name.substring(2);
        return null;
    }

    /**
     * Returns "Foo" from "setFoo".
     *
     * @return null
     *      if the method name doesn't look like a setter.
     */
    private static String getPropertyNameFromSetMethod(String name) {
        if(name.startsWith("set") && name.length()>3)
            return name.substring(3);
        return null;
    }

    /**
     * Creates a new {@link FieldPropertySeed} object.
     *
     * <p>
     * Derived class can override this method to create a sub-class.
     */
    protected PropertySeed<T,C,F,M> createFieldSeed(F f) {
        return new FieldPropertySeed<T,C,F,M>(this, f);
    }

    /**
     * Creates a new {@link GetterSetterPropertySeed} object.
     */
    protected PropertySeed<T,C,F,M> createAccessorSeed(M getter, M setter) {
        return new GetterSetterPropertySeed<T,C,F,M>(this, getter,setter);
    }

    public final boolean isElement() {
        return elementName!=null;
    }

    public boolean isAbstract() {
        return nav().isAbstract(clazz);
    }

    public boolean isOrdered() {
        return propOrder!=null;
    }

    public final boolean isFinal() {
        return nav().isFinal(clazz);
    }

    public final boolean hasSubClasses() {
        return hasSubClasses;
    }

    public final boolean hasAttributeWildcard() {
        return declaresAttributeWildcard() || inheritsAttributeWildcard();
    }

    public final boolean inheritsAttributeWildcard() {
        return getInheritedAttributeWildcard()!=null;
    }

    public final boolean declaresAttributeWildcard() {
        return attributeWildcard!=null;
    }

    /**
     * Gets the {@link PropertySeed} object for the inherited attribute wildcard.
     */
    private PropertySeed<T,C,F,M> getInheritedAttributeWildcard() {
        for( ClassInfoImpl<T,C,F,M> c=getBaseClass(); c!=null; c=c.getBaseClass() )
            if(c.attributeWildcard!=null)
                return c.attributeWildcard;
        return null;
    }

    public final QName getElementName() {
        return elementName;
    }

    public final QName getTypeName() {
        return typeName;
    }

    public final boolean isSimpleType() {
        List<? extends PropertyInfo> props = getProperties();
        if(props.size()!=1)     return false;
        return props.get(0).kind()==PropertyKind.VALUE;
    }

    /**
     * Called after all the {@link TypeInfo}s are collected into the {@link #owner}.
     */
    @Override
    /*package*/ void link() {
        getProperties();    // make sure properties!=null

        // property name collision cehck
        Map<String,PropertyInfoImpl> names = new HashMap();
        for( PropertyInfoImpl<T,C,F,M> p : properties ) {
            p.link();
            PropertyInfoImpl old = names.put(p.getName(),p);
            if(old!=null) {
                builder.reportError(new IllegalAnnotationException(
                    Messages.PROPERTY_COLLISION.format(p.getName()),
                    p, old ));
            }
        }
        super.link();
    }

    public Location getLocation() {
        return nav().getClassLocation(clazz);
    }

    /**
     *  XmlType allows specification of factoryClass and
     *  factoryMethod.  There are to be used if no default
     *  constructor is found.
     *
     * @return
     *      true if the factory method was found. False if not.
     */
    private  boolean hasFactoryConstructor(XmlType t){
        if (t == null) return false;

        String method = t.factoryMethod();
        T fClass = reader().getClassValue(t, "factoryClass");
        if (method.length() > 0){
            if(nav().isSameType(fClass, nav().ref(XmlType.DEFAULT.class))){
                fClass = nav().use(clazz);
            }
            for(M m: nav().getDeclaredMethods(nav().asDecl(fClass))){
                //- Find the zero-arg public static method with the required return type
                if (nav().getMethodName(m).equals(method) &&
                    nav().isSameType(nav().getReturnType(m), nav().use(clazz)) &&
                    nav().getMethodParameters(m).length == 0 &&
                    nav().isStaticMethod(m)){
                    factoryMethod = m;
                    break;
                }
            }
            if (factoryMethod == null){
                builder.reportError(new IllegalAnnotationException(
                Messages.NO_FACTORY_METHOD.format(nav().getClassName(nav().asDecl(fClass)), method), this ));
            }
        } else if(!nav().isSameType(fClass, nav().ref(XmlType.DEFAULT.class))){
            builder.reportError(new IllegalAnnotationException(
                Messages.FACTORY_CLASS_NEEDS_FACTORY_METHOD.format(nav().getClassName(nav().asDecl(fClass))), this ));
        }
        return factoryMethod != null;
    }

    public Method getFactoryMethod(){
        return (Method) factoryMethod;
    }

    @Override
    public String toString() {
        return "ClassInfo("+clazz+')';
    }

    private static final String[] DEFAULT_ORDER = new String[0];
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.