001: /*
002: Copyright (c) 2003-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.def;
030:
031: import java.util.ArrayList;
032:
033: import org.jibx.binding.classes.BranchWrapper;
034: import org.jibx.binding.classes.ClassFile;
035: import org.jibx.binding.classes.ClassItem;
036: import org.jibx.binding.classes.ContextMethodBuilder;
037: import org.jibx.binding.classes.MethodBuilder;
038: import org.jibx.binding.model.ClassUtils;
039: import org.jibx.binding.util.IntegerCache;
040: import org.jibx.runtime.JiBXException;
041:
042: /**
043: * Property definition from binding. This organizes shared information for
044: * bindings linked to fields or get/set methods of an object, and provides
045: * methods for related code generation.
046: *
047: * @author Dennis M. Sosnoski
048: */
049: public class PropertyDefinition {
050: //
051: // Constants and such related to code generation.
052:
053: // recognized test-method signatures.
054: private static final String[] TEST_METHOD_SIGNATURES = {
055: "(Lorg/jibx/runtime/IMarshallingContext;)Z", "()Z" };
056:
057: // recognized get-method signatures.
058: private static final String[] GET_METHOD_SIGNATURES = {
059: "(Lorg/jibx/runtime/IMarshallingContext;)", "()" };
060:
061: //
062: // Actual instance data
063:
064: /** Reference to "this" property of object flag. */
065: private boolean m_isThis;
066:
067: /** Reference to implicit value from collection. */
068: private boolean m_isImplicit;
069:
070: /** Optional item flag. */
071: private boolean m_isOptional;
072:
073: /** Containing object context. */
074: private final IContextObj m_objContext;
075:
076: /** Fully qualified name of actual type of value. */
077: private final String m_typeName;
078:
079: /** Fully qualified name of declared type of value loaded. */
080: private final String m_getValueType;
081:
082: /** Fully qualified name of declared type of value stored. */
083: private final String m_setValueType;
084:
085: /** Information for field (if given, may be <code>null</code>). */
086: private final ClassItem m_fieldItem;
087:
088: /** Information for test method (if given, may be <code>null</code>). */
089: private final ClassItem m_testMethod;
090:
091: /** Information for get method (if given, may be <code>null</code>). */
092: private final ClassItem m_getMethod;
093:
094: /** Information for set method (if given, may be <code>null</code>). */
095: private final ClassItem m_setMethod;
096:
097: /**
098: * Constructor.
099: *
100: * @param parent containing binding definition structure
101: * @param obj containing object context
102: * @param type fully qualified name of type
103: * @param isthis "this" object reference flag
104: * @param opt optional property flag
105: * @param fname containing object field name for property (may be
106: * <code>null</code>)
107: * @param test containing object method to test for property present (may be
108: * <code>null</code>)
109: * @param get containing object method to get property value (may be
110: * <code>null</code>)
111: * @param set containing object method to set property value (may be
112: * <code>null</code>)
113: * @throws JiBXException if configuration error
114: */
115:
116: public PropertyDefinition(IContainer parent, IContextObj obj,
117: String type, boolean isthis , boolean opt, String fname,
118: String test, String get, String set) throws JiBXException {
119: m_objContext = obj;
120: m_isThis = isthis ;
121: m_isOptional = opt;
122: ClassFile cf = m_objContext.getBoundClass().getClassFile();
123: m_isImplicit = false;
124: String dtype = null;
125: String gtype = null;
126: String stype = null;
127: if (isthis ) {
128: if (type == null) {
129: dtype = gtype = stype = cf.getName();
130: } else {
131: dtype = gtype = stype = type;
132: }
133: }
134: if (fname == null) {
135: m_fieldItem = null;
136: } else {
137: m_fieldItem = cf.getField(fname);
138: dtype = gtype = stype = m_fieldItem.getTypeName();
139: }
140: if (test == null) {
141: m_testMethod = null;
142: } else {
143: if (opt) {
144: m_testMethod = cf.getMethod(test,
145: TEST_METHOD_SIGNATURES);
146: if (m_testMethod == null) {
147: throw new JiBXException("test-method " + test
148: + " not found in class " + cf.getName());
149: }
150: } else {
151: throw new JiBXException(
152: "Test method only allowed for optional properties");
153: }
154: }
155: if (get == null) {
156: m_getMethod = null;
157: } else {
158: m_getMethod = cf.getMethod(get, GET_METHOD_SIGNATURES);
159: if (m_getMethod == null) {
160: throw new JiBXException("get-method " + get
161: + " not found in class " + cf.getName());
162: } else {
163: gtype = m_getMethod.getTypeName();
164: if (dtype == null) {
165: dtype = gtype;
166: }
167: }
168: }
169: if (set == null) {
170: m_setMethod = null;
171: } else {
172:
173: // need to handle overloads, so generate possible signatures
174: ArrayList sigs = new ArrayList();
175: if (m_getMethod != null) {
176: String psig = ClassUtils.getSignature(gtype);
177: sigs.add("(" + psig
178: + "Lorg/jibx/runtime/IUnmarshallingContext;"
179: + ")V");
180: sigs.add("(" + psig + ")V");
181: }
182: if (type != null) {
183: String psig = ClassUtils.getSignature(type);
184: sigs.add("(" + psig
185: + "Lorg/jibx/runtime/IUnmarshallingContext;"
186: + ")V");
187: sigs.add("(" + psig + ")V");
188: }
189: if (m_fieldItem != null) {
190: String psig = m_fieldItem.getSignature();
191: sigs.add("(" + psig
192: + "Lorg/jibx/runtime/IUnmarshallingContext;"
193: + ")V");
194: sigs.add("(" + psig + ")V");
195: }
196: sigs
197: .add("(Ljava/lang/Object;Lorg/jibx/runtime/IUnmarshallingContext;)V");
198: sigs.add("(Ljava/lang/Object;)V");
199:
200: // set method needs verification of argument and return type
201: ClassItem setmeth = cf.getMethod(set, (String[]) sigs
202: .toArray(new String[0]));
203: if (setmeth == null) {
204:
205: // nothing known about signature, try anything by name
206: setmeth = cf.getMethod(set, "");
207: if (setmeth != null) {
208: if (setmeth.getArgumentCount() != 1
209: || !setmeth.getTypeName().equals("void")) {
210: setmeth = null;
211: }
212: }
213: }
214:
215: // check if method found
216: m_setMethod = setmeth;
217: if (m_setMethod == null) {
218: throw new JiBXException("set-method " + set
219: + " not found in class " + cf.getName());
220: } else {
221: stype = m_setMethod.getArgumentType(0);
222: if (dtype == null) {
223: dtype = stype;
224: }
225: }
226: }
227: if (gtype == null) {
228: gtype = "java.lang.Object";
229: }
230: m_getValueType = gtype;
231: m_setValueType = stype;
232:
233: // check that enough information is supplied
234: BindingDefinition root = parent.getBindingRoot();
235: if (!isthis && m_fieldItem == null) {
236: if (root.isInput() && m_setMethod == null) {
237: throw new JiBXException(
238: "Missing way to set value for input binding");
239: }
240: if (root.isOutput() && m_getMethod == null) {
241: throw new JiBXException(
242: "Missing way to get value for output binding");
243: }
244: }
245:
246: // check that type information is consistent
247: if (type == null) {
248: m_typeName = dtype;
249: } else {
250: m_typeName = type;
251: boolean valid = true;
252: if (isthis ) {
253: valid = ClassItem.isAssignable(dtype, type);
254: } else {
255: if (root.isInput()) {
256: valid = ClassItem
257: .isAssignable(type, m_setValueType)
258: || ClassItem.isAssignable(m_setValueType,
259: type);
260: }
261: if (valid && root.isOutput()) {
262: valid = ClassItem
263: .isAssignable(type, m_getValueType)
264: || ClassItem.isAssignable(m_getValueType,
265: type);
266: }
267: }
268: if (!valid) {
269: throw new JiBXException(
270: "Incompatible types for property definition");
271: }
272: }
273: }
274:
275: /**
276: * Constructor for "this" object reference.
277: *
278: * @param obj containing object context
279: * @param opt optional property flag
280: */
281:
282: public PropertyDefinition(IContextObj obj, boolean opt) {
283: m_objContext = obj;
284: m_isThis = true;
285: m_isImplicit = false;
286: m_isOptional = opt;
287: ClassFile cf = m_objContext.getBoundClass().getClassFile();
288: m_fieldItem = m_testMethod = m_getMethod = m_setMethod = null;
289: m_typeName = m_getValueType = m_setValueType = cf.getName();
290: }
291:
292: /**
293: * Constructor for implicit object reference.
294: *
295: * @param type object type supplied
296: * @param obj containing object context
297: * @param opt optional property flag
298: */
299:
300: public PropertyDefinition(String type, IContextObj obj, boolean opt) {
301: m_objContext = obj;
302: m_isImplicit = true;
303: m_isThis = false;
304: m_isOptional = opt;
305: m_fieldItem = m_testMethod = m_getMethod = m_setMethod = null;
306: m_typeName = m_getValueType = m_setValueType = type;
307: }
308:
309: /**
310: * Check if property is "this" reference for object.
311: *
312: * @return <code>true</code> if reference to "this", <code>false</code> if
313: * not
314: */
315: public boolean isThis() {
316: return m_isThis;
317: }
318:
319: /**
320: * Check if property is implicit value from collection.
321: *
322: * @return <code>true</code> if implicit, <code>false</code> if not
323: */
324: public boolean isImplicit() {
325: return m_isImplicit;
326: }
327:
328: /**
329: * Switch property from "this" to "implicit".
330: */
331: public void switchProperty() {
332: m_isThis = false;
333: m_isImplicit = true;
334: }
335:
336: /**
337: * Check if property is optional.
338: *
339: * @return <code>true</code> if optional, <code>false</code> if required
340: */
341: public boolean isOptional() {
342: return m_isOptional;
343: }
344:
345: /**
346: * Set flag for an optional property.
347: *
348: * @param opt <code>true</code> if optional property, <code>false</code> if
349: * not
350: */
351:
352: public void setOptional(boolean opt) {
353: m_isOptional = opt;
354: }
355:
356: /**
357: * Get property name. If a field is defined this is the same as the field;
358: * otherwise it is either the get method name (with leading "get" stripped,
359: * if present) or the set method (with leading "set" stripped, if present),
360: * whichever is found.
361: *
362: * @return name for this property
363: */
364:
365: public String getName() {
366: if (m_isThis) {
367: return "this";
368: } else if (m_fieldItem != null) {
369: return m_fieldItem.getName();
370: } else if (m_getMethod != null) {
371: String name = m_getMethod.getName();
372: if (name.startsWith("get") && name.length() > 3) {
373: name = name.substring(3);
374: }
375: return name;
376: } else if (m_setMethod != null) {
377: String name = m_setMethod.getName();
378: if (name.startsWith("set") && name.length() > 3) {
379: name = name.substring(3);
380: }
381: return name;
382: } else {
383: return "item";
384: }
385: }
386:
387: /**
388: * Get declared type fully qualified name.
389: *
390: * @return fully qualified class name of declared type
391: */
392:
393: public String getTypeName() {
394: return m_typeName;
395: }
396:
397: /**
398: * Get value type as fully qualified name for loaded property value.
399: *
400: * @return fully qualified class name of value type
401: */
402:
403: public String getGetValueType() {
404: return m_getValueType;
405: }
406:
407: /**
408: * Get value type as fully qualified name for stored property value.
409: *
410: * @return fully qualified class name of value type
411: */
412:
413: public String getSetValueType() {
414: return m_setValueType;
415: }
416:
417: /**
418: * Check if property has presence test. Code needs to be generated to check
419: * for the presence of the property if it is optional and either a test
420: * method is defined or the value is an object reference.
421: *
422: * @return <code>true</code> if presence test needed, <code>false</code> if
423: * not
424: */
425:
426: public boolean hasTest() {
427: return isOptional()
428: && !isImplicit()
429: && (m_testMethod != null || !ClassItem
430: .isPrimitive(m_typeName));
431: }
432:
433: /**
434: * Generate code to test if property is present. The generated code
435: * assumes that the top of the stack is the reference for the containing
436: * object, and consumes this value for the test. The target for the
437: * returned branch instruction must be set by the caller.
438: *
439: * @param mb method builder
440: * @return wrapper for branch instruction taken when property is missing
441: */
442:
443: public BranchWrapper genTest(ContextMethodBuilder mb) {
444:
445: // first check for supplied test method
446: if (m_testMethod != null) {
447:
448: // generate call to test method to check for property present
449: mb.addMethodExceptions(m_testMethod);
450: if (m_testMethod.isStatic()) {
451: mb.appendPOP();
452: }
453: if (m_testMethod.getArgumentCount() > 0) {
454: mb.loadContext();
455: }
456: mb.appendCall(m_testMethod);
457: return mb.appendIFEQ(this );
458:
459: } else if (!m_isThis && !m_isImplicit
460: && !ClassItem.isPrimitive(m_typeName)) {
461:
462: // generated instruction either loads a field value or calls a "get"
463: // method, as appropriate
464: if (m_getMethod == null) {
465: if (m_fieldItem.isStatic()) {
466: mb.appendPOP();
467: }
468: mb.appendGet(m_fieldItem);
469: } else {
470: if (m_getMethod.isStatic()) {
471: mb.appendPOP();
472: }
473: if (m_getMethod.getArgumentCount() > 0) {
474: mb.loadContext();
475: }
476: mb.addMethodExceptions(m_getMethod);
477: mb.appendCall(m_getMethod);
478: }
479: return mb.appendIFNULL(this );
480:
481: } else {
482: return null;
483: }
484: }
485:
486: /**
487: * Generate code to load property value to stack. The generated code
488: * assumes that the top of the stack is the reference for the containing
489: * object. It consumes this and leaves the actual value on the stack. If
490: * the property value is not directly accessible from the context of the
491: * method being generated this automatically constructs an access method
492: * and uses that method.
493: *
494: * @param mb method builder
495: * @throws JiBXException if configuration error
496: */
497:
498: public void genLoad(ContextMethodBuilder mb) throws JiBXException {
499:
500: // nothing to be done if called on "this" or implicit reference
501: if (!m_isThis && !m_isImplicit) {
502:
503: // first check direct access to property from method class
504: ClassFile from = mb.getClassFile();
505: ClassItem access = m_getMethod;
506: if (access == null) {
507: access = m_fieldItem;
508: }
509: if (access != null && !from.isAccessible(access)) {
510: access = m_objContext.getBoundClass().getLoadMethod(
511: access, mb.getClassFile());
512: }
513:
514: // generated instruction either loads a field value or calls a "get"
515: // method, as appropriate
516: if (access == null) {
517: Integer index = (Integer) mb.getKeyValue(m_setMethod);
518: mb.appendPOP();
519: if (index == null) {
520: mb.appendACONST_NULL();
521: } else {
522: mb.appendLoadLocal(index.intValue());
523: }
524: } else {
525: if (access.isStatic()) {
526: mb.appendPOP();
527: }
528: if (access.isMethod()) {
529: if (access.getArgumentCount() > 0) {
530: mb.loadContext();
531: }
532: mb.addMethodExceptions(access);
533: mb.appendCall(access);
534: } else {
535: mb.appendGet(access);
536: }
537: }
538:
539: // cast value if necessary to assure correct type
540: mb.appendCreateCast(m_getValueType, m_typeName);
541:
542: }
543: }
544:
545: /**
546: * Generate code to store property value from stack. The generated code
547: * assumes that the reference to the containing object and the value to be
548: * stored have already been pushed on the stack. It consumes these, leaving
549: * nothing. If the property value is not directly accessible from the
550: * context of the method being generated this automatically constructs an
551: * access method and uses that method.
552: *
553: * @param mb method builder
554: * @throws JiBXException if configuration error
555: */
556:
557: public void genStore(MethodBuilder mb) throws JiBXException {
558:
559: // check for cast needed to convert to actual type
560: if (!ClassItem.isPrimitive(m_setValueType)) {
561: mb.appendCreateCast(m_setValueType);
562: }
563:
564: // nothing to be done if called on "this" or implicit reference
565: if (!m_isThis && !m_isImplicit) {
566:
567: // first check direct access to property from method class
568: ClassFile from = mb.getClassFile();
569: ClassItem access = m_setMethod;
570: if (access == null) {
571: access = m_fieldItem;
572: }
573: if (!from.isAccessible(access)) {
574: access = m_objContext.getBoundClass().getStoreMethod(
575: access, mb.getClassFile());
576: }
577:
578: // save to local if no way of getting value
579: if (m_getMethod == null && m_fieldItem == null) {
580: mb.appendDUP();
581: Integer index = (Integer) mb.getKeyValue(m_setMethod);
582: if (index == null) {
583: int slot = mb.addLocal(null, ClassItem
584: .typeFromName(m_typeName));
585: index = IntegerCache.getInteger(slot);
586: mb.setKeyValue(m_setMethod, index);
587: } else {
588: mb.appendStoreLocal(index.intValue());
589: }
590: }
591:
592: // generated instruction either stores a field value or calls a
593: // "set" method, as appropriate
594: if (access.isMethod()) {
595: if (access.getArgumentCount() > 1) {
596:
597: // this test is ugly, needed because of backfill method
598: // calls from ValueChild
599: if (mb instanceof ContextMethodBuilder) {
600: ((ContextMethodBuilder) mb).loadContext();
601: } else {
602: mb.appendACONST_NULL();
603: }
604: }
605: mb.addMethodExceptions(access);
606: mb.appendCall(access);
607: } else {
608: mb.appendPut(access);
609: }
610: if (access.isStatic()) {
611: mb.appendPOP();
612: }
613: }
614: }
615:
616: // DEBUG
617: public String toString() {
618: StringBuffer text = new StringBuffer();
619: if (m_isOptional) {
620: text.append("optional ");
621: }
622: text.append("property ");
623: if (m_isThis) {
624: text.append("\"this\" ");
625: } else if (m_isImplicit) {
626: text.append("from collection ");
627: } else if (m_fieldItem != null) {
628: text.append(m_fieldItem.getName() + " ");
629: } else {
630: if (m_getMethod != null) {
631: text.append("from " + m_getMethod.getName() + " ");
632: }
633: if (m_setMethod != null) {
634: text.append("to " + m_setMethod.getName() + " ");
635: }
636: }
637: if (m_typeName != null) {
638: text.append("(" + m_typeName + ")");
639: }
640: return text.toString();
641: }
642: }
|