001: /**
002: * ===========================================
003: * JFreeReport : a free Java reporting library
004: * ===========================================
005: *
006: * Project Info: http://reporting.pentaho.org/
007: *
008: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
009: *
010: * This library is free software; you can redistribute it and/or modify it under the terms
011: * of the GNU Lesser General Public License as published by the Free Software Foundation;
012: * either version 2.1 of the License, or (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
015: * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016: * See the GNU Lesser General Public License for more details.
017: *
018: * You should have received a copy of the GNU Lesser General Public License along with this
019: * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
020: * Boston, MA 02111-1307, USA.
021: *
022: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
023: * in the United States and other countries.]
024: *
025: * ------------
026: * StaticDataFactory.java
027: * ------------
028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
029: */package org.jfree.report.modules.misc.datafactory;
030:
031: import java.lang.reflect.Constructor;
032: import java.lang.reflect.Method;
033: import java.lang.reflect.Modifier;
034: import javax.swing.table.TableModel;
035:
036: import org.jfree.report.DataFactory;
037: import org.jfree.report.DataRow;
038: import org.jfree.report.ReportDataFactoryException;
039: import org.jfree.report.util.CSVTokenizer;
040: import org.jfree.util.ObjectUtilities;
041:
042: /**
043: * This report data factory uses introspection to search for a report data
044: * source. The query can have the following formats:
045: * <p/>
046: * <full-qualified-classname>#methodName(Parameters)
047: * <full-qualified-classname>(constructorparams)#methodName(Parameters)
048: * <full-qualified-classname>(constructorparams)
049: *
050: * @author Thomas Morgner
051: */
052: public class StaticDataFactory implements DataFactory, Cloneable {
053: private static final String[] EMPTY_PARAMS = new String[0];
054:
055: /**
056: * DefaultConstructor.
057: */
058: public StaticDataFactory() {
059: }
060:
061: /**
062: * Queries a datasource. The string 'query' defines the name of the query. The
063: * Parameterset given here may contain more data than actually needed.
064: * <p/>
065: * The dataset may change between two calls, do not assume anything!
066: *
067: * @param query the method call.
068: * @param parameters the set of parameters.
069: * @return the tablemodel from the executed method call, never null.
070: */
071: public TableModel queryData(final String query,
072: final DataRow parameters) throws ReportDataFactoryException {
073: final int methodSeparatorIdx = query.indexOf('#');
074:
075: if ((methodSeparatorIdx + 1) >= query.length()) {
076: // If we have a method separator, then it cant be at the end of the text.
077: throw new ReportDataFactoryException(
078: "Malformed query: " + query); //$NON-NLS-1$
079: }
080:
081: if (methodSeparatorIdx == -1) {
082: // we have no method. So this query must be a reference to a tablemodel
083: // instance.
084: final int parameterStartIdx = query.indexOf('(');
085: final String[] parameterNames;
086: final String constructorName;
087: if (parameterStartIdx == -1) {
088: parameterNames = EMPTY_PARAMS;
089: constructorName = query;
090: } else {
091: parameterNames = createParameterList(query,
092: parameterStartIdx);
093: constructorName = query.substring(0, parameterStartIdx);
094: }
095:
096: try {
097: final Constructor c = findDirectConstructor(
098: constructorName, parameterNames.length);
099:
100: final Object[] params = new Object[parameterNames.length];
101: for (int i = 0; i < parameterNames.length; i++) {
102: final String name = parameterNames[i];
103: params[i] = parameters.get(name);
104: }
105: return (TableModel) c.newInstance(params);
106: } catch (Exception e) {
107: throw new ReportDataFactoryException(
108: "Unable to instantiate class for non static call.", e); //$NON-NLS-1$
109: }
110: }
111:
112: return createComplexTableModel(query, methodSeparatorIdx,
113: parameters);
114: }
115:
116: /**
117: * Performs a complex query, where the tablemodel is retrieved from an method
118: * that was instantiated using parameters.
119: *
120: * @param query the query-string that contains the method to call.
121: * @param methodSeparatorIdx the position where the method specification starts.
122: * @param parameters the set of parameters.
123: * @return the resulting tablemodel, never null.
124: * @throws ReportDataFactoryException if something goes wrong.
125: */
126: private TableModel createComplexTableModel(final String query,
127: final int methodSeparatorIdx, final DataRow parameters)
128: throws ReportDataFactoryException {
129: final String constructorSpec = query.substring(0,
130: methodSeparatorIdx);
131: final int constParamIdx = constructorSpec.indexOf('(');
132: if (constParamIdx == -1) {
133: // Either a static call or a default constructor call..
134: return loadFromDefaultConstructor(query,
135: methodSeparatorIdx, parameters);
136: }
137:
138: // We have to find a suitable constructor ..
139: final String className = query.substring(0, constParamIdx);
140: final String[] parameterNames = createParameterList(
141: constructorSpec, constParamIdx);
142: final Constructor c = findIndirectConstructor(className,
143: parameterNames.length);
144:
145: final String methodQuery = query
146: .substring(methodSeparatorIdx + 1);
147: final String[] methodParameterNames;
148: final String methodName;
149: final int parameterStartIdx = methodQuery.indexOf('(');
150: if (parameterStartIdx == -1) {
151: // no parameters. Nice.
152: methodParameterNames = EMPTY_PARAMS;
153: methodName = methodQuery;
154: } else {
155: methodName = methodQuery.substring(0, parameterStartIdx);
156: methodParameterNames = createParameterList(methodQuery,
157: parameterStartIdx);
158: }
159: final Method m = findCallableMethod(className, methodName,
160: methodParameterNames.length);
161:
162: try {
163: final Object[] constrParams = new Object[parameterNames.length];
164: for (int i = 0; i < parameterNames.length; i++) {
165: final String name = parameterNames[i];
166: constrParams[i] = parameters.get(name);
167: }
168: final Object o = c.newInstance(constrParams);
169:
170: final Object[] methodParams = new Object[methodParameterNames.length];
171: for (int i = 0; i < methodParameterNames.length; i++) {
172: final String name = methodParameterNames[i];
173: methodParams[i] = parameters.get(name);
174: }
175: final Object data = m.invoke(o, methodParams);
176: return (TableModel) data;
177: } catch (Exception e) {
178: throw new ReportDataFactoryException(
179: "Unable to instantiate class for non static call."); //$NON-NLS-1$
180: }
181: }
182:
183: /**
184: * Loads a tablemodel from a parameterless class or method. Call does not
185: * use any parameters.
186: *
187: * @param query the query-string that contains the method to call.
188: * @param methodSeparatorIdx the position where the method specification starts.
189: * @param parameters the set of parameters.
190: * @return the resulting tablemodel, never null.
191: * @throws ReportDataFactoryException if something goes wrong.
192: */
193: private TableModel loadFromDefaultConstructor(final String query,
194: final int methodSeparatorIdx, final DataRow parameters)
195: throws ReportDataFactoryException {
196: final String className = query.substring(0, methodSeparatorIdx);
197:
198: final String methodSpec = query
199: .substring(methodSeparatorIdx + 1);
200: final String methodName;
201: final String[] parameterNames;
202: final int parameterStartIdx = methodSpec.indexOf('(');
203: if (parameterStartIdx == -1) {
204: // no parameters. Nice.
205: parameterNames = EMPTY_PARAMS;
206: methodName = methodSpec;
207: } else {
208: parameterNames = createParameterList(methodSpec,
209: parameterStartIdx);
210: methodName = methodSpec.substring(0, parameterStartIdx);
211: }
212:
213: try {
214: final Method m = findCallableMethod(className, methodName,
215: parameterNames.length);
216: final Object[] params = new Object[parameterNames.length];
217: for (int i = 0; i < parameterNames.length; i++) {
218: final String name = parameterNames[i];
219: params[i] = parameters.get(name);
220: }
221:
222: if (Modifier.isStatic(m.getModifiers())) {
223: final Object o = m.invoke(null, params);
224: return (TableModel) o;
225: }
226:
227: final ClassLoader classLoader = getClassLoader();
228: final Class c = classLoader.loadClass(className);
229: final Object o = c.newInstance();
230: if (o == null) {
231: throw new ReportDataFactoryException(
232: "Unable to instantiate class for non static call."); //$NON-NLS-1$
233: }
234: final Object data = m.invoke(o, params);
235: return (TableModel) data;
236: } catch (ReportDataFactoryException rdfe) {
237: throw rdfe;
238: } catch (Exception e) {
239: throw new ReportDataFactoryException(
240: "Something went terribly wrong: ", e); //$NON-NLS-1$
241: }
242: }
243:
244: /**
245: * Creates the list of column names that should be mapped into the method
246: * or constructor parameters.
247: *
248: * @param query the query-string.
249: * @param parameterStartIdx the index from where to read the parameter list.
250: * @return an array with column names.
251: * @throws ReportDataFactoryException if something goes wrong.
252: */
253: private String[] createParameterList(final String query,
254: final int parameterStartIdx)
255: throws ReportDataFactoryException {
256: final int parameterEndIdx = query.lastIndexOf(')');
257: if (parameterEndIdx < parameterStartIdx) {
258: throw new ReportDataFactoryException(
259: "Malformed query: " + query); //$NON-NLS-1$
260: }
261: final String parameterText = query.substring(
262: parameterStartIdx + 1, parameterEndIdx);
263: final CSVTokenizer tokenizer = new CSVTokenizer(parameterText);
264: final int size = tokenizer.countTokens();
265: final String[] parameterNames = new String[size];
266: int i = 0;
267: while (tokenizer.hasMoreTokens()) {
268: parameterNames[i] = tokenizer.nextToken();
269: i += 1;
270: }
271: return parameterNames;
272: }
273:
274: /**
275: * Returns the current classloader.
276: *
277: * @return the current classloader.
278: */
279: protected ClassLoader getClassLoader() {
280: return ObjectUtilities.getClassLoader(StaticDataFactory.class);
281: }
282:
283: /**
284: * Tries to locate a method-object for the call. This method will
285: * throw an Exception if the method was not found or not public.
286: *
287: * @param className the name of the class where to seek the method.
288: * @param methodName the name of the method.
289: * @param paramCount the parameter count of the method we seek.
290: * @return the method object.
291: * @throws ReportDataFactoryException if something goes wrong.
292: */
293: private Method findCallableMethod(final String className,
294: final String methodName, final int paramCount)
295: throws ReportDataFactoryException {
296: final ClassLoader classLoader = getClassLoader();
297:
298: if (classLoader == null) {
299: throw new ReportDataFactoryException("No classloader!"); //$NON-NLS-1$
300: }
301: try {
302: final Class c = classLoader.loadClass(className);
303: if (Modifier.isAbstract(c.getModifiers())) {
304: throw new ReportDataFactoryException(
305: "Abstract class cannot be handled!"); //$NON-NLS-1$
306: }
307:
308: final Method[] methods = c.getMethods();
309: for (int i = 0; i < methods.length; i++) {
310: final Method method = methods[i];
311: if (Modifier.isPublic(method.getModifiers()) == false) {
312: continue;
313: }
314: if (method.getName().equals(methodName) == false) {
315: continue;
316: }
317: final Class returnType = method.getReturnType();
318: if (TableModel.class.isAssignableFrom(returnType) == false) {
319: continue;
320: }
321: if (method.getParameterTypes().length != paramCount) {
322: continue;
323: }
324: return method;
325: }
326: } catch (ClassNotFoundException e) {
327: throw new ReportDataFactoryException("No such Class", e); //$NON-NLS-1$
328: }
329: throw new ReportDataFactoryException(
330: "No such Method: " + className + '#' + methodName); //$NON-NLS-1$ //$NON-NLS-2$
331: }
332:
333: /**
334: * Tries to locate a suitable public constructor for the number of parameters.
335: * This will return the first constructor that matches, no matter whether the
336: * parameter types will match too.
337: *
338: * The Class that is referenced must be a Tablemodel implementation.
339: *
340: * @param className the classname on where to find the constructor.
341: * @param paramCount the number of parameters expected in the constructor.
342: * @return the Constructor object, never null.
343: * @throws ReportDataFactoryException if the constructor could not be found
344: * or something went wrong.
345: */
346: private Constructor findDirectConstructor(final String className,
347: final int paramCount) throws ReportDataFactoryException {
348: final ClassLoader classLoader = getClassLoader();
349: if (classLoader == null) {
350: throw new ReportDataFactoryException("No classloader!"); //$NON-NLS-1$
351: }
352:
353: try {
354: final Class c = classLoader.loadClass(className);
355: if (TableModel.class.isAssignableFrom(c) == false) {
356: throw new ReportDataFactoryException(
357: "The specified class must be either a TableModel or a ReportData implementation."); //$NON-NLS-1$
358: }
359: if (Modifier.isAbstract(c.getModifiers())) {
360: throw new ReportDataFactoryException(
361: "The specified class cannot be instantiated: it is abstract."); //$NON-NLS-1$
362: }
363:
364: final Constructor[] methods = c.getConstructors();
365: for (int i = 0; i < methods.length; i++) {
366: final Constructor method = methods[i];
367: if (Modifier.isPublic(method.getModifiers()) == false) {
368: continue;
369: }
370: if (method.getParameterTypes().length != paramCount) {
371: continue;
372: }
373: return method;
374: }
375: } catch (ClassNotFoundException e) {
376: throw new ReportDataFactoryException("No such Class", e); //$NON-NLS-1$
377: }
378: throw new ReportDataFactoryException(
379: "There is no constructor in class " + className + //$NON-NLS-1$
380: " that accepts " + paramCount + " parameters."); //$NON-NLS-1$ //$NON-NLS-2$
381: }
382:
383: /**
384: * Tries to locate a constructor that accepts the specified number of
385: * parameters. The referenced class can be of any type, as we will call
386: * a method on that class that will return the tablemodel for us.
387: *
388: * @param className the classname of the class where to search the constructor.
389: * @param paramCount the numbers of parameters expected.
390: * @return the constructor object, never null.
391: * @throws ReportDataFactoryException if the constructor could not be found
392: * or something went wrong.
393: */
394: private Constructor findIndirectConstructor(final String className,
395: final int paramCount) throws ReportDataFactoryException {
396: final ClassLoader classLoader = getClassLoader();
397: if (classLoader == null) {
398: throw new ReportDataFactoryException("No classloader!"); //$NON-NLS-1$
399: }
400:
401: try {
402: final Class c = classLoader.loadClass(className);
403: if (Modifier.isAbstract(c.getModifiers())) {
404: throw new ReportDataFactoryException(
405: "The specified class cannot be instantiated: it is abstract."); //$NON-NLS-1$
406: }
407:
408: final Constructor[] methods = c.getConstructors();
409: for (int i = 0; i < methods.length; i++) {
410: final Constructor method = methods[i];
411: if (Modifier.isPublic(method.getModifiers()) == false) {
412: continue;
413: }
414: if (method.getParameterTypes().length != paramCount) {
415: continue;
416: }
417: return method;
418: }
419: } catch (ClassNotFoundException e) {
420: throw new ReportDataFactoryException("No such Class", e); //$NON-NLS-1$
421: }
422: throw new ReportDataFactoryException(
423: "There is no constructor in class " + className + //$NON-NLS-1$
424: " that accepts " + paramCount + " parameters."); //$NON-NLS-1$ //$NON-NLS-2$
425: }
426:
427: /**
428: * Derives the factory. In fact, this returns the this-reference, as
429: * this factory is a stateless class.
430: *
431: * @return the derived factory.
432: * @throws ReportDataFactoryException in case something goes wrong.
433: */
434: public DataFactory derive() throws ReportDataFactoryException {
435: return this ;
436: }
437:
438: /**
439: * Opens the data factory. This initializes everything. Performing queries
440: * on data factories which have not yet been opened will result in exceptions.
441: *
442: * This method does nothing at all.
443: */
444: public void open() {
445:
446: }
447:
448: /**
449: * Closes the data factory and frees all resources held by this instance.
450: *
451: * This method is empty.
452: */
453: public void close() {
454:
455: }
456:
457: /**
458: * Returns a clone of the factory.
459: *
460: * @return the clone.
461: * @throws CloneNotSupportedException if cloning failed.
462: */
463: public Object clone() throws CloneNotSupportedException {
464: return super.clone();
465: }
466: }
|