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,v 1.7 2007/04/01 18:49:26 taqua Exp $
027 * ------------
028 * (C) Copyright 2000-2005, by Object Refinery Limited.
029 * (C) Copyright 2005-2007, by Pentaho Corporation.
030 */
031 package org.jfree.report.modules.data.beans;
032
033 import java.lang.reflect.Method;
034 import java.lang.reflect.Modifier;
035 import java.lang.reflect.Constructor;
036
037 import javax.swing.table.TableModel;
038
039 import org.jfree.report.DataSet;
040 import org.jfree.report.ReportData;
041 import org.jfree.report.ReportDataFactory;
042 import org.jfree.report.ReportDataFactoryException;
043 import org.jfree.report.TableReportData;
044 import org.jfree.report.util.CSVTokenizer;
045 import org.jfree.report.util.DataSetUtility;
046 import org.jfree.util.ObjectUtilities;
047
048 /**
049 * This report data factory uses introspection to search for a report data
050 * source. The query has the following format:
051 *
052 * <full-qualified-classname&gr;#methodName(Parameters)
053 * <full-qualified-classname&gr;(constructorparams)#methodName(Parameters)
054 * <full-qualified-classname&gr;(constructorparams)
055 *
056 * @author Thomas Morgner
057 */
058 public class StaticReportDataFactory implements ReportDataFactory
059 {
060 public StaticReportDataFactory()
061 {
062 }
063
064 /**
065 * Queries a datasource. The string 'query' defines the name of the query. The
066 * Parameterset given here may contain more data than actually needed.
067 * <p/>
068 * The dataset may change between two calls, do not assume anything!
069 *
070 * @param query
071 * @param parameters
072 * @return
073 */
074 public ReportData queryData(final String query, final DataSet parameters)
075 throws ReportDataFactoryException
076 {
077 final int methodSeparatorIdx = query.indexOf('#');
078
079 if ((methodSeparatorIdx + 1) >= query.length())
080 {
081 // If we have a method separator, then it cant be at the end of the text.
082 throw new ReportDataFactoryException("Malformed query: " + query);
083 }
084
085 if (methodSeparatorIdx == -1)
086 {
087 // we have no method. So this query must be a reference to a tablemodel
088 // instance.
089 final String[] parameterNames;
090 final int parameterStartIdx = query.indexOf('(');
091 final String constructorName;
092 if (parameterStartIdx == -1)
093 {
094 parameterNames = new String[0];
095 constructorName = query;
096 }
097 else
098 {
099 parameterNames = createParameterList(query, parameterStartIdx);
100 constructorName = query.substring(0, parameterStartIdx);
101 }
102
103 try
104 {
105 Constructor c = findDirectConstructor(constructorName, parameterNames.length);
106
107 Object[] params = new Object[parameterNames.length];
108 for (int i = 0; i < parameterNames.length; i++)
109 {
110 final String name = parameterNames[i];
111 params[i] = DataSetUtility.getByName(parameters, name);
112 }
113 final Object o = c.newInstance(params);
114 if (o instanceof TableModel)
115 {
116 return new TableReportData ((TableModel) o);
117 }
118
119 return (ReportData) o;
120 }
121 catch (Exception e)
122 {
123 throw new ReportDataFactoryException
124 ("Unable to instantiate class for non static call.", e);
125 }
126 }
127
128 return createComplexTableModel
129 (query, methodSeparatorIdx, parameters);
130 }
131
132 private ReportData createComplexTableModel(final String query,
133 final int methodSeparatorIdx,
134 final DataSet parameters)
135 throws ReportDataFactoryException
136 {
137 final String constructorSpec = query.substring(0, methodSeparatorIdx);
138 final int constParamIdx = constructorSpec.indexOf('(');
139 if (constParamIdx == -1)
140 {
141 // Either a static call or a default constructor call..
142 return loadFromDefaultConstructor(query, methodSeparatorIdx, parameters);
143 }
144
145 // We have to find a suitable constructor ..
146 final String className = query.substring(0, constParamIdx);
147 final String[] parameterNames = createParameterList(constructorSpec, constParamIdx);
148 final Constructor c = findIndirectConstructor(className, parameterNames.length);
149
150 final String methodQuery = query.substring(methodSeparatorIdx + 1);
151 final String[] methodParameterNames;
152 final String methodName;
153 final int parameterStartIdx = methodQuery.indexOf('(');
154 if (parameterStartIdx == -1)
155 {
156 // no parameters. Nice.
157 methodParameterNames = new String[0];
158 methodName = methodQuery;
159 }
160 else
161 {
162 methodName = methodQuery.substring(0, parameterStartIdx);
163 methodParameterNames = createParameterList(methodQuery, parameterStartIdx);
164 }
165 final Method m = findCallableMethod(className, methodName, methodParameterNames.length);
166
167 try
168 {
169 final Object[] constrParams = new Object[parameterNames.length];
170 for (int i = 0; i < parameterNames.length; i++)
171 {
172 final String name = parameterNames[i];
173 constrParams[i] = DataSetUtility.getByName(parameters, name);
174 }
175 final Object o = c.newInstance(constrParams);
176
177 final Object[] methodParams = new Object[methodParameterNames.length];
178 for (int i = 0; i < methodParameterNames.length; i++)
179 {
180 final String name = methodParameterNames[i];
181 methodParams[i] = DataSetUtility.getByName(parameters, name);
182 }
183 final Object data = m.invoke(o, methodParams);
184 if (data instanceof TableModel)
185 {
186 return new TableReportData((TableModel) data);
187 }
188 return (ReportData) data;
189 }
190 catch (Exception e)
191 {
192 throw new ReportDataFactoryException
193 ("Unable to instantiate class for non static call.");
194 }
195 }
196
197 private ReportData loadFromDefaultConstructor(final String query,
198 final int methodSeparatorIdx,
199 final DataSet parameters)
200 throws ReportDataFactoryException
201 {
202 final String className = query.substring(0, methodSeparatorIdx);
203 final String methodSpec = query.substring(methodSeparatorIdx + 1);
204 final String methodName;
205 final String[] parameterNames;
206 final int parameterStartIdx = methodSpec.indexOf('(');
207 if (parameterStartIdx == -1)
208 {
209 // no parameters. Nice.
210 parameterNames = new String[0];
211 methodName = methodSpec;
212 }
213 else
214 {
215 parameterNames = createParameterList(methodSpec, parameterStartIdx);
216 methodName = methodSpec.substring(0, parameterStartIdx);
217 }
218
219 try
220 {
221 final Method m = findCallableMethod(className, methodName, parameterNames.length);
222 Object[] params = new Object[parameterNames.length];
223 for (int i = 0; i < parameterNames.length; i++)
224 {
225 final String name = parameterNames[i];
226 params[i] = DataSetUtility.getByName(parameters, name);
227 }
228
229 if (Modifier.isStatic(m.getModifiers()))
230 {
231 final Object o = m.invoke(null, params);
232 if (o instanceof TableModel)
233 {
234 return new TableReportData((TableModel) o);
235 }
236 return (ReportData) o;
237 }
238
239 final ClassLoader classLoader = getClassLoader();
240 final Class c = classLoader.loadClass(className);
241 final Object o = c.newInstance();
242 if (o == null)
243 {
244 throw new ReportDataFactoryException
245 ("Unable to instantiate class for non static call.");
246 }
247 final Object data = m.invoke(o, params);
248 if (data instanceof TableModel)
249 {
250 return new TableReportData((TableModel) data);
251 }
252 return (ReportData) data;
253 }
254 catch (ReportDataFactoryException rdfe)
255 {
256 throw rdfe;
257 }
258 catch (Exception e)
259 {
260 throw new ReportDataFactoryException
261 ("Something went terribly wrong: ", e);
262 }
263 }
264
265 private String[] createParameterList(final String query,
266 final int parameterStartIdx)
267 throws ReportDataFactoryException
268 {
269 final int parameterEndIdx = query.lastIndexOf(')');
270 if (parameterEndIdx < parameterStartIdx)
271 {
272 throw new ReportDataFactoryException("Malformed query: " + query);
273 }
274 final String parameterText =
275 query.substring(parameterStartIdx + 1, parameterEndIdx);
276 final CSVTokenizer tokenizer = new CSVTokenizer(parameterText);
277 final int size = tokenizer.countTokens();
278 final String[] parameterNames = new String[size];
279 int i = 0;
280 while (tokenizer.hasMoreTokens())
281 {
282 parameterNames[i] = tokenizer.nextToken();
283 i += 1;
284 }
285 return parameterNames;
286 }
287
288 protected ClassLoader getClassLoader()
289 {
290 return ObjectUtilities.getClassLoader(StaticReportDataFactory.class);
291 }
292
293 private Method findCallableMethod(final String className,
294 final String methodName,
295 final int paramCount)
296 throws ReportDataFactoryException
297 {
298 ClassLoader classLoader = getClassLoader();
299
300 if (classLoader == null)
301 {
302 throw new ReportDataFactoryException("No classloader!");
303 }
304 try
305 {
306 Class c = classLoader.loadClass(className);
307 if (Modifier.isAbstract(c.getModifiers()))
308 {
309 throw new ReportDataFactoryException("Abstract class cannot be handled!");
310 }
311
312 Method[] methods = c.getMethods();
313 for (int i = 0; i < methods.length; i++)
314 {
315 final Method method = methods[i];
316 if (Modifier.isPublic(method.getModifiers()) == false)
317 {
318 continue;
319 }
320 if (method.getName().equals(methodName) == false)
321 {
322 continue;
323 }
324 final Class returnType = method.getReturnType();
325 if (method.getParameterTypes().length != paramCount)
326 {
327 continue;
328 }
329 if (TableModel.class.isAssignableFrom(returnType) ||
330 ReportData.class.isAssignableFrom(returnType))
331 {
332 return method;
333 }
334 }
335 }
336 catch (ClassNotFoundException e)
337 {
338 throw new ReportDataFactoryException("No such Class", e);
339 }
340 throw new ReportDataFactoryException("No such Method: " + className + "#" + methodName);
341 }
342
343 private Constructor findDirectConstructor(final String className,
344 final int paramCount)
345 throws ReportDataFactoryException
346 {
347 ClassLoader classLoader = getClassLoader();
348 if (classLoader == null)
349 {
350 throw new ReportDataFactoryException("No classloader!");
351 }
352
353 try
354 {
355 Class c = classLoader.loadClass(className);
356 if (TableModel.class.isAssignableFrom(c) == false &&
357 ReportData.class.isAssignableFrom(c) == false)
358 {
359 throw new ReportDataFactoryException("The specified class must be either a TableModel or a ReportData implementation.");
360 }
361 if (Modifier.isAbstract(c.getModifiers()))
362 {
363 throw new ReportDataFactoryException("The specified class cannot be instantiated: it is abstract.");
364 }
365
366 Constructor[] methods = c.getConstructors();
367 for (int i = 0; i < methods.length; i++)
368 {
369 final Constructor method = methods[i];
370 if (Modifier.isPublic(method.getModifiers()) == false)
371 {
372 continue;
373 }
374 if (method.getParameterTypes().length != paramCount)
375 {
376 continue;
377 }
378 return method;
379 }
380 }
381 catch (ClassNotFoundException e)
382 {
383 throw new ReportDataFactoryException("No such Class", e);
384 }
385 throw new ReportDataFactoryException
386 ("There is no constructor in class " + className +
387 " that accepts " + paramCount + " parameters.");
388 }
389
390
391 private Constructor findIndirectConstructor(final String className,
392 final int paramCount)
393 throws ReportDataFactoryException
394 {
395 ClassLoader classLoader = getClassLoader();
396 if (classLoader == null)
397 {
398 throw new ReportDataFactoryException("No classloader!");
399 }
400
401 try
402 {
403 Class c = classLoader.loadClass(className);
404 if (Modifier.isAbstract(c.getModifiers()))
405 {
406 throw new ReportDataFactoryException("The specified class cannot be instantiated: it is abstract.");
407 }
408
409 Constructor[] methods = c.getConstructors();
410 for (int i = 0; i < methods.length; i++)
411 {
412 final Constructor method = methods[i];
413 if (Modifier.isPublic(method.getModifiers()) == false)
414 {
415 continue;
416 }
417 if (method.getParameterTypes().length != paramCount)
418 {
419 continue;
420 }
421 return method;
422 }
423 }
424 catch (ClassNotFoundException e)
425 {
426 throw new ReportDataFactoryException("No such Class", e);
427 }
428 throw new ReportDataFactoryException
429 ("There is no constructor in class " + className +
430 " that accepts " + paramCount + " parameters.");
431 }
432
433
434 public void open()
435 {
436
437 }
438
439 public void close()
440 {
441
442 }
443
444 /**
445 * Derives a freshly initialized report data factory, which is independend of
446 * the original data factory. Opening or Closing one data factory must not
447 * affect the other factories.
448 *
449 * @return
450 */
451 public ReportDataFactory derive()
452 {
453 return this;
454 }
455 }