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 */ 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 final Constructor c = findDirectConstructor(constructorName, parameterNames.length); 106 107 final 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 final 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 final ClassLoader classLoader = getClassLoader(); 299 300 if (classLoader == null) 301 { 302 throw new ReportDataFactoryException("No classloader!"); 303 } 304 try 305 { 306 final Class c = classLoader.loadClass(className); 307 if (Modifier.isAbstract(c.getModifiers())) 308 { 309 throw new ReportDataFactoryException("Abstract class cannot be handled!"); 310 } 311 312 final 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 final ClassLoader classLoader = getClassLoader(); 348 if (classLoader == null) 349 { 350 throw new ReportDataFactoryException("No classloader!"); 351 } 352 353 try 354 { 355 final 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 final 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 final ClassLoader classLoader = getClassLoader(); 396 if (classLoader == null) 397 { 398 throw new ReportDataFactoryException("No classloader!"); 399 } 400 401 try 402 { 403 final 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 final 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 }