001: package uk.org.ponder.saxalizer;
002:
003: import java.lang.reflect.Field;
004: import java.lang.reflect.Method;
005:
006: import uk.org.ponder.util.AssertionException;
007: import uk.org.ponder.util.EnumerationConverter;
008: import uk.org.ponder.util.UniversalRuntimeException;
009:
010: /**
011: * SAXAccessMethod is a package private class that represents a
012: * SAXAccessMethodSpec that the SAXalizer has resolved to an actual method by
013: * means of reflection. For a set method, when the correct XML closing tag is
014: * seen, the method will be invoked with the just-constructed tag as argument.
015: * All construction of reflectors is done during the construction of this class.
016: *
017: * @author Antranig Basman (antranig@caret.cam.ac.uk)
018: * @author Aaron Zeckoski (aaronz@vt.edu)
019: */
020: public class SAXAccessMethod implements AccessMethod {
021: public static final Class[] emptyclazz = {};
022: public static final Object[] emptyobj = {};
023: /** The Field object corresponding to the child field, if there is
024: one. */
025: Field field;
026: Method getmethod; // The actual Method object to be invoked
027: Method setmethod;
028: /** The type of subobject (or superclass thereof) handled by this method */
029: Class clazz;
030: /** The actual (declared) return or field type in code (maybe container) */
031: Class accessclazz;
032: /** The owning class, an AccessMethod for some particular user-visible class */
033: Class parentclazz;
034: /** The class where the physical declaration of this access member appears */
035: Class declaringclazz;
036:
037: public String tagname;
038: /** Uses the new "tag*" polymorphic nickname scheme */
039: boolean ispolymorphic;
040: /** A collection rather than a single object is being addressed */
041: public boolean ismultiple;
042: /** A more specific set method has been supplied than get method. */
043: public boolean isexactsetter;
044: /** if "ismultiple" is this ONLY enumerable and not denumerable? */
045: boolean isenumonly;
046: /** Is this property typed either BeanLocator or Map? */
047: boolean ismappable;
048: /** if this is a "black hole" setter for ignoring properties */
049: boolean isdevnull;
050:
051: // Note that enumerations are the only things which are enumerable but not
052: // denumerable.
053:
054: private SAXAccessMethod(Class parentclazz, String tagname) {
055: this .parentclazz = parentclazz;
056: if (tagname != null && tagname.endsWith("*")) {
057: tagname = tagname.substring(0, tagname.length() - 1);
058: ispolymorphic = true;
059: }
060: this .tagname = tagname;
061: }
062:
063: // returns any (one-argument) method with the required name in the specified
064: // class, regardless of type. This is called by the constructor when it
065: // is trying to automatically infer set types.
066: private static Method findSetMethod(Class tosearch,
067: String methodname) {
068: Method[] methods = tosearch.getMethods();
069: for (int i = 0; i < methods.length; ++i) {
070: Method this method = methods[i];
071: if (this method.getName().equals(methodname)
072: && this method.getParameterTypes().length == 1
073: && this method.getReturnType().equals(Void.TYPE)) {
074: return this method;
075: }
076: }
077: return null;
078: }
079:
080: protected SAXAccessMethod() {
081:
082: }
083:
084: public SAXAccessMethod(SAXAccessMethodSpec m, Class parentclazz) {
085: this (parentclazz, m.xmlname);
086:
087: if (m.fieldname != null) {
088: try {
089: field = parentclazz.getField(m.fieldname);
090: } catch (Throwable t) {
091: throw UniversalRuntimeException.accumulate(t,
092: "Unable to find field with name " + m.fieldname
093: + " in class " + parentclazz);
094: }
095: // record the specified class name if there was one.
096: // TODO: for containers like StringSet we should try to look up
097: // container/containee information from DefaultInferrer. Semantics
098: // of this are slightly unclear... this is not a "default" mapping, BUT
099: // ERM! byXMLNameSafe appears to try to fill in m.clazz by itself, does
100: // this work?
101: accessclazz = field.getType();
102: clazz = m.clazz == null ? accessclazz : m.clazz;
103: checkEnumerable(accessclazz);
104: } else {
105: if (m.getmethodname != null) {
106: try {
107: getmethod = parentclazz.getMethod(m.getmethodname,
108: emptyclazz);
109: } catch (Throwable t) {
110: throw UniversalRuntimeException.accumulate(t,
111: "Unable to find GET method with name "
112: + m.getmethodname + " in class "
113: + parentclazz);
114: }
115: accessclazz = getmethod.getReturnType();
116: if (!checkEnumerable(accessclazz) && m.clazz != null
117: && !m.clazz.isAssignableFrom(accessclazz)) {
118: throw new AssertionException(
119: "Actual return type of get method \""
120: + getmethod
121: + "\" is not assignable to advertised type of "
122: + m.clazz);
123: }
124: }
125: if (m.setmethodname != null) {
126: if (m.clazz == null) {
127: // infer the type of a set method by looking for a method of the
128: // same name. If you have created two such, you should be destroyed
129: // anyway.
130: setmethod = findSetMethod(parentclazz,
131: m.setmethodname);
132: if (setmethod == null) {
133: throw new UniversalRuntimeException(
134: "Unable to find a SET method with name "
135: + m.setmethodname
136: + " in class " + parentclazz);
137: }
138: m.clazz = setmethod.getParameterTypes()[0];
139: } else { // the spec specifies the actual object type
140: try {
141: setmethod = parentclazz.getMethod(
142: m.setmethodname,
143: new Class[] { m.clazz });
144: } catch (Throwable t) {
145: setmethod = findSetMethod(parentclazz,
146: m.setmethodname);
147: Class setaccessclazz = setmethod
148: .getParameterTypes()[0];
149: boolean setismultiple = EnumerationConverter
150: .isDenumerable(setaccessclazz);
151: // if it didn't have exactly the right type, last-ditch search for a compatible setter.
152: if (setmethod == null
153: || (!setaccessclazz
154: .isAssignableFrom(m.clazz) && !setismultiple)) {
155: throw (UniversalRuntimeException
156: .accumulate(
157: t,
158: "Unable to find SET method with name "
159: + m.setmethodname
160: + " accepting argument "
161: + m.clazz
162: + " in class "
163: + parentclazz));
164: }
165: }
166: }
167: clazz = m.clazz;
168: if (accessclazz == null) {
169: // avoid overwriting any container type discovered from field or get
170: accessclazz = clazz;
171: }
172: } // end if there is a set method
173: if (clazz == null) { // triggers if there is get method, no set method and no m info
174: clazz = accessclazz;
175: }
176: if (m.accesstype.equals(SAXAccessMethodSpec.ACCESS_IGNORE)) {
177: isdevnull = true;
178: }
179: // if there is only a get method (as for maps) the actual type will not
180: // be determined yet
181: } // end if not a fieldname
182: if (setmethod != null && getmethod != null) {
183: isexactsetter = !setmethod.getParameterTypes()[0]
184: .equals(getmethod.getReturnType());
185: }
186:
187: // populate the declaringclazz variable in whichever method is appropriate
188: if (field != null) {
189: declaringclazz = field.getDeclaringClass();
190: } else if (setmethod != null) {
191: declaringclazz = setmethod.getDeclaringClass();
192: } else if (getmethod != null) {
193: declaringclazz = getmethod.getDeclaringClass();
194: }
195:
196: // Antranig's version of the code block above which is more "understandable" -AZ
197: // declaringclazz = (field == null ? (setmethod == null ?
198: // getmethod.getDeclaringClass() : setmethod.getDeclaringClass() ) :
199: // field.getDeclaringClass() );
200:
201: // TODO: some weird code here for "default" tags - what on earth does this do?
202: if (tagname != null && tagname.equals("")) {
203: if (!canGet()) {
204: throw new UniversalRuntimeException(
205: "No GET scheme supplied for mapped default tag");
206: }
207: if (!EnumerationConverter.isMappable(accessclazz)) {
208: throw new UniversalRuntimeException(
209: "Default mapped type does not map onto a mappable type");
210: }
211: // unless specified by now, remain mapped values as strings.
212: // TODO make sure defaultinferring happens before now.
213: clazz = m.clazz == null ? String.class : m.clazz;
214: }
215: }
216:
217: /**
218: * Determines whether the supplied class, the actualtype of a field or a get
219: * method, is enumerable. If it is, the ismultiple field is set, and the
220: * isenumeration field is conditionally set.
221: *
222: * @param clazz2
223: */
224: private boolean checkEnumerable(Class clazz) {
225: ismultiple = EnumerationConverter.isEnumerable(clazz);
226: if (ismultiple) {
227: isenumonly = !EnumerationConverter.isDenumerable(clazz);
228: }
229: ismappable = EnumerationConverter.isMappable(clazz);
230: return ismultiple;
231: }
232:
233: public String toString() {
234: return "SAXAccessMethod name " + getmethod.getName()
235: + " parent " + parentclazz + " tagname " + tagname;
236: }
237:
238: public Object getChildObject(Object parent) {
239: try {
240: if (field != null) {
241: return field.get(parent);
242: } else
243: return getmethod.invoke(parent, emptyobj);
244: } catch (Throwable t) {
245: throw UniversalRuntimeException.accumulate(t,
246: "Error acquiring child object " + tagname
247: + " of object " + parent + " "
248: + parent.getClass());
249: }
250: }
251:
252: public void setChildObject(Object parent, Object newchild) {
253: try {
254: if (field != null) {
255: field.set(parent, newchild);
256: } else if (setmethod != null) {
257: setmethod.invoke(parent, new Object[] { newchild });
258: }
259: } catch (Throwable t) {
260: throw UniversalRuntimeException.accumulate(t,
261: "Error setting child " + tagname + " of parent "
262: + parent + " to value " + newchild);
263: }
264: }
265:
266: /**
267: * Determines whether this method can be used for getting.
268: *
269: * @return
270: */
271: public boolean canGet() {
272: return (field != null || getmethod != null);
273: }
274:
275: /**
276: * Determines whether this method can be used for setting.
277: *
278: * @return
279: */
280: public boolean canSet() {
281: return (field != null || setmethod != null || isdevnull);
282: }
283:
284: /**
285: * Determines whether the return type of this method is assignable to
286: * Enumeration, which is interpreted as indicating a non-settable multiple
287: * value, in the case that there is no individual set method.
288: */
289: public boolean isEnumeration() {
290: return isenumonly;
291: }
292:
293: /**
294: * Determines whether this GET method result may be used for the delivery of multiple
295: * subobjects. If it is, the object delivered by Get may be converted into a
296: * receiver by EnumerationConverter.getDenumeration(oldinstance).
297: */
298: public boolean isDenumerable() {
299: return ismultiple && !isenumonly && !isexactsetter;
300: }
301:
302: /** The type of subobject that this method deals in * */
303: public Class getAccessedType() {
304: return clazz;
305: }
306:
307: public Class getDeclaredType() {
308: return accessclazz;
309: }
310:
311: public String getPropertyName() {
312: return tagname;
313: }
314:
315: public Class getDeclaringClass() {
316: return declaringclazz;
317: }
318:
319: }
|