001: package uk.org.ponder.saxalizer;
002:
003: import java.util.ArrayList;
004: import java.util.Collections;
005: import java.util.Enumeration;
006: import java.util.Map;
007:
008: import uk.org.ponder.arrayutil.ArrayEnumeration;
009: import uk.org.ponder.beanutil.BeanLocator;
010: import uk.org.ponder.beanutil.PropertyAccessor;
011: import uk.org.ponder.beanutil.WriteableBeanLocator;
012: import uk.org.ponder.beanutil.support.BeanLocatorPropertyAccessor;
013: import uk.org.ponder.beanutil.support.IndexedPropertyAccessor;
014: import uk.org.ponder.beanutil.support.MapPropertyAccessor;
015: import uk.org.ponder.errorutil.PropertyException;
016: import uk.org.ponder.reflect.ClassGetter;
017: import uk.org.ponder.saxalizer.mapping.SAXalizerMapperEntry;
018: import uk.org.ponder.util.Logger;
019: import uk.org.ponder.util.UniversalRuntimeException;
020:
021: /**
022: * One instance of a MethodAnalyser is stored for each SAXalizable class that
023: * the SAXalizer discovers; this instance is returned when a call is made to
024: * <code>getMethodAnalyser</code> with an object of the SAXalizable class as
025: * argument. MethodAnalysers are cached in a static hashtable indexed by the
026: * SAXalizable class.
027: * <p>
028: * Some "bean-sense" has been retroactively blown into this class, which dates
029: * from the dinosaur SAXalizer days of 2000, with the retrofit of the
030: * <code>PropertyAccessor</code> interface. Its structure still needs a little
031: * work though, since it still maintains separate collections for "tag" and
032: * "attribute" methods &c.
033: */
034: public class MethodAnalyser implements PropertyAccessor {
035: public Class targetclass;
036: /**
037: * Each of the four types of SAXAccessMethods supported, being get and set
038: * methods for subtags and attributes.
039: */
040: public SAXAccessMethodHash tagmethods;
041: public SAXAccessMethodHash attrmethods;
042: public SAXAccessMethod bodymethod;
043: /** A flat array of ALL accessors for the target class. This will be the most
044: * efficient means of using introspection information, and is populated on
045: * construction of this MethodAnalyser.
046: */
047: public SAXAccessMethod[] allgetters;
048:
049: private void assembleGetters() {
050: ArrayList accumulate = new ArrayList();
051: for (SAMIterator tagget = tagmethods.getGetEnumeration(); tagget
052: .valid(); tagget.next()) {
053: accumulate.add(tagget.get());
054: }
055: for (SAMIterator tagget = attrmethods.getGetEnumeration(); tagget
056: .valid(); tagget.next()) {
057: accumulate.add(tagget.get());
058: }
059: if (bodymethod != null) {
060: accumulate.add(allgetters);
061: }
062: allgetters = new SAXAccessMethod[accumulate.size()];
063: for (int i = 0; i < accumulate.size(); ++i) {
064: allgetters[i] = (SAXAccessMethod) accumulate.get(i);
065: }
066: }
067:
068: public AccessMethod getAccessMethod(String tagname) {
069: SAXAccessMethod method = tagmethods.get(tagname);
070: if (method == null) {
071: method = attrmethods.get(tagname);
072: }
073: if (method == null
074: && WriteableBeanLocator.class
075: .isAssignableFrom(targetclass)) {
076: return new WBLAccessMethod(WriteableBeanLocator.class,
077: tagname);
078: }
079: return method;
080: }
081:
082: // ****** Begin implementation of PropertyAccessor interface
083: public boolean canSet(String name) {
084: AccessMethod accessmethod = getAccessMethod(name);
085: return accessmethod == null ? false : accessmethod.canSet();
086: }
087:
088: public void setProperty(Object parent, String name, Object value) {
089: AccessMethod accessmethod = getAccessMethod(name);
090: if (accessmethod == null) {
091: throw UniversalRuntimeException.accumulate(
092: new PropertyException(), "Property " + name
093: + " of object " + parent.getClass()
094: + " not found");
095: } else if (!accessmethod.canSet()) {
096: throw UniversalRuntimeException.accumulate(
097: new PropertyException(), "Property " + name
098: + " of object " + parent.getClass()
099: + " is not writeable");
100: }
101: accessmethod.setChildObject(parent, value);
102: }
103:
104: public void unlink(Object parent, String name) {
105: AccessMethod accessmethod = getAccessMethod(name);
106: if (accessmethod == null) {
107: throw UniversalRuntimeException.accumulate(
108: new PropertyException(), "Property " + name
109: + " of object " + parent.getClass()
110: + " not found");
111: }
112: accessmethod.setChildObject(parent, null);
113: }
114:
115: public boolean canGet(String name) {
116: AccessMethod accessmethod = getAccessMethod(name);
117: return accessmethod == null ? false : accessmethod.canGet();
118: }
119:
120: public Object getProperty(Object parent, String name) {
121: AccessMethod accessmethod = getAccessMethod(name);
122: if (accessmethod == null) {
123: throw UniversalRuntimeException.accumulate(
124: new PropertyException(), "Property " + name
125: + " of object " + parent.getClass()
126: + " not found");
127: }
128: return accessmethod.getChildObject(parent);
129: }
130:
131: public Class getPropertyType(Object parent, String name) {
132: AccessMethod accessmethod = getAccessMethod(name);
133: if (accessmethod == null) {
134: throw UniversalRuntimeException.accumulate(
135: new PropertyException(), "Property " + name
136: + " of " + targetclass + " not found ");
137: }
138: return accessmethod.getAccessedType();
139: }
140:
141: public boolean isMultiple(Object parent, String name) {
142: AccessMethod accessmethod = getAccessMethod(name);
143: if (accessmethod == null) {
144: throw UniversalRuntimeException.accumulate(
145: new PropertyException(), "Property " + name
146: + " of " + targetclass + " not found");
147: }
148: return accessmethod.isDenumerable();
149: }
150:
151: // ****** End implementation of PropertyAccessor interface.
152: /**
153: * Given an object to be serialised/deserialised, return a MethodAnalyser
154: * object containing a hash of Method and Field accessors. The
155: * <code>context</code> stores a hash of these analysers so they are only
156: * ever computed once per context per object class analysed.
157: *
158: * @param o Either an object instance to be investigated, or an object class.
159: * If a class is specified and no analyser is registered, a new
160: * object will be created using newInstance() to be queried.
161: */
162:
163: static MethodAnalyser constructMethodAnalyser(Class objclass,
164: SAXalizerMappingContext context) {
165:
166: SAXalizerMapperEntry entry = context.mapper.byClass(objclass);
167: try {
168: MethodAnalyser togo = new MethodAnalyser(objclass, entry,
169: context);
170: return togo;
171: } catch (Exception e) {
172: throw UniversalRuntimeException.accumulate(e,
173: "Error constructing method analyser for "
174: + objclass);
175: }
176: }
177:
178: public static PropertyAccessor getPropertyAccessor(Object o,
179: SAXalizerMappingContext context) {
180: if (o instanceof BeanLocator) {
181: return BeanLocatorPropertyAccessor.instance;
182: } else if (o instanceof Map) {
183: return MapPropertyAccessor.instance;
184: } else if (IndexedPropertyAccessor.isIndexed(o.getClass())) {
185: return context.getIndexedPropertyAccessor();
186: } else
187: return context.getAnalyser(o.getClass());
188: }
189:
190: private void condenseMethods(SAMSList existingmethods,
191: Enumeration newmethods, String xmlform) {
192: while (newmethods.hasMoreElements()) {
193: SAXAccessMethodSpec nextentry = (SAXAccessMethodSpec) newmethods
194: .nextElement();
195: // fuse together any pairs of methods that refer to the same tag/property
196: // name(xmlname)
197: // as getters and setters.
198: if (nextentry.xmlform.equals(xmlform)) {
199: SAXAccessMethodSpec previous = existingmethods
200: .byXMLName(nextentry.xmlname);
201: if (previous != null) {
202: SAXAccessMethodSpec setmethod = null;
203: if (previous.setmethodname != null)
204: setmethod = previous;
205: if (nextentry.setmethodname != null) {
206: if (setmethod != null) {
207: throw new UniversalRuntimeException(
208: "Duplicate set method specification for tag "
209: + previous.xmlname
210: + " with java type "
211: + previous.clazz);
212: }
213: setmethod = nextentry;
214: previous.setmethodname = nextentry.setmethodname;
215: }
216: if (setmethod == null) {
217: throw new UniversalRuntimeException(
218: "Neither of specifications " + previous
219: + " and " + nextentry
220: + " defines a set method");
221: }
222: // The "set" method will in general have a more precise argument type.
223: previous.clazz = setmethod.clazz;
224: if (nextentry.getmethodname != null) {
225: previous.getmethodname = nextentry.getmethodname;
226: }
227: } else {
228: existingmethods.add(nextentry);
229: }
230: }
231: }
232: }
233:
234: SAXAccessMethodSpec bodymethodspec = null;
235:
236: public void checkBodyMethodSpec(SAXAccessMethodSpec bodymethodspec) {
237: if (bodymethodspec.xmlform.equals(SAXAccessMethodSpec.XML_BODY)) {
238: if (this .bodymethodspec != null) {
239: throw new UniversalRuntimeException(
240: "Duplicate body method spec " + bodymethodspec);
241: }
242: this .bodymethodspec = bodymethodspec;
243: }
244: }
245:
246: // Note that these two methods have side-effect on bodymethodspec
247: private void absorbSAMSArray(SAXAccessMethodSpec[] setmethods,
248: SAMSList tagMethods, SAMSList attrMethods) {
249: condenseMethods(tagMethods, new ArrayEnumeration(setmethods),
250: SAXAccessMethodSpec.XML_TAG);
251: condenseMethods(attrMethods, new ArrayEnumeration(setmethods),
252: SAXAccessMethodSpec.XML_ATTRIBUTE);
253: if (setmethods != null) {
254: for (int i = 0; i < setmethods.length; ++i) {
255: checkBodyMethodSpec(setmethods[i]);
256: }
257: }
258: }
259:
260: private void absorbSAMSList(SAXalizerMapperEntry entry,
261: SAMSList tagMethods, SAMSList attrMethods) {
262: condenseMethods(tagMethods, Collections.enumeration(entry
263: .getSAMSList()), SAXAccessMethodSpec.XML_TAG);
264: condenseMethods(attrMethods, Collections.enumeration(entry
265: .getSAMSList()), SAXAccessMethodSpec.XML_ATTRIBUTE);
266: for (int i = 0; i < entry.size(); ++i) {
267: checkBodyMethodSpec(entry.specAt(i));
268: }
269: }
270:
271: /**
272: * This constructor locates SAXAccessMethodSpec objects for objects of the
273: * supplied class from all available static and dynamic sources, sorts them
274: * into tag and attribute methods while condensing together set and get
275: * specifications into single entries, and returns a MethodAnalyser object
276: * with the specs resolved into Method and Field accessors ready for use.
277: *
278: * @param objclass The class of the object to be inspected.
279: * @param o Either the object to be inspected for accessors, or its class in
280: * the case construction is to be deferred until the last possible
281: * moment (it implements SAXalizable &c)
282: * @param entry A SAXalizerMapperEntry object already determined from dynamic
283: * sources.
284: * @param context The global mapping context.
285: */
286: MethodAnalyser(Class objclass, SAXalizerMapperEntry entry,
287: SAXalizerMappingContext context) {
288: targetclass = objclass;
289: bodymethodspec = null;
290: SAMSList tagMethods = new SAMSList();
291: SAMSList attrMethods = new SAMSList();
292: boolean defaultinferrible = context.inferrer != null
293: && (context.inferrer.isDefaultInferrible(objclass) || entry != null
294: && entry.defaultible);
295: // source 1: dynamic info from mapper file takes precendence
296: if (entry != null) {
297: // do not absorb entry if defaultinferrible, since it will be done again
298: // later.
299: if (!defaultinferrible) {
300: absorbSAMSList(entry, tagMethods, attrMethods);
301: }
302: } else {
303: if (SAXalizable.class.isAssignableFrom(objclass)
304: || SAXalizableAttrs.class
305: .isAssignableFrom(objclass)
306: || DeSAXalizable.class.isAssignableFrom(objclass)
307: || DeSAXalizableAttrs.class
308: .isAssignableFrom(objclass)) {
309: // this branch will become gradually more deprecated - info should move
310: // into mapping files or else be default.
311: Object o = ClassGetter.construct(objclass);
312: // source 2: static info from interfaces is second choice
313: // System.out.println("MethodAnalyser called for object "+o);
314: if (o instanceof SAXalizable) {
315: SAXalizable so = (SAXalizable) o;
316: SAXAccessMethodSpec[] setMethods = so
317: .getSAXSetMethods();
318: SAXAccessMethodSpec.convertToSetSpec(setMethods);
319: absorbSAMSArray(setMethods, tagMethods, attrMethods);
320: }
321: if (o instanceof SAXalizableAttrs) { // now do the same for attributes
322: SAXalizableAttrs sao = (SAXalizableAttrs) o;
323: SAXAccessMethodSpec[] setAttrMethods = sao
324: .getSAXSetAttrMethods();
325: if (setAttrMethods != null) {
326: Logger.println("MethodAnalyser found "
327: + setAttrMethods.length
328: + " setattr methods for "
329: + o.getClass(),
330: Logger.DEBUG_INFORMATIONAL);
331: }
332: SAXAccessMethodSpec
333: .convertToAttrSpec(setAttrMethods);
334: SAXAccessMethodSpec
335: .convertToSetSpec(setAttrMethods);
336: absorbSAMSArray(setAttrMethods, tagMethods,
337: attrMethods);
338: }
339: if (o instanceof DeSAXalizable) {
340: // construct array of SAXAccessMethods for DeSAXalizable objects
341: DeSAXalizable doz = (DeSAXalizable) o;
342: SAXAccessMethodSpec[] getMethods = doz
343: .getSAXGetMethods();
344: absorbSAMSArray(getMethods, tagMethods, attrMethods);
345: }
346: if (o instanceof DeSAXalizableAttrs) { // now do the same for
347: // attributes
348: DeSAXalizableAttrs sao = (DeSAXalizableAttrs) o;
349: SAXAccessMethodSpec[] getAttrMethods = sao
350: .getSAXGetAttrMethods();
351: if (getAttrMethods != null) {
352: SAXAccessMethodSpec
353: .convertToAttrSpec(getAttrMethods);
354: Logger.println("MethodAnalyser found "
355: + getAttrMethods.length
356: + " getattr methods for " + o,
357: Logger.DEBUG_INFORMATIONAL);
358: }
359: absorbSAMSArray(getAttrMethods, tagMethods,
360: attrMethods);
361: }
362: }
363: }
364: // Source 3: if no accessors have so far been discovered, try to infer some
365: // using an inferrer if one is set.
366: if (context.inferrer != null
367: && (tagMethods.size() == 0 && attrMethods.size() == 0)
368: || defaultinferrible) {
369: entry = context.inferrer.inferEntry(objclass, entry);
370: absorbSAMSList(entry, tagMethods, attrMethods);
371: }
372:
373: tagmethods = new SAXAccessMethodHash(tagMethods, objclass);
374: attrmethods = new SAXAccessMethodHash(attrMethods, objclass);
375: if (bodymethodspec != null) {
376: bodymethod = new SAXAccessMethod(bodymethodspec, objclass);
377: }
378: bodymethodspec = null;
379: assembleGetters();
380: }
381:
382: }
|