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    }