001: /**
002: * ========================================
003: * JFreeReport : a free Java report library
004: * ========================================
005: *
006: * Project Info: http://reporting.pentaho.org/
007: *
008: * (C) Copyright 2000-2007, by Object Refinery Limited, 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: * $Id: StaticReportDataFactory.java 3525 2007-10-16 11:43:48Z tmorgner $
027: * ------------
028: * (C) Copyright 2000-2005, by Object Refinery Limited.
029: * (C) Copyright 2005-2007, by Pentaho Corporation.
030: */package org.jfree.report.modules.data.beans;
031:
032: import java.lang.reflect.Method;
033: import java.lang.reflect.Modifier;
034: import java.lang.reflect.Constructor;
035:
036: import javax.swing.table.TableModel;
037:
038: import org.jfree.report.DataSet;
039: import org.jfree.report.ReportData;
040: import org.jfree.report.ReportDataFactory;
041: import org.jfree.report.ReportDataFactoryException;
042: import org.jfree.report.TableReportData;
043: import org.jfree.report.util.CSVTokenizer;
044: import org.jfree.report.util.DataSetUtility;
045: import org.jfree.util.ObjectUtilities;
046:
047: /**
048: * This report data factory uses introspection to search for a report data
049: * source. The query has the following format:
050: *
051: * <full-qualified-classname&gr;#methodName(Parameters)
052: * <full-qualified-classname&gr;(constructorparams)#methodName(Parameters)
053: * <full-qualified-classname&gr;(constructorparams)
054: *
055: * @author Thomas Morgner
056: */
057: public class StaticReportDataFactory implements ReportDataFactory {
058: public StaticReportDataFactory() {
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
068: * @param parameters
069: * @return
070: */
071: public ReportData queryData(final String query,
072: final DataSet 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("Malformed query: "
078: + query);
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 String[] parameterNames;
085: final int parameterStartIdx = query.indexOf('(');
086: final String constructorName;
087: if (parameterStartIdx == -1) {
088: parameterNames = new String[0];
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] = DataSetUtility.getByName(parameters,
104: name);
105: }
106: final Object o = c.newInstance(params);
107: if (o instanceof TableModel) {
108: return new TableReportData((TableModel) o);
109: }
110:
111: return (ReportData) o;
112: } catch (Exception e) {
113: throw new ReportDataFactoryException(
114: "Unable to instantiate class for non static call.",
115: e);
116: }
117: }
118:
119: return createComplexTableModel(query, methodSeparatorIdx,
120: parameters);
121: }
122:
123: private ReportData createComplexTableModel(final String query,
124: final int methodSeparatorIdx, final DataSet parameters)
125: throws ReportDataFactoryException {
126: final String constructorSpec = query.substring(0,
127: methodSeparatorIdx);
128: final int constParamIdx = constructorSpec.indexOf('(');
129: if (constParamIdx == -1) {
130: // Either a static call or a default constructor call..
131: return loadFromDefaultConstructor(query,
132: methodSeparatorIdx, parameters);
133: }
134:
135: // We have to find a suitable constructor ..
136: final String className = query.substring(0, constParamIdx);
137: final String[] parameterNames = createParameterList(
138: constructorSpec, constParamIdx);
139: final Constructor c = findIndirectConstructor(className,
140: parameterNames.length);
141:
142: final String methodQuery = query
143: .substring(methodSeparatorIdx + 1);
144: final String[] methodParameterNames;
145: final String methodName;
146: final int parameterStartIdx = methodQuery.indexOf('(');
147: if (parameterStartIdx == -1) {
148: // no parameters. Nice.
149: methodParameterNames = new String[0];
150: methodName = methodQuery;
151: } else {
152: methodName = methodQuery.substring(0, parameterStartIdx);
153: methodParameterNames = createParameterList(methodQuery,
154: parameterStartIdx);
155: }
156: final Method m = findCallableMethod(className, methodName,
157: methodParameterNames.length);
158:
159: try {
160: final Object[] constrParams = new Object[parameterNames.length];
161: for (int i = 0; i < parameterNames.length; i++) {
162: final String name = parameterNames[i];
163: constrParams[i] = DataSetUtility.getByName(parameters,
164: name);
165: }
166: final Object o = c.newInstance(constrParams);
167:
168: final Object[] methodParams = new Object[methodParameterNames.length];
169: for (int i = 0; i < methodParameterNames.length; i++) {
170: final String name = methodParameterNames[i];
171: methodParams[i] = DataSetUtility.getByName(parameters,
172: name);
173: }
174: final Object data = m.invoke(o, methodParams);
175: if (data instanceof TableModel) {
176: return new TableReportData((TableModel) data);
177: }
178: return (ReportData) data;
179: } catch (Exception e) {
180: throw new ReportDataFactoryException(
181: "Unable to instantiate class for non static call.");
182: }
183: }
184:
185: private ReportData loadFromDefaultConstructor(final String query,
186: final int methodSeparatorIdx, final DataSet parameters)
187: throws ReportDataFactoryException {
188: final String className = query.substring(0, methodSeparatorIdx);
189: final String methodSpec = query
190: .substring(methodSeparatorIdx + 1);
191: final String methodName;
192: final String[] parameterNames;
193: final int parameterStartIdx = methodSpec.indexOf('(');
194: if (parameterStartIdx == -1) {
195: // no parameters. Nice.
196: parameterNames = new String[0];
197: methodName = methodSpec;
198: } else {
199: parameterNames = createParameterList(methodSpec,
200: parameterStartIdx);
201: methodName = methodSpec.substring(0, parameterStartIdx);
202: }
203:
204: try {
205: final Method m = findCallableMethod(className, methodName,
206: parameterNames.length);
207: final Object[] params = new Object[parameterNames.length];
208: for (int i = 0; i < parameterNames.length; i++) {
209: final String name = parameterNames[i];
210: params[i] = DataSetUtility.getByName(parameters, name);
211: }
212:
213: if (Modifier.isStatic(m.getModifiers())) {
214: final Object o = m.invoke(null, params);
215: if (o instanceof TableModel) {
216: return new TableReportData((TableModel) o);
217: }
218: return (ReportData) o;
219: }
220:
221: final ClassLoader classLoader = getClassLoader();
222: final Class c = classLoader.loadClass(className);
223: final Object o = c.newInstance();
224: if (o == null) {
225: throw new ReportDataFactoryException(
226: "Unable to instantiate class for non static call.");
227: }
228: final Object data = m.invoke(o, params);
229: if (data instanceof TableModel) {
230: return new TableReportData((TableModel) data);
231: }
232: return (ReportData) data;
233: } catch (ReportDataFactoryException rdfe) {
234: throw rdfe;
235: } catch (Exception e) {
236: throw new ReportDataFactoryException(
237: "Something went terribly wrong: ", e);
238: }
239: }
240:
241: private String[] createParameterList(final String query,
242: final int parameterStartIdx)
243: throws ReportDataFactoryException {
244: final int parameterEndIdx = query.lastIndexOf(')');
245: if (parameterEndIdx < parameterStartIdx) {
246: throw new ReportDataFactoryException("Malformed query: "
247: + query);
248: }
249: final String parameterText = query.substring(
250: parameterStartIdx + 1, parameterEndIdx);
251: final CSVTokenizer tokenizer = new CSVTokenizer(parameterText);
252: final int size = tokenizer.countTokens();
253: final String[] parameterNames = new String[size];
254: int i = 0;
255: while (tokenizer.hasMoreTokens()) {
256: parameterNames[i] = tokenizer.nextToken();
257: i += 1;
258: }
259: return parameterNames;
260: }
261:
262: protected ClassLoader getClassLoader() {
263: return ObjectUtilities
264: .getClassLoader(StaticReportDataFactory.class);
265: }
266:
267: private Method findCallableMethod(final String className,
268: final String methodName, final int paramCount)
269: throws ReportDataFactoryException {
270: final ClassLoader classLoader = getClassLoader();
271:
272: if (classLoader == null) {
273: throw new ReportDataFactoryException("No classloader!");
274: }
275: try {
276: final Class c = classLoader.loadClass(className);
277: if (Modifier.isAbstract(c.getModifiers())) {
278: throw new ReportDataFactoryException(
279: "Abstract class cannot be handled!");
280: }
281:
282: final Method[] methods = c.getMethods();
283: for (int i = 0; i < methods.length; i++) {
284: final Method method = methods[i];
285: if (Modifier.isPublic(method.getModifiers()) == false) {
286: continue;
287: }
288: if (method.getName().equals(methodName) == false) {
289: continue;
290: }
291: final Class returnType = method.getReturnType();
292: if (method.getParameterTypes().length != paramCount) {
293: continue;
294: }
295: if (TableModel.class.isAssignableFrom(returnType)
296: || ReportData.class
297: .isAssignableFrom(returnType)) {
298: return method;
299: }
300: }
301: } catch (ClassNotFoundException e) {
302: throw new ReportDataFactoryException("No such Class", e);
303: }
304: throw new ReportDataFactoryException("No such Method: "
305: + className + "#" + methodName);
306: }
307:
308: private Constructor findDirectConstructor(final String className,
309: final int paramCount) throws ReportDataFactoryException {
310: final ClassLoader classLoader = getClassLoader();
311: if (classLoader == null) {
312: throw new ReportDataFactoryException("No classloader!");
313: }
314:
315: try {
316: final Class c = classLoader.loadClass(className);
317: if (TableModel.class.isAssignableFrom(c) == false
318: && ReportData.class.isAssignableFrom(c) == false) {
319: throw new ReportDataFactoryException(
320: "The specified class must be either a TableModel or a ReportData implementation.");
321: }
322: if (Modifier.isAbstract(c.getModifiers())) {
323: throw new ReportDataFactoryException(
324: "The specified class cannot be instantiated: it is abstract.");
325: }
326:
327: final Constructor[] methods = c.getConstructors();
328: for (int i = 0; i < methods.length; i++) {
329: final Constructor method = methods[i];
330: if (Modifier.isPublic(method.getModifiers()) == false) {
331: continue;
332: }
333: if (method.getParameterTypes().length != paramCount) {
334: continue;
335: }
336: return method;
337: }
338: } catch (ClassNotFoundException e) {
339: throw new ReportDataFactoryException("No such Class", e);
340: }
341: throw new ReportDataFactoryException(
342: "There is no constructor in class " + className
343: + " that accepts " + paramCount
344: + " parameters.");
345: }
346:
347: private Constructor findIndirectConstructor(final String className,
348: final int paramCount) throws ReportDataFactoryException {
349: final ClassLoader classLoader = getClassLoader();
350: if (classLoader == null) {
351: throw new ReportDataFactoryException("No classloader!");
352: }
353:
354: try {
355: final Class c = classLoader.loadClass(className);
356: if (Modifier.isAbstract(c.getModifiers())) {
357: throw new ReportDataFactoryException(
358: "The specified class cannot be instantiated: it is abstract.");
359: }
360:
361: final Constructor[] methods = c.getConstructors();
362: for (int i = 0; i < methods.length; i++) {
363: final Constructor method = methods[i];
364: if (Modifier.isPublic(method.getModifiers()) == false) {
365: continue;
366: }
367: if (method.getParameterTypes().length != paramCount) {
368: continue;
369: }
370: return method;
371: }
372: } catch (ClassNotFoundException e) {
373: throw new ReportDataFactoryException("No such Class", e);
374: }
375: throw new ReportDataFactoryException(
376: "There is no constructor in class " + className
377: + " that accepts " + paramCount
378: + " parameters.");
379: }
380:
381: public void open() {
382:
383: }
384:
385: public void close() {
386:
387: }
388:
389: /**
390: * Derives a freshly initialized report data factory, which is independend of
391: * the original data factory. Opening or Closing one data factory must not
392: * affect the other factories.
393: *
394: * @return
395: */
396: public ReportDataFactory derive() {
397: return this;
398: }
399: }
|