001: package com.xoetrope.data.pojo;
002:
003: import com.xoetrope.carousel.visualizer.TreeNodeCaption;
004: import java.lang.reflect.InvocationHandler;
005: import java.lang.reflect.InvocationTargetException;
006: import java.lang.reflect.Method;
007: import java.lang.reflect.Type;
008: import java.lang.reflect.TypeVariable;
009: import java.net.URL;
010: import java.util.ArrayList;
011: import java.util.Collection;
012: import java.util.Enumeration;
013: import java.util.Hashtable;
014: import java.util.Vector;
015: import net.xoetrope.data.XDataSource;
016: import net.xoetrope.debug.DebugLogger;
017: import net.xoetrope.editor.project.XEditorProject;
018: import net.xoetrope.editor.project.XEditorProjectManager;
019: import net.xoetrope.editor.project.pages.IEditorUtility;
020: import net.xoetrope.optional.data.pojo.XPojoAdapter;
021: import net.xoetrope.optional.data.pojo.XPojoContext;
022: import net.xoetrope.optional.data.pojo.XPojoDataSource;
023: import net.xoetrope.optional.data.pojo.XPojoHelper;
024: import net.xoetrope.optional.data.pojo.XPojoModel;
025: import net.xoetrope.optional.data.pojo.XPojoRoot;
026: import net.xoetrope.optional.resources.XProjectClassLoader;
027: import net.xoetrope.xml.XmlElement;
028: import net.xoetrope.xui.XProject;
029: import net.xoetrope.xui.build.BuildProperties;
030: import net.xoetrope.xui.data.XBaseModel;
031: import net.xoetrope.xui.data.XModel;
032: import net.xoetrope.xui.helper.ReflectionHelper;
033:
034: /**
035: * <p>A data source for working with POJOs in the data visualiser.</p>
036: * <p> Copyright (c) Xoetrope Ltd., 2001-2007, This software is licensed under
037: * the GNU Public License (GPL), please see license.txt for more details. If
038: * you make commercial use of this software you must purchase a commercial
039: * license from Xoetrope.</p>
040: */
041: public class XPojoDataSourceEx extends XDataSource {
042: protected static final String[] GETTER_PREFIXES = { "get", "is",
043: "find" };
044: protected static final String[] SETTER_PREFIXES = { "set" };
045:
046: protected XPropertiesRetriever propertiesRetriever;
047: protected Object pojoContext;
048: protected XModel pojoRootModel;
049: protected Hashtable overrides;
050: protected Hashtable adapters;
051: protected ArrayList modelListeners;
052:
053: /**
054: * Creates new instance of XPojoDataSource
055: * @param project the owning project
056: */
057: public XPojoDataSourceEx(XProject project) {
058: super (project);
059: propertiesRetriever = new PropertiesRetriever();
060: pojoContext = null;
061: overrides = new Hashtable();
062: adapters = new Hashtable();
063: }
064:
065: /**
066: * Returns the finder arguments values holder.
067: */
068: public XPropertiesRetriever getPropertiesRetriever() {
069: return propertiesRetriever;
070: }
071:
072: /**
073: * Returns the pojo context object.
074: * @return pojo context object
075: */
076: public Object getPojoContext() {
077: return pojoContext;
078: }
079:
080: /**
081: * Gets the adapter for the specified class
082: * @param pojoClass the class whose adapter is
083: * to be returned
084: * @return the adapter
085: */
086: public XPojoAdapterEx getAdapter(Class pojoClass) {
087: XPojoAdapterEx adapter = null;
088: if (XPojoHelper.needsAdapter(pojoClass)) {
089: String pojoClassName = pojoClass.getName();
090: adapter = (XPojoAdapterEx) adapters.get(pojoClassName);
091: if (adapter == null) {
092: adapter = createAdapter(pojoClass);
093: adapters.put(pojoClassName, adapter);
094: overrideAdapter(adapter);
095: adapter.getProperties().createKeyTables();
096: }
097: }
098: return adapter;
099: }
100:
101: /**
102: * Creates a new instance of XPojoAdapterEx
103: * @param pojoClass the class to be adapted
104: */
105: protected XPojoAdapterEx createAdapter(Class pojoClass) {
106: return (new XPojoAdapterEx(pojoClass, this ));
107: }
108:
109: /**
110: * Overrides the adapter, adds the customization specified
111: * by the configuration.
112: * @param adapter the adapter to be customized
113: */
114: protected void overrideAdapter(XPojoAdapterEx adapter) {
115: XmlElement override = (XmlElement) overrides.get(adapter
116: .getAdaptedClass().getName());
117: if (override != null) {
118: Enumeration enumOverrides = override.getChildren()
119: .elements();
120: while (enumOverrides.hasMoreElements()) {
121: XmlElement propertyElement = (XmlElement) enumOverrides
122: .nextElement();
123: customizeProperty(adapter, propertyElement);
124: }
125: }
126: }
127:
128: protected void customizeProperty(XPojoAdapterEx adapter,
129: XmlElement propertyElement) {
130: String elementName = propertyElement.getName();
131: if ("property".equals(elementName)) {
132: String propertyName = propertyElement.getAttribute("id");
133: String getterName = propertyElement.getAttribute("getter");
134: String setterName = propertyElement.getAttribute("setter");
135: adapter.customizePojoProperty(propertyName, getterName,
136: setterName);
137:
138: // add the type of collection elements, if one specified
139: String collectionOf = propertyElement
140: .getAttribute("collectionOf");
141: if (collectionOf != null)
142: adapter.addCollectionType(propertyName, collectionOf);
143: } else if ("transient_property".equals(elementName)) {
144: String propertyName = propertyElement.getAttribute("id");
145: String className = propertyElement.getAttribute("class");
146: adapter.addTransientProperty(propertyName, className);
147:
148: // add the type of collection elements, if one specified
149: String collectionOf = propertyElement
150: .getAttribute("collectionOf");
151: if (collectionOf != null)
152: adapter.addTransientCollectionType(propertyName,
153: collectionOf);
154: }
155: }
156:
157: /**
158: * Adapt a POJO for use in the model, applying any overrides to the POJO's
159: * API defined in the data source's configuration file (by default pojo.xml)
160: * @param pojo the object being adapted
161: * @param parentNode the parent model node
162: * @return the pojo model node encapsulating the pojo
163: */
164: public XPojoModelVis adaptPojo(Object pojo, XModel parentNode) {
165: if (pojo == null)
166: return null;
167: // do not adapt existing model nodes
168: if (pojo instanceof XPojoModelVis)
169: return (XPojoModelEx) pojo;
170: // wrap the pojo
171: XPojoModelEx model = new XPojoModelEx(parentNode, pojo, this );
172: String value = getDispValue(pojo);
173: if (model != null)
174: model.setCaption(value + ": ");
175: return model;
176: }
177:
178: /**
179: * Adapts the POJO returned by <code>method</code> invoked on a <code>parent</code>
180: * object.
181: * @param parentNode parent model node
182: * @param method the Method that returns the pojo which is to be adapted
183: * @return the pojo model node encapsulating the pojo
184: */
185: public XPojoModelVis adaptPojo(Method getter, Method setter,
186: String propertyName, XModel parentNode) {
187: return (new XPojoModelEx(parentNode, getter, setter,
188: propertyName, this ));
189: }
190:
191: /**
192: * Adapts the transient pojo of the specified type.
193: * @param pojoClass the type of the underlying pojo
194: * @param propertyName the name of the transient property
195: * @param parentNode parent model node
196: */
197: public XPojoModelVis adaptPojo(Class pojoClass,
198: String propertyName, XModel parentNode) {
199: return (new XPojoModelEx(parentNode, pojoClass, propertyName,
200: this ));
201: }
202:
203: /**
204: * Determines whether the passed getter is supported
205: * @param method the Method to be checked
206: * @param prefixes indicates whether the getter prefixes should be checked
207: * @return true if the method is supported, false otherwise
208: */
209: public static boolean getterSupported(Method method,
210: boolean prefixes) {
211: if (method == null)
212: return false;
213: // exclude methods from java.lang package classes
214: if (method.getDeclaringClass().getName()
215: .startsWith("java.lang"))
216: return false;
217: // check if the method returns sth.
218: Class returnType = method.getReturnType();
219: if (void.class.isAssignableFrom(returnType))
220: return false;
221: if (Void.class.isAssignableFrom(returnType))
222: return false;
223:
224: // check the method name
225: if (prefixes) {
226: boolean methodNameOk = false;
227: String methodName = method.getName();
228: for (int i = 0; (i < GETTER_PREFIXES.length)
229: && !methodNameOk; i++)
230: methodNameOk = methodName
231: .startsWith(GETTER_PREFIXES[i]);
232: if (!methodNameOk)
233: return false;
234: }
235:
236: // check if the parameter types are supported
237: Class[] paramTypes = method.getParameterTypes();
238: Type[] genericParamTypes = method.getGenericParameterTypes();
239: boolean pok = true;
240: for (int i = 0; (i < paramTypes.length) && pok; i++) {
241: pok = (genericParamTypes[i] instanceof TypeVariable);
242: pok = (pok || XPojoHelper.isPrimitiveType(paramTypes[i]));
243: }
244: return pok;
245: }
246:
247: /**
248: * Determines whether the passed setter method is supported
249: * @param method the method to be queried
250: * @param prefixes indicates whether the getter prefixes should be checked
251: * @return true if the passed method is supported, false
252: * otherwise
253: */
254: public static boolean setterSupported(Method method,
255: boolean prefixes) {
256: if (method == null)
257: return false;
258: // exclude methods from java.lang package classes
259: if (method.getDeclaringClass().getName()
260: .startsWith("java.lang"))
261: return false;
262:
263: // check the method name
264: if (prefixes) {
265: boolean methodNameOk = false;
266: String methodName = method.getName();
267: for (int i = 0; (i < SETTER_PREFIXES.length)
268: && !methodNameOk; i++)
269: methodNameOk = methodName
270: .startsWith(SETTER_PREFIXES[i]);
271: if (!methodNameOk)
272: return false;
273: }
274:
275: // check if the parameter type is supported
276: Class[] paramTypes = method.getParameterTypes();
277: if (paramTypes.length != 1)
278: return false;
279: Type[] genericParamTypes = method.getGenericParameterTypes();
280: boolean iss = (genericParamTypes[0] instanceof TypeVariable);
281: iss = (iss || XPojoHelper.isPrimitiveType(paramTypes[0]));
282: return iss;
283: }
284:
285: /**
286: * Gets the pojo caption that will appear in the visualiser
287: * tree node.
288: * @param pojo Object
289: * @return the caption
290: */
291: protected static String getDispValue(Object pojo) {
292: if (pojo == null)
293: return null;
294: Class type = pojo.getClass();
295: String dispValue = (XPojoHelper.isPrimitiveType(type) ? pojo
296: .toString() : type.getSimpleName());
297:
298: return dispValue;
299: }
300:
301: /**
302: * Returns the class loader being used to load
303: * current project's classes.
304: * @return XProjectClassLoader of the current project
305: */
306: protected XProjectClassLoader getProjectClassLoader() {
307: return (XProjectClassLoader) ((XEditorProject) currentProject)
308: .getProjectClassLoader();
309: }
310:
311: /**
312: * Recursively load the model data
313: * @param source the source element
314: * @param model the model node for the source element
315: */
316: public void loadTable(XmlElement source, XModel model) {
317: try {
318: // setup the pojo context
319: XmlElement contextElement = source
320: .getFirstChildNamed("context");
321: String contextClassName = contextElement
322: .getAttribute("class");
323: String configFileName = contextElement
324: .getAttribute("config");
325:
326: XProjectClassLoader classLoader = getProjectClassLoader();
327:
328: // store the current thread's class loader
329: ClassLoader oldClassLoader = Thread.currentThread()
330: .getContextClassLoader();
331: Thread.currentThread().setContextClassLoader(classLoader);
332: try {
333: if (classLoader != null)
334: pojoContext = classLoader.findClass(
335: contextClassName).newInstance();
336: } catch (Exception e) {
337: e.printStackTrace(); //@todo log this e
338: } finally {
339: // restore the current thread's class loader
340: Thread.currentThread().setContextClassLoader(
341: oldClassLoader);
342: }
343:
344: if (pojoContext instanceof XPojoContext) {
345: ((XPojoContext) pojoContext).setProject(currentProject);
346: if (configFileName != null)
347: ((XPojoContext) pojoContext)
348: .configure(currentProject
349: .findResource(configFileName));
350: }
351:
352: // Load the customiztaions of the POJO classes
353: XmlElement overridesElement = source
354: .getFirstChildNamed("overrides");
355: if (overridesElement != null) {
356: Vector overrideSpecs = overridesElement.getChildren();
357: int numOverrides = overrideSpecs.size();
358: for (int i = 0; i < numOverrides; i++) {
359: XmlElement overrideElement = (XmlElement) overrideSpecs
360: .elementAt(i);
361: String pojoClassName = overrideElement
362: .getAttribute("class");
363: overrides.put(pojoClassName, overrideElement);
364: }
365: }
366:
367: // setup the POJO root
368: XmlElement rootElement = source.getFirstChildNamed("root");
369: String pojoRootName = "pojo";
370: Object pojoRoot = null;
371:
372: if (rootElement != null) {
373: String rootClassName = rootElement
374: .getAttribute("class");
375: pojoRootName = rootElement.getAttribute("id");
376: configFileName = rootElement.getAttribute("config");
377: int numParams = rootElement.getChildren().size();
378:
379: if (numParams == 0) {
380: if (classLoader != null)
381: pojoRoot = Class.forName(rootClassName, true,
382: classLoader);
383: else
384: pojoRoot = Class.forName(rootClassName);
385: if (pojoRoot != null)
386: pojoRoot = ((Class) pojoRoot).newInstance();
387: } else {
388: Class[] params = new Class[numParams];
389: Object[] values = new Object[numParams];
390:
391: for (int i = 0; i < numParams; i++) {
392: XmlElement paramElement = (XmlElement) rootElement
393: .getChildren().elementAt(i);
394: String paramType = paramElement
395: .getAttribute("class");
396: params[i] = ReflectionHelper
397: .getParamClass(paramType);
398: values[i] = ReflectionHelper.getObject(
399: paramType, paramElement
400: .getAttribute("value"));
401: }
402:
403: Class klass = null;
404: if (classLoader != null)
405: klass = Class.forName(rootClassName, true,
406: classLoader);
407: else
408: klass = Class.forName(rootClassName);
409:
410: pojoRoot = ReflectionHelper.constructViaReflection(
411: klass, params, values);
412: }
413:
414: if (pojoRoot instanceof XPojoRoot) {
415: ((XPojoRoot) pojoRoot).setProject(currentProject);
416: if (configFileName != null)
417: ((XPojoRoot) pojoRoot).configure(currentProject
418: .findResource(configFileName));
419: }
420: } else if (pojoContext != null) {
421: pojoRoot = ((XPojoContext) pojoContext).getRoot();
422: }
423:
424: // Attach the POJO topology. No need to scan the topology as nodes are
425: // adapted on demand
426: pojoRootModel = (XPojoModelVis) adaptPojo(pojoRoot, model);
427: ((XPojoModelVis) pojoRootModel).setCaption(pojoRootName
428: + ": ");
429: model.append(pojoRootModel);
430: } catch (Exception ex) {
431: DebugLogger.logError("Failed to load the POJO datasource");
432: if (BuildProperties.DEBUG)
433: ex.printStackTrace();
434: }
435: }
436:
437: }
|