public class CollectionProperty<I,P> extends Property<I,P> {
	public CollectionProperty(final CollectionPropertyInfo<I, P> info, final I owner) {
		super(info, owner);
	}

	@Override
	public CollectionPropertyInfo<I, P> getInfo() {
		return (CollectionPropertyInfo<I, P>)super.getInfo();
	}

	@Override
	public List<P> get() {
		return getInfo().get(getOwner());
	}

	public void set(final List<P> values) {
		getInfo().set(getOwner(), values);
	}
} public abstract class CollectionPropertyInfo<I,P> extends PropertyInfo<I,P> {
	protected CollectionPropertyInfo(final String propertyName, final Class<I> declaringClass, final Class<P> declaredType, final boolean collection, final P defaultValue, final QName schemaName, final QName schemaType, final boolean attribute) {
		super(propertyName, declaringClass, declaredType, collection, defaultValue, schemaName, schemaType, attribute);
	}

	@Override
	public abstract List<P> get(final I instance);

	public abstract void set(final I instance, final List<P> values);

} public class IndirectCollectionProperty<I,P> extends Property<I,P> {
	public IndirectCollectionProperty(final PropertyInfo<I, P> info, final I owner) {
		super(info, owner);
	}

	@Override
	public IndirectCollectionPropertyInfo<I, P> getInfo() {
		return (IndirectCollectionPropertyInfo<I, P>)super.getInfo();
	}

	@Override
	public List<JAXBElement<? extends P>> get() {
		return getInfo().get(getOwner());
	}

	public void set(final List<JAXBElement<? extends P>> values) {
		getInfo().set(getOwner(), values);
	}
} public abstract class IndirectCollectionPropertyInfo<I,P> extends PropertyInfo<I,P> {
	protected IndirectCollectionPropertyInfo(final String propertyName, final Class<I> declaringClass, final Class<P> declaredType, final boolean collection, final P defaultValue, final QName schemaName, final QName schemaType, final boolean attribute) {
		super(propertyName, declaringClass, declaredType, collection, defaultValue, schemaName, schemaType, attribute);
	}

	@Override
	public abstract List<JAXBElement<? extends P>> get(final I i) ;

	public abstract void set(final I instance, final List<JAXBElement<? extends P>> values);
} public class IndirectPrimitiveCollectionProperty<I,P> extends Property<I,P> {
	public IndirectPrimitiveCollectionProperty(final PropertyInfo<I, P> info, final I owner) {
			super(info, owner);
		}

		@Override
		public IndirectPrimitiveCollectionPropertyInfo<I, P> getInfo() {
			return (IndirectPrimitiveCollectionPropertyInfo<I, P>)super.getInfo();
		}

		@Override
		public List<JAXBElement<P>> get() {
			return getInfo().get(getOwner());
		}

		public void set(final List<JAXBElement<P>> values) {
			getInfo().set(getOwner(), values);
		}
} public abstract class IndirectPrimitiveCollectionPropertyInfo<I,P> extends PropertyInfo<I,P> {
	protected IndirectPrimitiveCollectionPropertyInfo(final String propertyName, final Class<I> declaringClass, final Class<P> declaredType, final boolean collection, final P defaultValue, final QName schemaName, final QName schemaType, final boolean attribute) {
			super(propertyName, declaringClass, declaredType, collection, defaultValue, schemaName, schemaType, attribute);
		}

		@Override
		public abstract List<JAXBElement<P>> get(final I i) ;

		public abstract void set(final I instance, final List<JAXBElement<P>> values);
} public class ItemProperty<I,P> extends Property<I,P> {
	final P value;
	final int index;

	public ItemProperty(final CollectionPropertyInfo<I, P> info, final I owner, final P value, final int index) {
		super(info, owner);
		this.value = value;
		this.index = index;
	}

	@Override
	public CollectionPropertyInfo<I, P> getInfo() {
		return (CollectionPropertyInfo<I, P>)super.getInfo();
	}

	@Override
	public P get() {
		return this.value;
	}

	public int getIndex() {
		return this.index;
	}
} public abstract class Property<I,P> {
	private final PropertyInfo<I,P> info;
	private final I owner;

	protected Property(final PropertyInfo<I, P> info, final I owner) {
		this.info = info;
		this.owner = owner;
	}

	public PropertyInfo<I, P> getInfo() {
		return this.info;
	}

	public I getOwner() {
		return this.owner;
	}

	public Object get() {
		return this.info.get(this.owner);
	}

} public abstract class PropertyInfo<I, P> {
	public final String propertyName;
	public final Class<P> declaredType;
	public final Class<I> declaringClass;
	public final boolean collection;
	public final P defaultValue;
	public final QName schemaType;
	public final boolean attribute;
	public final QName schemaName;

	protected PropertyInfo(final String propertyName, final Class<I> declaringClass, final Class<P> declaredType, final boolean collection, final P defaultValue, final QName schemaName, final QName schemaType, final boolean attribute) {
		this.propertyName = propertyName;
		this.declaredType = declaredType;
		this.declaringClass = declaringClass;
		this.collection = collection;
		this.defaultValue = defaultValue;
		this.schemaType = schemaType;
		this.attribute = attribute;
		this.schemaName = schemaName;
	}

	public abstract Object get(final I instance);

	@Override
	public boolean equals(final Object o) {
		if (this == o) return true;
		if (!(o instanceof PropertyInfo)) return false;
		final PropertyInfo<?, ?> that = (PropertyInfo<?, ?>)o;
		if (!this.propertyName.equals(that.propertyName)) return false;
		return declaringClass.equals(that.declaringClass);
	}

	@Override
	public int hashCode() {
		int result = this.propertyName.hashCode();
		result = 31 * result + this.declaringClass.hashCode();
		return result;
	}
} public interface PropertyVisitor {
	/**
	 * Called upon visiting a top-level object
	 * @param value The Object to be visited
	 */
	void visit(final Object value);

	/**
	 * Called upon visitng a collection element
	 * @param property The property being visited
	 * @return true if visiting shall continue, false if visiting should be finished
	 */
	boolean visit(final ItemProperty<?, ?> property);

	/**
	 * Called upon visitng a single-value property
	 * @param property The property being visited
	 * @return true if visiting shall continue, false if visiting should be finished
	 */
	boolean visit(final SingleProperty<? ,?> property);

	/**
	 * Called upon visiting a collection property
	 * @param property The property being visited
	 * @return true if visiting shall continue, false if visiting should be finished
	 */
	boolean visit(final CollectionProperty<?, ?> property);


	/**
	 * Called upon visiting a collection property, where the collection items are wrapped in a
	 * {@link javax.xml.bind.JAXBElement} instance.
	 * @param property The property being visited
	 * @return true if visiting shall continue, false if visiting should be finished
	 */
	boolean visit(final IndirectCollectionProperty<? ,?> property);

	/**
	 * Called upon visiting a collection property, where the collection items are wrapped in a
	 * {@link javax.xml.bind.JAXBElement} instance and are of a java primitive type
	 * @param property The property being visited
	 * @return true if visiting shall continue, false if visiting should be finished
	 */
	boolean visit(final IndirectPrimitiveCollectionProperty<? ,?> property);
} public class SingleProperty<I,P> extends Property<I,P> {

	public SingleProperty(final SinglePropertyInfo<I, P> info, final I owner) {
		super(info, owner);
	}

	@Override
	public SinglePropertyInfo<I, P> getInfo() {
		return (SinglePropertyInfo<I, P>)super.getInfo();
	}

	@Override
	public P get() {
		return (P)super.get();
	}

	public void set(final P value) {
		getInfo().set(getOwner(), value);
	}
} public abstract class SinglePropertyInfo<I,P> extends PropertyInfo<I,P> {
	protected SinglePropertyInfo(final String propertyName, final Class<I> declaringClass, final Class<P> declaredType, final boolean collection, final P defaultValue, final QName schemaName, final QName schemaType, final boolean attribute) {
		super(propertyName, declaringClass, declaredType, collection, defaultValue, schemaName, schemaType, attribute);
	}

	@Override
	public abstract P get(final I instance);
	public abstract void set(final I instance, final P value);

} public class DefinedPropertyOutline implements PropertyOutline {
	private final FieldOutline fieldOutline;
	private final PropertyInfo<NType,NClass> propertyInfo;
	private final List<TagRef> referencedItems;
	private final Map<TagRef, String> referencedAnnotations;
	private final JClass jaxbElementClass;
	private final String annotationText;

	public DefinedPropertyOutline(final FieldOutline fieldOutline) {
		this.fieldOutline = fieldOutline;
		this.propertyInfo = fieldOutline.getPropertyInfo();
		this.jaxbElementClass = fieldOutline.getRawType().owner().ref(JAXBElement.class);

		final ClassOutline classOutline = fieldOutline.parent();
		final CClassInfo classInfo = classOutline.target;
		final String fieldName = fieldOutline.getPropertyInfo().getName(false);
		final CPropertyInfo property = getPropertyInfo(classInfo, fieldName);

		if(this.propertyInfo instanceof ElementPropertyInfo) {
			final ElementPropertyInfo<NType, NClass> elementPropertyInfo = (ElementPropertyInfo<NType, NClass>)this.propertyInfo;
			this.referencedItems = new ArrayList<>(elementPropertyInfo.getTypes().size());
			this.referencedAnnotations = new HashMap<>(elementPropertyInfo.getTypes().size());
			for(final TypeRef<NType,NClass> typeRef : elementPropertyInfo.getTypes()) {

				TagRef tagRef = new TagRef(typeRef.getTagName(), typeRef.getTarget());
				this.referencedItems.add(tagRef);
				if (typeRef.getTarget() instanceof CClassInfo) {
					// Get the annotation for the referenced type, i.e. the complex type
				    final String referencedItemAnnotation = SchemaAnnotationUtils.getClassAnnotationDescription(
				    		(CClassInfo) typeRef.getTarget());
				    this.referencedAnnotations.put(tagRef, referencedItemAnnotation);
				}
			}
			if (elementPropertyInfo.getTypes() != null && elementPropertyInfo.getTypes().size() > 1) {
				// This appears to be a single java field bound to an xs:choice and there does't appear to be any
				// way of getting the annotation for the xs:choice itself. If we try and get the field annotation
				// we will get the annotation of the first choice. Therefore provide no javadoc.
				this.annotationText = null;
			} else {
				this.annotationText = SchemaAnnotationUtils.getFieldAnnotationDescription(property);
			}
		} else if(this.propertyInfo instanceof ReferencePropertyInfo) {
			final ReferencePropertyInfo<NType, NClass> elementPropertyInfo = (ReferencePropertyInfo<NType, NClass>)this.propertyInfo;
			this.referencedItems = new ArrayList<>(elementPropertyInfo.getElements().size());
			this.referencedAnnotations = new HashMap<>(elementPropertyInfo.getElements().size());
			for(final Element<NType,NClass> element : elementPropertyInfo.getElements()) {
				this.referencedItems.add(new TagRef(element.getElementName(), element));
			}
			this.annotationText = SchemaAnnotationUtils.getFieldAnnotationDescription(property);
		} else {
			this.referencedItems = Collections.emptyList();
			this.referencedAnnotations = Collections.emptyMap();
			this.annotationText = SchemaAnnotationUtils.getFieldAnnotationDescription(property);
		}
	}

	private CPropertyInfo getPropertyInfo(final CClassInfo classInfo, final String fieldName) {
		return classInfo.getProperties().stream()
					.filter(it -> it.getName(false).equals(fieldName))
					.findAny()
					.orElseThrow(() ->
							new IllegalStateException("Can't find property [" +
									fieldName + "] in class [" + classInfo.getTypeName() + "]"));
	}

	@Override
	public String getBaseName() {
		return this.fieldOutline.getPropertyInfo().getName(true);
	}

	@Override
	public String getFieldName() {
		return this.fieldOutline.getPropertyInfo().getName(false);
	}

	@Override
	public JType getRawType() {
		return this.fieldOutline.getRawType();
	}

	@Override
	public JType getElementType() {
		if (isCollection() && !getRawType().isArray()) {
			return ((JClass) getRawType()).getTypeParameters().get(0);
		} else {
			return getRawType();
		}
	}

	@Override
	public boolean isIndirect() {
		return this.jaxbElementClass.fullName().equals(getElementType().erasure().fullName());
	}

	@Override
	public JFieldVar getFieldVar() {
		String propertyName = this.fieldOutline.getPropertyInfo().getName(false);
		if ("any".equals(propertyName)) {
			propertyName = "content";
		}
		return this.fieldOutline.parent().implClass.fields().get(propertyName);
	}

	public boolean hasGetter() {
		for (final JMethod method : this.fieldOutline.parent().implClass.methods()) {
			if ((method.name().equals("get" + this.fieldOutline.getPropertyInfo().getName(true))
					|| method.name().equals("is" + this.fieldOutline.getPropertyInfo().getName(true))) && method.params().isEmpty()) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean isCollection() {
		return this.fieldOutline.getPropertyInfo().isCollection();
	}

	@Override
	public List<TagRef> getChoiceProperties() {
		return this.referencedItems;
	}

    @Override
    public Optional<String> getSchemaAnnotationText() {
		if (this.annotationText == null || this.annotationText.isEmpty()) {
			return Optional.empty();
		} else {
			return Optional.of(this.annotationText);
		}
    }

	@Override
	public Optional<String> getSchemaAnnotationText(final TagRef tagRef) {
		if (this.referencedAnnotations == null || this.referencedAnnotations.isEmpty()) {
			return Optional.empty();
		} else {
			return Optional.ofNullable(this.referencedAnnotations.get(tagRef));
		}
	}

	public boolean isArray() {
		return getRawType().isArray();
	}

} public interface PropertyOutline {
	String getBaseName();
	String getFieldName();
	JType getRawType();
	JType getElementType();
	JFieldVar getFieldVar();
	boolean hasGetter();
	boolean isCollection();
	boolean isIndirect();
	List<TagRef> getChoiceProperties();

	/**
	 * @return The annotation description text from the corresponding part of the schema if there is any.
	 */
	default Optional<String> getSchemaAnnotationText() {
		return Optional.empty();
	}

	/**
     * @param tagRef The {@link TagRef} of the referenced type to get the schema annotation for
	 * @return The annotation description text from the corresponding part of the schema if there is any.
	 */
	default Optional<String> getSchemaAnnotationText(TagRef tagRef) {
		return Optional.empty();
	}

	class TagRef {
		private final QName tagName;
		private final TypeInfo<NType,NClass> typeInfo;

		public TagRef(final QName tagName, final TypeInfo<NType, NClass> typeInfo) {
			this.tagName = tagName;
			this.typeInfo = typeInfo;
		}

		public QName getTagName() {
			return this.tagName;
		}

		public TypeInfo<NType, NClass> getTypeInfo() {
			return this.typeInfo;
		}
	}
}
import java.lang.reflect.Method;
import java.util.List;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import com.kscs.util.jaxb.CollectionProperty;
import com.kscs.util.jaxb.CollectionPropertyInfo;
import com.kscs.util.jaxb.IndirectCollectionProperty;
import com.kscs.util.jaxb.IndirectCollectionPropertyInfo;
import com.kscs.util.jaxb.IndirectPrimitiveCollectionProperty;
import com.kscs.util.jaxb.IndirectPrimitiveCollectionPropertyInfo;
import com.kscs.util.jaxb.ItemProperty;
import com.kscs.util.jaxb.Property;
import com.kscs.util.jaxb.PropertyInfo;
import com.kscs.util.jaxb.PropertyVisitor;
import com.kscs.util.jaxb.SingleProperty;
import com.kscs.util.jaxb.SinglePropertyInfo;
import com.kscs.util.plugins.xjc.base.AbstractPlugin;
import com.kscs.util.plugins.xjc.base.AbstractXSFunction;
import com.kscs.util.plugins.xjc.base.Opt;
import com.kscs.util.plugins.xjc.outline.DefinedPropertyOutline;
import com.kscs.util.plugins.xjc.outline.PropertyOutline;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JConditional;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JExpressionImpl;
import com.sun.codemodel.JFieldRef;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JForEach;
import com.sun.codemodel.JFormatter;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JOp;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.model.CDefaultValue;
import com.sun.tools.xjc.model.Model;
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.tools.xjc.reader.xmlschema.BGMBuilder;
import com.sun.xml.xsom.XSAttributeDecl;
import com.sun.xml.xsom.XSAttributeUse;
import com.sun.xml.xsom.XSComplexType;
import com.sun.xml.xsom.XSComponent;
import com.sun.xml.xsom.XSElementDecl;
import com.sun.xml.xsom.XSModelGroup;
import com.sun.xml.xsom.XSModelGroupDecl;
import com.sun.xml.xsom.XSParticle;
import com.sun.xml.xsom.XSSimpleType;
import com.sun.xml.xsom.XSWildcard;
import com.sun.xml.xsom.visitor.XSFunction;
			} else {
				return new QName(type.getTargetNamespace(), type.getName());
			}
		}
	};
	private static final XSFunction<Boolean> ATTRIBUTE_FUNC = new AbstractXSFunction<Boolean>() {
		@Override
		public Boolean attributeDecl(final XSAttributeDecl decl) {
			return true;
		}

		@Override
		public Boolean attributeUse(final XSAttributeUse use) {
			return true;
		}

		@Override
		public Boolean elementDecl(final XSElementDecl decl) {
			return false;
		}

		@Override
		public Boolean particle(final XSParticle particle) {
			return false;
		}

		@Override
		public Boolean simpleType(final XSSimpleType simpleType) {
			return false;
		}

		@Override
		public Boolean complexType(final XSComplexType type) {
			return false;
		}

		@Override
		public Boolean modelGroupDecl(final XSModelGroupDecl decl) {
			return false;
		}

		@Override
		public Boolean modelGroup(final XSModelGroup group) {
			return false;
		}

		@Override
		public Boolean wildcard(final XSWildcard wc) {
			return false;
		}
	};
	@Opt
	private boolean generateTools = true;
	@Opt
	private boolean extended = false;
	@Opt
	private boolean camelCase = false;
	@Opt
	private String metaClassName = "PropInfo";
	@Opt
	private boolean allowSet = true;
	@Opt
	private String visitMethodName = "visit";
	private boolean fixedAttributeAsConstantProperty;

	@Override
	public String getOptionName() {
		return "Xmeta";
	}

	@Override
	public void postProcessModel(final Model model, final ErrorHandler errorHandler) {
		this.fixedAttributeAsConstantProperty = Ring.get(BGMBuilder.class).getGlobalBinding().getDefaultProperty().isConstantProperty();
	}

	public boolean isExtended() {
		return this.extended;
	}

	@Override
	public boolean run(final Outline outline, final Options opt, final ErrorHandler errorHandler) throws SAXException {
		final PluginContext pluginContext = PluginContext.get(outline, opt, errorHandler);
		if (this.extended && this.generateTools) {
			pluginContext.writeSourceFile(PropertyInfo.class);
			pluginContext.writeSourceFile(SinglePropertyInfo.class);
			pluginContext.writeSourceFile(CollectionPropertyInfo.class);
			pluginContext.writeSourceFile(IndirectCollectionPropertyInfo.class);
			pluginContext.writeSourceFile(IndirectPrimitiveCollectionPropertyInfo.class);
			pluginContext.writeSourceFile(PropertyVisitor.class);
			pluginContext.writeSourceFile(Property.class);
			pluginContext.writeSourceFile(SingleProperty.class);
			pluginContext.writeSourceFile(CollectionProperty.class);
			pluginContext.writeSourceFile(IndirectCollectionProperty.class);
			pluginContext.writeSourceFile(IndirectPrimitiveCollectionProperty.class);
			pluginContext.writeSourceFile(ItemProperty.class);
		}
		for (final ClassOutline classOutline : outline.getClasses()) {
			generateMetaClass(pluginContext, classOutline, errorHandler);
		}
		return true;
	}

	private void generateMetaClass(final PluginContext pluginContext, final ClassOutline classOutline, final ErrorHandler errorHandler) throws SAXException {
		try {
			final JDefinedClass metaClass = classOutline.implClass._class(JMod.PUBLIC | JMod.STATIC, this.metaClassName);
			final JMethod visitMethod = generateVisitMethod(classOutline);
			for (final FieldOutline fieldOutline : classOutline.getDeclaredFields()) {
				if (this.extended) {
					generateExtendedMetaField(pluginContext, metaClass, visitMethod, fieldOutline);
				} else {
					generateNameOnlyMetaField(pluginContext, metaClass, fieldOutline);
				}
			}
			visitMethod.body()._return(JExpr._this());
		} catch (final JClassAlreadyExistsException e) {
			errorHandler.error(new SAXParseException(getMessage("error.metaClassExists", classOutline.implClass.name(), this.metaClassName), classOutline.target.getLocator()));
		}
	}

	private void generateNameOnlyMetaField(final PluginContext pluginContext, final JDefinedClass metaClass, final FieldOutline fieldOutline) {
		final PropertyOutline propertyOutline = new DefinedPropertyOutline(fieldOutline);