Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 2007-2009, Dennis M. Sosnoski. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following
* disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of
* JiBX nor the names of its contributors may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jibx.custom.classes;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jibx.custom.CustomUtils;
import org.jibx.runtime.EnumSet;
import org.jibx.runtime.IUnmarshallingContext;
import org.jibx.runtime.QName;
import org.jibx.util.IClass;
import org.jibx.util.IClassItem;
import org.jibx.util.IClassLocator;
import org.jibx.util.InsertionOrderedMap;
import org.jibx.util.InsertionOrderedSet;
import org.jibx.util.StringArray;
/**
* Class customization information. This supports direct class customizations (such as the corresponding element name,
* when building a concrete mapping) and also acts as a container for individual fields and/or properties.
*
* @author Dennis M. Sosnoski
*/
public class ClassCustom extends NestingBase implements IApply
{
/** Enumeration of allowed attribute names */
public static final StringArray s_allowedAttributes =
new StringArray(new String[] { "create-type", "deserializer", "element-name", "enum-value-method", "excludes",
"factory", "form", "includes", "name", "optionals", "requireds", "serializer", "type-name", "use-super" },
NestingBase.s_allowedAttributes);
/** Element name in XML customization file. */
public static final String ELEMENT_NAME = "class";
// value set information
public static final int FORM_DEFAULT = 0;
public static final int FORM_CONCRETE_MAPPING = 1;
public static final int FORM_ABSTRACT_MAPPING = 2;
public static final int FORM_STRING = 3;
public static final EnumSet s_representationEnum =
new EnumSet(FORM_DEFAULT, new String[] { "default", "concrete-mapping", "abstract-mapping", "simple" });
// values specific to class level
private String m_name;
private String m_elementName;
private String m_typeName;
private String m_createType;
private String m_factoryMethod;
private String m_enumValueMethod;
private int m_form;
private String[] m_includes;
private String[] m_excludes;
private boolean m_useSuper;
private String[] m_requireds;
private String[] m_optionals;
private String m_serializer;
private String m_deserializer;
// list of contained items
private final ArrayList m_children;
// values filled in by apply() method
private boolean m_isApplied;
private QName m_typeQName;
private QName m_elementQName;
private IClass m_classInformation;
private InsertionOrderedMap m_memberMap;
/**
* Constructor.
*
* @param parent
* @param name class simple name (without package)
*/
/* package */ClassCustom(NestingBase parent, String name) {
super(parent);
m_name = name;
m_children = new ArrayList();
m_useSuper = true;
m_form = -1;
}
/**
* Make sure all attributes are defined.
*
* @param uctx unmarshalling context
*/
private void preSet(IUnmarshallingContext uctx) {
validateAttributes(uctx, s_allowedAttributes);
}
/**
* Get fully-qualified class name.
*
* @return class name
*/
public String getName() {
PackageCustom parent = (PackageCustom)getParent();
String pack = parent.getName();
if (pack.length() > 0) {
return pack + '.' + m_name;
} else {
return m_name;
}
}
/**
* Get simple class name.
*
* @return class name
*/
public String getSimpleName() {
return m_name;
}
/**
* Get the element name to be used for this class in a concrete mapping.
*
* @return element name
*/
public String getElementName() {
return m_elementName;
}
/**
* Get the qualified element name to be used for this class in a concrete mapping.
*
* @return element name
*/
public QName getElementQName() {
return m_elementQName;
}
/**
* Get the type name to be used for this class in an abstract mapping.
*
* @return type name
*/
public String getTypeName() {
return m_typeName;
}
/**
* Get the type name to be used when creating an instance of this class.
*
* @return type name
*/
public String getCreateType() {
return m_createType;
}
/**
* Set the type name to be used when creating an instance of this class.
*
* @param type name
*/
public void setCreateType(String type) {
m_createType = type;
}
/**
* Get serializer method.
*
* @return serializer
*/
public String getSerializer() {
return m_serializer;
}
/**
* Get deserializer method.
*
* @return deserializer
*/
public String getDeserializer() {
return m_deserializer;
}
/**
* Get the method used to retrieve the text value for an enum class.
*
* @return method name
*/
public String getEnumValueMethod() {
return m_enumValueMethod;
}
/**
* Get the factory method to be used when creating an instance of this class.
*
* @return method name
*/
public String getFactoryMethod() {
return m_factoryMethod;
}
/**
* Get the qualified type name to be used for this class in an abstract mapping.
*
* @return type qname
*/
public QName getTypeQName() {
return m_typeQName;
}
/**
* Get the representation code.
*
* @return value from {@link #s_representationEnum} enumeration
*/
public int getForm() {
return m_form;
}
/**
* Get list of names to be excluded from class representation.
*
* @return excludes (null if none)
*/
public String[] getExcludes() {
return m_excludes;
}
/**
* Get list of names to be included in class representation.
*
* @return includes (null if none)
*/
public String[] getIncludes() {
return m_includes;
}
/**
* Check for superclass to be included in binding.
*
* @return true if superclass included, false if not
*/
public boolean isUseSuper() {
return m_useSuper;
}
/**
* Check if this is a directly instantiable class (not an interface, and not abstract)
*
* @return true if instantiable, false if not
*/
public boolean isConcreteClass() {
return !(m_classInformation.isAbstract() || m_classInformation.isInterface());
}
/**
* Check if class represents a simple text value.
*
* @return text value flag
*/
public boolean isSimpleValue() {
return m_form == FORM_STRING;
}
/**
* Check if abstract mapping required for class.
*
* @return abstract mapping flag
*/
public boolean isAbstractMappingForced() {
return m_form == FORM_ABSTRACT_MAPPING;
}
/**
* Check if concrete mapping required for class. If a 'form' setting is defined for the class it returns the flag
* based on that setting, and otherwise returns based on the nesting abstract mapping flag.
*
* @return abstract mapping flag
*/
public boolean isConcreteMappingForced() {
return m_form == FORM_CONCRETE_MAPPING;
}
/**
* Get list of children.
*
* @return list
*/
public List getChildren() {
return m_children;
}
/**
* Add child.
*
* @param child custom base
*/
protected void addChild(CustomBase child) {
if (child.getParent() == this) {
m_children.add(child);
} else {
throw new IllegalStateException("Internal error: child not linked");
}
}
/**
* Form set text method. This is intended for use during unmarshalling. TODO: add validation
*
* @param text method
* @param ictx context
*/
private void setFormText(String text, IUnmarshallingContext ictx) {
m_form = s_representationEnum.getValue(text);
}
/**
* Form get text method. This is intended for use during marshalling.
*
* @return text method
*/
private String getFormText() {
return s_representationEnum.getName(m_form);
}
/**
* Build map from member names to read access methods. This assumes that each public, non-static, no-argument
* method which returns a value and has a name beginning with "get" or "is" is a property read access method. It
* maps the corresponding property name to the method, and returns the map.
*
* @param methods items
* @param inclset set of member names to be included (null if not specified)
* @param exclset set of member names to be excluded (null if not specified, ignored if inclset is
* non-null)
* @return map
*/
private Map mapPropertyReadMethods(IClassItem[] methods, Set inclset, Set exclset) {
// check all methods for property read access matches
InsertionOrderedMap getmap = new InsertionOrderedMap();
for (int i = 0; i < methods.length; i++) {
IClassItem item = methods[i];
String name = item.getName();
int flags = item.getAccessFlags();
if (item.getArgumentCount() == 0 && (flags & Modifier.STATIC) == 0 && (flags & Modifier.PUBLIC) != 0 &&
((name.startsWith("get") && !item.getTypeName().equals("void")) || (name.startsWith("is") &&
item.getTypeName().equals("boolean")))) {
// have what appears to be a getter, check if it should be used
String memb = ValueCustom.memberNameFromGetMethod(name);
boolean use = true;
if (inclset != null) {
use = inclset.contains(memb.toLowerCase());
} else if (exclset != null) {
use = !exclset.contains(memb.toLowerCase());
}
if (use) {
getmap.put(memb, item);
}
}
}
return getmap;
}
/**
* Build map from member names to write access methods. This assumes that each public, non-static, single-argument
* method which returns void and has a name beginning with "set" is a property write access method. It maps the
* corresponding property name to the method, and returns the map.
*
* @param methods
* @param inclset set of member names to be included (null if not specified)
* @param exclset set of member names to be excluded (null if not specified, ignored if inclset is
* non-null)
* @return map
*/
private Map mapPropertyWriteMethods(IClassItem[] methods, Set inclset, Set exclset) {
// check all methods for property write access matches
InsertionOrderedMap setmap = new InsertionOrderedMap();
for (int i = 0; i < methods.length; i++) {
IClassItem item = methods[i];
String name = item.getName();
int flags = item.getAccessFlags();
if (item.getArgumentCount() == 1 && (flags & Modifier.STATIC) == 0 && (flags & Modifier.PUBLIC) != 0 &&
name.startsWith("set") && item.getTypeName().equals("void")) {
// have what appears to be a setter, check if it should be used
String memb = ValueCustom.memberNameFromSetMethod(name);
boolean use = true;
if (inclset != null) {
use = inclset.contains(memb.toLowerCase());
} else if (exclset != null) {
use = !exclset.contains(memb.toLowerCase());
}
if (use) {
setmap.put(memb, item);
}
}
}
return setmap;
}
/**
* Build map from member names to fields. This includes all non-static and non-transient fields of the class.
*
* @param fields
* @param prefs prefixes to be stripped in deriving names
* @param suffs suffixes to be stripped in deriving names
* @param inclset set of member names to be included (null if not specified)
* @param exclset set of member names to be excluded (null if not specified, ignored if inclset is
* non-null)
* @return map
*/
private Map mapFields(IClassItem[] fields, String[] prefs, String[] suffs, Set inclset, Set exclset) {
// check all fields for use as members
InsertionOrderedMap fieldmap = new InsertionOrderedMap();
for (int i = 0; i < fields.length; i++) {
IClassItem item = fields[i];
if ((item.getAccessFlags() & (Modifier.FINAL | Modifier.STATIC | Modifier.TRANSIENT)) == 0) {
// not final, static, or transient, so check if it should be used
String name = item.getName();
String memb = ValueCustom.memberNameFromField(name, prefs, suffs);
boolean use = true;
if (inclset != null) {
use = inclset.contains(memb.toLowerCase());
} else if (exclset != null) {
use = !exclset.contains(memb.toLowerCase());
}
if (use) {
fieldmap.put(memb, item);
}
}
}
return fieldmap;
}
/**
* Find the most specific type for a property based on the access methods.
*
* @param gmeth read access method (null if not defined)
* @param smeth write access method (null if not defined)
* @param icl
* @return most specific type name
*/
private String findPropertyType(IClassItem gmeth, IClassItem smeth, IClassLocator icl) {
String type;
if (gmeth == null) {
if (smeth == null) {
throw new IllegalArgumentException("Internal error: no access methods known");
} else {
type = smeth.getArgumentType(0);
}
} else if (smeth == null) {
type = gmeth.getTypeName();
} else {
String gtype = gmeth.getTypeName();
String stype = smeth.getArgumentType(0);
IClass gclas = icl.getRequiredClassInfo(gtype);
if (gclas.isSuperclass(stype) || gclas.isImplements(stype)) {
type = gtype;
} else {
type = stype;
}
}
return type;
}
/**
* Utility method to strip any leading non-alphanumeric characters from an array of name strings.
*
* @param names (null if none)
* @return array of stripped names (null if none)
*/
private static String[] stripNames(String[] names) {
if (names == null) {
return null;
} else {
String[] strips = null;
for (int i = 0; i < names.length; i++) {
String name = names[i];
int index = 0;
while (index < name.length()) {
char chr = name.charAt(index);
if (Character.isLetter(chr) || Character.isDigit(chr)) {
break;
} else {
index++;
}
}
if (index > 0) {
if (strips == null) {
strips = new String[names.length];
System.arraycopy(names, 0, strips, 0, names.length);
}
strips[i] = name.substring(index);
}
}
return (strips == null) ? names : strips;
}
}
/**
* Classify an array of names as elements or attributes, based on leading flag characters ('@' for an attribute,
* '<' for an element).
*
* @param names (null if none)
* @param elems set of element names
* @param attrs set of attribute names
*/
private static void classifyNames(String[] names, Set elems, Set attrs) {
if (names != null) {
for (int i = 0; i < names.length; i++) {
String name = names[i];
if (name.length() > 0) {
char chr = name.charAt(0);
Set addset = null;
Set altset = null;
if (chr == '@') {
addset = attrs;
altset = elems;
} else if (chr == '/') {
addset = elems;
altset = attrs;
}
if (addset != null) {
name = name.substring(1).toLowerCase();
addset.add(name);
if (altset.contains(name)) {
throw new IllegalArgumentException("Name " + name + " used as both element and attribute");
}
}
}
}
}
}
/**
* Apply customizations to class to fill out members. The name handling gets tricky, because of the variety of
* options provided. The user can specify value names to be included and/or excluded for the class, and also value
* names to be treated as optional or required. If an 'includes' list is given, only the values on that list are
* processed from the class, along with any values with their own child elements. If an 'excludes' list is given,
* only the names not on that list are processed. If neither an 'includes' nor an 'excludes' list is present, all
* values defined by the class will be processed. It is an error if the same name occurs on both an 'excludes' list
* and any other list, or if a name on the 'excludes' list has a child element present. It is also an error if the
* same name occurs on both the 'optionals' and 'requireds' list. Each list name can also be flagged with a leading
* indicator character to say whether the value should be represented as an attribute ('@') or element ('<'). The
* order of child elements is also partially determined by the lists, with the 'includes' list processed first, then
* the 'requireds' list, then the 'optionals' list, then the child elements, then any values not yet processed.
*
* @param icl class locator
*/
public void apply(IClassLocator icl) {
// check for repeated call (happens due to package-level apply()
if (m_isApplied) {
return;
}
m_isApplied = true;
// initialize class information
m_classInformation = icl.getClassInfo(getName());
if (m_classInformation == null) {
throw new IllegalStateException("Internal error: unable to find class " + m_name);
}
// skip any member handling if simple value
if (m_form == FORM_STRING) {
return;
}
// inherit namespace directly from package level, if not specified
String ns = getSpecifiedNamespace();
if (ns == null) {
ns = getParent().getNamespace();
}
setNamespace(ns);
// set the name(s) to be used if mapped
String cname = convertName(getSimpleName());
if (m_elementName == null) {
m_elementName = cname;
}
m_elementQName = new QName(getNamespace(), m_elementName);
if (m_typeName == null) {
m_typeName = cname;
}
m_typeQName = new QName(getNamespace(), m_typeName);
// initialize maps with existing member customizations
HashMap namemap = new HashMap();
for (Iterator iter = getChildren().iterator(); iter.hasNext();) {
ValueCustom memb = (ValueCustom)iter.next();
String name = memb.getBaseName();
if (name != null) {
namemap.put(name, memb);
}
}
// generate sets of names to be included or ignored, and optional/requires, elements/attributes
InsertionOrderedSet nameset = new InsertionOrderedSet();
Set exclset = null;
Set inclset = null;
Set elemset = Collections.EMPTY_SET;
Set attrset = Collections.EMPTY_SET;
Set optset = Collections.EMPTY_SET;
Set reqset = Collections.EMPTY_SET;
if (m_includes != null || m_optionals != null || m_requireds != null) {
// initialize the ordered set of names to be included
String[] optionals = stripNames(m_optionals);
String[] requireds = stripNames(m_requireds);
if (m_includes != null) {
nameset.addAll(stripNames(m_includes));
} else {
if (requireds != null) {
nameset.addAll(requireds);
}
if (optionals != null) {
nameset.addAll(optionals);
}
}
// build sets of includeds, optionals, and requireds
inclset = CustomUtils.noCaseNameSet(stripNames(m_includes));
if (optionals != null) {
optset = CustomUtils.noCaseNameSet(optionals);
}
if (requireds != null) {
reqset = CustomUtils.noCaseNameSet(requireds);
}
// classify names as elements or attributes
elemset = new HashSet();
attrset = new HashSet();
classifyNames(m_requireds, elemset, attrset);
classifyNames(m_optionals, elemset, attrset);
classifyNames(m_includes, elemset, attrset);
}
if (m_excludes != null) {
exclset = CustomUtils.noCaseNameSet(stripNames(m_excludes));
}
// first find members from property access methods
Map getmap = null;
Map setmap = null;
IClassItem[] methods = m_classInformation.getMethods();
GlobalCustom global = getGlobal();
if (global.isOutput()) {
// find properties using read access methods
getmap = mapPropertyReadMethods(methods, inclset, exclset);
if (global.isInput()) {
// find properties using write access methods
setmap = mapPropertyWriteMethods(methods, inclset, exclset);
// discard any read-only properties
for (Iterator iter = getmap.keySet().iterator(); iter.hasNext();) {
if (!setmap.containsKey(iter.next())) {
iter.remove();
}
}
}
}
if (global.isInput()) {
// find properties using write access methods
setmap = mapPropertyWriteMethods(methods, inclset, exclset);
}
// find members from fields
IClassItem[] fields = m_classInformation.getFields();
Map fieldmap = mapFields(fields, getStripPrefixes(), getStripSuffixes(), inclset, exclset);
// get list of names selected for use by options
if (m_includes == null) {
if (isPropertyAccess()) {
if (global.isOutput()) {
nameset.addAll(getmap.keySet());
} else {
nameset.addAll(setmap.keySet());
}
} else {
for (Iterator iter = fieldmap.keySet().iterator(); iter.hasNext();) {
String name = (String)iter.next();
IClassItem field = (IClassItem)fieldmap.get(name);
int access = field.getAccessFlags();
if (!Modifier.isStatic(access) && !Modifier.isTransient(access)) {
nameset.add(name);
}
}
}
}
// process all members found in class
m_memberMap = new InsertionOrderedMap();
boolean auto = !getName().startsWith("java.") && !getName().startsWith("javax.");
List names = nameset.asList();
for (Iterator iter = names.iterator(); iter.hasNext();) {
// get basic member information
String name = (String)iter.next();
String lcname = name.toLowerCase();
ValueCustom cust = null;
IClassItem gmeth = (IClassItem)(getmap == null ? null : getmap.get(name));
IClassItem smeth = (IClassItem)(setmap == null ? null : setmap.get(name));
IClassItem field = (IClassItem)(fieldmap == null ? null : fieldmap.get(name));
// find the optional/required setting
Boolean isreq = null;
if (optset.contains(lcname)) {
isreq = Boolean.FALSE;
}
if (reqset.contains(lcname)) {
isreq = Boolean.TRUE;
}
// find the style setting
Integer style = null;
if (attrset.contains(lcname)) {
style = ATTRIBUTE_STYLE_INTEGER;
} else if (elemset.contains(lcname)) {
style = ELEMENT_STYLE_INTEGER;
}
// check for existing customization
if (namemap.containsKey(name)) {
// fill in data missing from existing member customization
cust = (ValueCustom)namemap.get(name);
} else if (auto) {
if (isPropertyAccess()) {
if (gmeth != null || smeth != null) {
// create value information from methods
cust = new ValueCustom(this, name);
}
} else if (field != null) {
// create value information from field
cust = new ValueCustom(this, name);
}
}
// add customization to map
if (cust != null) {
if (isPropertyAccess()) {
cust.fillDetails(null, gmeth, smeth, icl, isreq, style);
} else {
cust.fillDetails(field, null, null, icl, isreq, style);
}
m_memberMap.put(name, cust);
}
}
// check for any supplied customizations that haven't been matched
for (Iterator iter = namemap.keySet().iterator(); iter.hasNext();) {
String name = (String)iter.next();
String lcname = name.toLowerCase();
if (!m_memberMap.containsKey(name)) {
// find the optional/required setting
Boolean req = null;
if (optset != null && optset.contains(lcname)) {
req = Boolean.FALSE;
}
if (reqset != null && reqset.contains(lcname)) {
req = Boolean.TRUE;
}
// find the style setting
Integer style = null;
if (attrset.contains(lcname)) {
style = ATTRIBUTE_STYLE_INTEGER;
} else if (elemset.contains(lcname)) {
style = ELEMENT_STYLE_INTEGER;
}
// complete the customization and add to map
ValueCustom cust = (ValueCustom)namemap.get(name);
cust.fillDetails(m_classInformation, req, style);
m_memberMap.put(name, cust);
}
}
}
/**
* Get customization information for a member by name. This method may only be called after
* {@link #apply(IClassLocator)}.
*
* @param name Member name
* @return customization, or null if none
*/
public ValueCustom getMember(String name) {
return (ValueCustom)m_memberMap.get(name);
}
/**
* Get actual class information. This method may only be called after {@link #apply(IClassLocator)}.
*
* @return class information
*/
public IClass getClassInformation() {
return m_classInformation;
}
/**
* Get collection of members in class.
*
* @return members
*/
public Collection getMembers() {
return m_memberMap.values();
}
}