Source for org.jfree.report.modules.data.beans.StaticReportDataFactory

   1: /**
   2:  * ========================================
   3:  * JFreeReport : a free Java report library
   4:  * ========================================
   5:  *
   6:  * Project Info:  http://reporting.pentaho.org/
   7:  *
   8:  * (C) Copyright 2000-2007, by Object Refinery Limited, Pentaho Corporation and Contributors.
   9:  *
  10:  * This library is free software; you can redistribute it and/or modify it under the terms
  11:  * of the GNU Lesser General Public License as published by the Free Software Foundation;
  12:  * either version 2.1 of the License, or (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  15:  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  16:  * See the GNU Lesser General Public License for more details.
  17:  *
  18:  * You should have received a copy of the GNU Lesser General Public License along with this
  19:  * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  20:  * Boston, MA 02111-1307, USA.
  21:  *
  22:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  23:  * in the United States and other countries.]
  24:  *
  25:  * ------------
  26:  * $Id: StaticReportDataFactory.java 3525 2007-10-16 11:43:48Z tmorgner $
  27:  * ------------
  28:  * (C) Copyright 2000-2005, by Object Refinery Limited.
  29:  * (C) Copyright 2005-2007, by Pentaho Corporation.
  30:  */
  31: package org.jfree.report.modules.data.beans;
  32: 
  33: import java.lang.reflect.Method;
  34: import java.lang.reflect.Modifier;
  35: import java.lang.reflect.Constructor;
  36: 
  37: import javax.swing.table.TableModel;
  38: 
  39: import org.jfree.report.DataSet;
  40: import org.jfree.report.ReportData;
  41: import org.jfree.report.ReportDataFactory;
  42: import org.jfree.report.ReportDataFactoryException;
  43: import org.jfree.report.TableReportData;
  44: import org.jfree.report.util.CSVTokenizer;
  45: import org.jfree.report.util.DataSetUtility;
  46: import org.jfree.util.ObjectUtilities;
  47: 
  48: /**
  49:  * This report data factory uses introspection to search for a report data
  50:  * source. The query has the following format:
  51:  *
  52:  * <full-qualified-classname&gr;#methodName(Parameters)
  53:  * <full-qualified-classname&gr;(constructorparams)#methodName(Parameters)
  54:  * <full-qualified-classname&gr;(constructorparams)
  55:  *
  56:  * @author Thomas Morgner
  57:  */
  58: public class StaticReportDataFactory implements ReportDataFactory
  59: {
  60:   public StaticReportDataFactory()
  61:   {
  62:   }
  63: 
  64:   /**
  65:    * Queries a datasource. The string 'query' defines the name of the query. The
  66:    * Parameterset given here may contain more data than actually needed.
  67:    * <p/>
  68:    * The dataset may change between two calls, do not assume anything!
  69:    *
  70:    * @param query
  71:    * @param parameters
  72:    * @return
  73:    */
  74:   public ReportData queryData(final String query, final DataSet parameters)
  75:           throws ReportDataFactoryException
  76:   {
  77:     final int methodSeparatorIdx = query.indexOf('#');
  78: 
  79:     if ((methodSeparatorIdx + 1) >= query.length())
  80:     {
  81:       // If we have a method separator, then it cant be at the end of the text.
  82:       throw new ReportDataFactoryException("Malformed query: " + query);
  83:     }
  84: 
  85:     if (methodSeparatorIdx == -1)
  86:     {
  87:       // we have no method. So this query must be a reference to a tablemodel
  88:       // instance.
  89:       final String[] parameterNames;
  90:       final int parameterStartIdx = query.indexOf('(');
  91:       final String constructorName;
  92:       if (parameterStartIdx == -1)
  93:       {
  94:         parameterNames = new String[0];
  95:         constructorName = query;
  96:       }
  97:       else
  98:       {
  99:         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: }