001: /*
002: Copyright (c) 2007, Dennis M. Sosnoski
003: All rights reserved.
004:
005: Redistribution and use in source and binary forms, with or without modification,
006: are permitted provided that the following conditions are met:
007:
008: * Redistributions of source code must retain the above copyright notice, this
009: list of conditions and the following disclaimer.
010: * Redistributions in binary form must reproduce the above copyright notice,
011: this list of conditions and the following disclaimer in the documentation
012: and/or other materials provided with the distribution.
013: * Neither the name of JiBX nor the names of its contributors may be used
014: to endorse or promote products derived from this software without specific
015: prior written permission.
016:
017: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
018: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
019: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
021: ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: */
028:
029: package org.jibx.binding.generator;
030:
031: import java.lang.reflect.Modifier;
032: import java.util.ArrayList;
033: import java.util.Collection;
034: import java.util.HashMap;
035: import java.util.Iterator;
036: import java.util.List;
037: import java.util.Map;
038: import java.util.Set;
039:
040: import org.jibx.binding.model.IClass;
041: import org.jibx.binding.model.IClassItem;
042: import org.jibx.binding.model.IClassLocator;
043: import org.jibx.runtime.EnumSet;
044: import org.jibx.runtime.IUnmarshallingContext;
045: import org.jibx.runtime.QName;
046:
047: /**
048: * Class customization information. This supports direct class customizations.
049: * (such as the corresponding element name, when building a concrete mapping)
050: * and also acts as a container for individual fields and/or properties.
051: */
052: public class ClassCustom extends NestingBase implements IApply {
053: /** Element name in XML customization file. */
054: public static final String ELEMENT_NAME = "class";
055:
056: // value set information
057: public static final int DEFAULT_REPRESENTATION = 0;
058: public static final int CONCRETE_MAPPING_REPRESENTATION = 1;
059: public static final int ABSTRACT_MAPPING_REPRESENTATION = 2;
060:
061: public static final EnumSet s_representationEnum = new EnumSet(
062: DEFAULT_REPRESENTATION, new String[] { "default",
063: "concrete-mapping", "abstract-mapping" });
064:
065: // values specific to class level
066: private String m_name;
067: private String m_elementName;
068: private String m_typeName;
069: private String m_createType;
070: private String m_factoryMethod;
071: private int m_form;
072: private String[] m_includes;
073: private String[] m_excludes;
074: private boolean m_useSuper;
075: private String[] m_requireds;
076: private String[] m_optionals;
077:
078: // list of contained items
079: private final ArrayList m_children;
080:
081: // values filled in by apply() method
082: private QName m_typeQName;
083: private QName m_elementQName;
084: private IClass m_classInformation;
085: private Map m_memberMap;
086:
087: /**
088: * Constructor.
089: *
090: * @param parent
091: * @param name class simple name (without package)
092: */
093: /*package*/ClassCustom(NestingBase parent, String name) {
094: super (parent);
095: m_name = name;
096: m_children = new ArrayList();
097: m_useSuper = true;
098: }
099:
100: /**
101: * Get fully-qualified class name.
102: *
103: * @return class name
104: */
105: public String getName() {
106: PackageCustom parent = (PackageCustom) getParent();
107: String pack = parent.getName();
108: if (pack.length() > 0) {
109: return pack + '.' + m_name;
110: } else {
111: return m_name;
112: }
113: }
114:
115: /**
116: * Get simple class name.
117: *
118: * @return class name
119: */
120: public String getSimpleName() {
121: return m_name;
122: }
123:
124: /**
125: * Get the element name to be used for this class in a concrete mapping.
126: *
127: * @return element name
128: */
129: public String getElementName() {
130: return m_elementName;
131: }
132:
133: /**
134: * Get the qualified element name to be used for this class in a concrete
135: * mapping.
136: *
137: * @return element name
138: */
139: public QName getElementQName() {
140: return m_elementQName;
141: }
142:
143: /**
144: * Get the type name to be used for this class in an abstract mapping.
145: *
146: * @return type name
147: */
148: public String getTypeName() {
149: return m_typeName;
150: }
151:
152: /**
153: * Get the type name to be used when creating an instance of this class.
154: *
155: * @return type name
156: */
157: public String getCreateType() {
158: return m_createType;
159: }
160:
161: /**
162: * Set the type name to be used when creating an instance of this class.
163: *
164: * @param type
165: */
166: public void setCreateType(String type) {
167: m_createType = type;
168: }
169:
170: /**
171: * Get the factory method to be used when creating an instance of this
172: * class.
173: *
174: * @return method name
175: */
176: public String getFactoryMethod() {
177: return m_factoryMethod;
178: }
179:
180: /**
181: * Get the qualified type name to be used for this class in an abstract
182: * mapping.
183: *
184: * @return type qname
185: */
186: public QName getTypeQName() {
187: return m_typeQName;
188: }
189:
190: /**
191: * Get the representation code.
192: *
193: * @return value from {@link #s_representationEnum} enumeration
194: */
195: public int getForm() {
196: return m_form;
197: }
198:
199: /**
200: * Get list of names to be excluded from class representation.
201: *
202: * @return excludes (<code>null</code> if none)
203: */
204: public String[] getExcludes() {
205: return m_excludes;
206: }
207:
208: /**
209: * Get list of names to be included in class representation.
210: *
211: * @return includes (<code>null</code> if none)
212: */
213: public String[] getIncludes() {
214: return m_includes;
215: }
216:
217: /**
218: * Check for superclass to be included in binding.
219: *
220: * @return <code>true</code> if superclass included, <code>false</code> if
221: * not
222: */
223: public boolean isUseSuper() {
224: return m_useSuper;
225: }
226:
227: /**
228: * Check if this is a directly instantiable class (not an interface, and not
229: * abstract)
230: *
231: * @return <code>true</code> if instantiable, <code>false</code> if not
232: */
233: public boolean isConcrete() {
234: return !(m_classInformation.isAbstract() || m_classInformation
235: .isInterface());
236: }
237:
238: /**
239: * Get list of children.
240: *
241: * @return list
242: */
243: public List getChildren() {
244: return m_children;
245: }
246:
247: /**
248: * Add child.
249: *
250: * @param child
251: */
252: protected void addChild(CustomBase child) {
253: if (child.getParent() == this ) {
254: m_children.add(child);
255: } else {
256: throw new IllegalStateException(
257: "Internal error: child not linked");
258: }
259: }
260:
261: /**
262: * Form set text method. This is intended for use during unmarshalling.
263: * TODO: add validation
264: *
265: * @param text
266: * @param ictx
267: */
268: private void setFormText(String text, IUnmarshallingContext ictx) {
269: m_form = s_valueStyleEnum.getValue(text);
270: }
271:
272: /**
273: * Form get text method. This is intended for use during marshalling.
274: *
275: * @return text
276: */
277: private String getFormText() {
278: return s_valueStyleEnum.getName(m_form);
279: }
280:
281: /**
282: * Build map from member names to read access methods. This assumes that
283: * each no-argument method which returns a value and has a name beginning
284: * with "get" or "is" is a property read access method. It maps the
285: * corresponding property name to the method, and returns the map.
286: *
287: * @param methods
288: * @param inclset set of member names to be included (<code>null</code>
289: * if not specified)
290: * @param exclset set of member names to be excluded (<code>null</code>
291: * if not specified, ignored if inclset is non-<code>null</code>)
292: * @return map
293: */
294: private Map mapPropertyReadMethods(IClassItem[] methods,
295: Set inclset, Set exclset) {
296:
297: // check all methods for property read access matches
298: HashMap getmap = new HashMap();
299: for (int i = 0; i < methods.length; i++) {
300: IClassItem item = methods[i];
301: String name = item.getName();
302: if (item.getArgumentCount() == 0
303: && ((name.startsWith("get") && !item.getTypeName()
304: .equals("void")) || (name.startsWith("is") && item
305: .getTypeName().equals("boolean")))) {
306:
307: // have what appears to be a getter, check if it should be used
308: String memb = MemberCustom
309: .memberNameFromGetMethod(name);
310: boolean use = true;
311: if (inclset != null) {
312: use = inclset.contains(memb.toLowerCase());
313: } else if (exclset != null) {
314: use = !exclset.contains(memb.toLowerCase());
315: }
316: if (use) {
317: getmap.put(memb, item);
318: }
319:
320: }
321: }
322: return getmap;
323: }
324:
325: /**
326: * Build map from member names to write access methods. This assumes that
327: * each single-argument method which returns void and has a name beginning
328: * with "set" is a property write access method. It maps the corresponding
329: * property name to the method, and returns the map.
330: *
331: * @param methods
332: * @param inclset set of member names to be included (<code>null</code>
333: * if not specified)
334: * @param exclset set of member names to be excluded (<code>null</code>
335: * if not specified, ignored if inclset is non-<code>null</code>)
336: * @return map
337: */
338: private Map mapPropertyWriteMethods(IClassItem[] methods,
339: Set inclset, Set exclset) {
340:
341: // check all methods for property write access matches
342: HashMap setmap = new HashMap();
343: for (int i = 0; i < methods.length; i++) {
344: IClassItem item = methods[i];
345: String name = item.getName();
346: if (item.getArgumentCount() == 1 && name.startsWith("set")
347: && item.getTypeName().equals("void")) {
348:
349: // have what appears to be a setter, check if it should be used
350: String memb = MemberCustom
351: .memberNameFromSetMethod(name);
352: boolean use = true;
353: if (inclset != null) {
354: use = inclset.contains(memb.toLowerCase());
355: } else if (exclset != null) {
356: use = !exclset.contains(memb.toLowerCase());
357: }
358: if (use) {
359: setmap.put(memb, item);
360: }
361:
362: }
363: }
364: return setmap;
365: }
366:
367: /**
368: * Build map from member names to fields. This includes all non-static and
369: * non-transient fields of the class.
370: *
371: * @param fields
372: * @param prefs prefixes to be stripped in deriving names
373: * @param suffs suffixes to be stripped in deriving names
374: * @param inclset set of member names to be included (<code>null</code>
375: * if not specified)
376: * @param exclset set of member names to be excluded (<code>null</code>
377: * if not specified, ignored if inclset is non-<code>null</code>)
378: * @return map
379: */
380: private Map mapFields(IClassItem[] fields, String[] prefs,
381: String[] suffs, Set inclset, Set exclset) {
382:
383: // check all fields for use as members
384: HashMap fieldmap = new HashMap();
385: for (int i = 0; i < fields.length; i++) {
386: IClassItem item = fields[i];
387: String name = item.getName();
388: String memb = MemberCustom.memberNameFromField(name, prefs,
389: suffs);
390: boolean use = true;
391: if (inclset != null) {
392: use = inclset.contains(memb.toLowerCase());
393: } else if (exclset != null) {
394: use = !exclset.contains(memb.toLowerCase());
395: }
396: if (use) {
397: fieldmap.put(memb, item);
398: }
399: }
400: return fieldmap;
401: }
402:
403: /**
404: * Find the most specific type for a property based on the access methods.
405: *
406: * @param gmeth read access method (<code>null</code> if not defined)
407: * @param smeth write access method (<code>null</code> if not defined)
408: * @param icl
409: * @return most specific type name
410: */
411: private String findPropertyType(IClassItem gmeth, IClassItem smeth,
412: IClassLocator icl) {
413: String type;
414: if (gmeth == null) {
415: if (smeth == null) {
416: throw new IllegalArgumentException(
417: "Internal error: no access methods known");
418: } else {
419: type = smeth.getArgumentType(0);
420: }
421: } else if (smeth == null) {
422: type = gmeth.getTypeName();
423: } else {
424: String gtype = gmeth.getTypeName();
425: String stype = smeth.getArgumentType(0);
426: IClass gclas = icl.getClassInfo(gtype);
427: if (gclas.isSuperclass(stype) || gclas.isImplements(stype)) {
428: type = gtype;
429: } else {
430: type = stype;
431: }
432: }
433: return type;
434: }
435:
436: /**
437: * Apply customizations to class to fill out members.
438: *
439: * @param icl class locator
440: */
441: public void apply(IClassLocator icl) {
442:
443: // initialize class information
444: m_classInformation = icl.getClassInfo(getName());
445: if (m_classInformation == null) {
446: throw new IllegalStateException(
447: "Internal error: unable to find class " + m_name);
448: }
449:
450: // inherit namespace directly from package level, if not specified
451: String ns = getSpecifiedNamespace();
452: if (ns == null) {
453: ns = getParent().getNamespace();
454: }
455: setNamespace(ns);
456:
457: // set the name(s) to be used if mapped
458: String cname = convertName(getSimpleName());
459: if (m_elementName == null) {
460: m_elementName = cname;
461: }
462: m_elementQName = new QName(getNamespace(), m_elementName);
463: if (m_typeName == null) {
464: m_typeName = cname;
465: }
466: m_typeQName = new QName(getNamespace(), m_typeName);
467:
468: // initialize maps with existing member customizations
469: HashMap namemap = new HashMap();
470: for (Iterator iter = getChildren().iterator(); iter.hasNext();) {
471: MemberCustom memb = (MemberCustom) iter.next();
472: String name = memb.getBaseName();
473: if (name != null) {
474: namemap.put(name, memb);
475: }
476: }
477:
478: // generate sets of names to be included or ignored
479: Set inclset = nameSet(m_includes);
480: Set exclset = nameSet(m_excludes);
481:
482: // first find members from property access methods
483: Map getmap = null;
484: Map setmap = null;
485: IClassItem[] methods = m_classInformation.getMethods();
486: GlobalCustom global = getGlobal();
487: if (global.isOutput()) {
488:
489: // find properties using read access methods
490: getmap = mapPropertyReadMethods(methods, inclset, exclset);
491: if (global.isInput()) {
492:
493: // find properties using write access methods
494: setmap = mapPropertyWriteMethods(methods, inclset,
495: exclset);
496:
497: // discard any read-only properties
498: for (Iterator iter = getmap.keySet().iterator(); iter
499: .hasNext();) {
500: if (!setmap.containsKey(iter.next())) {
501: iter.remove();
502: }
503: }
504:
505: }
506: }
507: if (global.isInput()) {
508:
509: // find properties using write access methods
510: setmap = mapPropertyWriteMethods(methods, inclset, exclset);
511:
512: }
513:
514: // find members from fields
515: Map fieldmap = mapFields(m_classInformation.getFields(),
516: getStripPrefixes(), getStripSuffixes(), inclset,
517: exclset);
518:
519: // get list of names selected for use by options
520: ArrayList names;
521: if (isPropertyAccess()) {
522: if (global.isOutput()) {
523: names = new ArrayList(getmap.keySet());
524: } else {
525: names = new ArrayList(setmap.keySet());
526: }
527: } else {
528: names = new ArrayList();
529: for (Iterator iter = fieldmap.keySet().iterator(); iter
530: .hasNext();) {
531: String name = (String) iter.next();
532: IClassItem field = (IClassItem) fieldmap.get(name);
533: int access = field.getAccessFlags();
534: if (!Modifier.isStatic(access)
535: && !Modifier.isTransient(access)) {
536: names.add(name);
537: }
538: }
539: }
540:
541: // build sets of required or optional members
542: Set optset = null;
543: if (m_optionals != null) {
544: optset = nameSet(m_optionals);
545: }
546: Set reqset = null;
547: if (m_requireds != null) {
548: reqset = nameSet(m_requireds);
549: }
550:
551: // process all members found in class
552: m_memberMap = new HashMap();
553: boolean auto = !getName().startsWith("java.")
554: && !getName().startsWith("javax.");
555: for (int i = 0; i < names.size(); i++) {
556:
557: // get basic member information
558: String name = (String) names.get(i);
559: MemberCustom cust = null;
560: IClassItem gmeth = (IClassItem) (getmap == null ? null
561: : getmap.get(name));
562: IClassItem smeth = (IClassItem) (setmap == null ? null
563: : setmap.get(name));
564: IClassItem field = (IClassItem) (fieldmap == null ? null
565: : fieldmap.get(name));
566:
567: // find the optional/required setting
568: Boolean req = null;
569: if (optset != null && optset.contains(name)) {
570: req = Boolean.FALSE;
571: }
572: if (reqset != null && reqset.contains(name)) {
573: req = Boolean.TRUE;
574: }
575:
576: // check for existing customization
577: if (namemap.containsKey(name)) {
578:
579: // fill in data missing from existing member customization
580: cust = (MemberCustom) namemap.get(name);
581: if (cust instanceof MemberFieldCustom) {
582: MemberFieldCustom mfcust = (MemberFieldCustom) cust;
583: mfcust.completeField((IClassItem) fieldmap
584: .get(name), req);
585: } else {
586: String type = findPropertyType(gmeth, smeth, icl);
587: MemberPropertyCustom mpcust = (MemberPropertyCustom) cust;
588: mpcust.completeProperty(gmeth, smeth, type, req);
589: }
590:
591: } else if (auto) {
592: if (isPropertyAccess()) {
593: if (gmeth != null || smeth != null) {
594:
595: // check for a collection property
596: MemberPropertyCustom pcust;
597: String type = findPropertyType(gmeth, smeth,
598: icl);
599: IClass info = icl.getClassInfo(type);
600: if (type.endsWith("[]")
601: || info
602: .isImplements("Ljava/util/Collection;")) {
603: pcust = new CollectionPropertyCustom(this ,
604: name);
605: } else {
606: pcust = new MemberPropertyCustom(this , name);
607: }
608:
609: // fill in the details of the property
610: pcust.completeProperty(gmeth, smeth, type, req);
611: cust = pcust;
612:
613: }
614: } else if (field != null) {
615:
616: // check for a collection field
617: MemberFieldCustom fcust;
618: String type = field.getTypeName();
619: IClass info = icl.getClassInfo(type);
620: if (type.endsWith("[]")
621: || info
622: .isImplements("Ljava/util/Collection;")) {
623: fcust = new CollectionFieldCustom(this , name);
624: } else {
625: fcust = new MemberFieldCustom(this , name);
626: }
627:
628: // fill in the details of the property
629: fcust.completeField(field, req);
630: cust = fcust;
631:
632: }
633: }
634:
635: // add customization to map
636: if (cust != null) {
637: m_memberMap.put(name, cust);
638: }
639: }
640:
641: // check for any supplied customizations that haven't been matched
642: for (Iterator iter = namemap.keySet().iterator(); iter
643: .hasNext();) {
644: String name = (String) iter.next();
645: if (!m_memberMap.containsKey(name)) {
646:
647: // find the optional/required setting
648: Boolean req = null;
649: if (optset != null && optset.contains(name)) {
650: req = Boolean.FALSE;
651: }
652: if (reqset != null && reqset.contains(name)) {
653: req = Boolean.TRUE;
654: }
655:
656: // complete the customization and add to map
657: MemberCustom cust = (MemberCustom) namemap.get(name);
658: cust.complete(null, req);
659: m_memberMap.put(name, cust);
660:
661: }
662: }
663: }
664:
665: /**
666: * Get customization information for a member by name. This method may only
667: * be called after {@link #apply(IClassLocator)}.
668: *
669: * @param name
670: * @return customization, or <code>null</code> if none
671: */
672: public MemberCustom getMember(String name) {
673: return (MemberCustom) m_memberMap.get(name);
674: }
675:
676: /**
677: * Get actual class information. This method may only be called after {@link
678: * #apply(IClassLocator)}.
679: *
680: * @return class information
681: */
682: public IClass getClassInformation() {
683: return m_classInformation;
684: }
685:
686: /**
687: * Get collection of members in class.
688: *
689: * @return members
690: */
691: public Collection getMembers() {
692: return m_memberMap.values();
693: }
694: }
|