1   /*
2    * Copyright 2003 - 2013 The eFaps Team
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Revision:        $Rev$
17   * Last Changed:    $Date$
18   * Last Changed By: $Author$
19   */
20  
21  package org.efaps.update.schema.program.jasperreport;
22  
23  import groovy.lang.GroovyClassLoader;
24  import groovyjarjarasm.asm.ClassVisitor;
25  import groovyjarjarasm.asm.ClassWriter;
26  
27  import java.io.ByteArrayInputStream;
28  import java.io.File;
29  import java.io.Serializable;
30  import java.io.UnsupportedEncodingException;
31  import java.util.HashMap;
32  import java.util.Map;
33  
34  import net.sf.jasperreports.compilers.JRGroovyCompiler;
35  import net.sf.jasperreports.engine.DefaultJasperReportsContext;
36  import net.sf.jasperreports.engine.JRException;
37  import net.sf.jasperreports.engine.JasperReportsContext;
38  import net.sf.jasperreports.engine.design.JRCompilationUnit;
39  
40  import org.codehaus.groovy.ast.ClassNode;
41  import org.codehaus.groovy.control.CompilationFailedException;
42  import org.codehaus.groovy.control.CompilationUnit;
43  import org.codehaus.groovy.control.CompilerConfiguration;
44  import org.codehaus.groovy.control.Phases;
45  import org.efaps.admin.program.esjp.EFapsClassLoader;
46  
47  /**
48   * Calculator compiler that uses groovy to compile expressions. It is used due
49   * to the reason that the Classloader inside groovy must be set to use the
50   * classpath from the maven targets.
51   *
52   * @author The eFaps Team
53   * @version $Id$
54   */
55  public class JasperGroovyCompiler
56      extends JRGroovyCompiler
57  {
58  
59      /**
60       *
61       */
62      public JasperGroovyCompiler()
63      {
64          this(DefaultJasperReportsContext.getInstance());
65      }
66  
67      /**
68       * @param _jasperReportsContext context to be used
69       */
70      public JasperGroovyCompiler(final JasperReportsContext _jasperReportsContext)
71      {
72          super(_jasperReportsContext);
73      }
74  
75      /**
76       * @see net.sf.jasperreports.compilers.JRGroovyCompiler#compileUnits(
77       * net.sf.jasperreports.engine.design.JRCompilationUnit[],
78       *      java.lang.String, java.io.File)
79       * @param _units        compilation units for Jasper
80       * @param _classpath    classpath
81       * @param _tempDirFile  directory for the temporary files
82       * @return null
83       * @throws JRException on error during compilation
84       */
85      @Override
86      protected String compileUnits(final JRCompilationUnit[] _units,
87                                    final String _classpath,
88                                    final File _tempDirFile)
89          throws JRException
90      {
91          final CompilerConfiguration config = new CompilerConfiguration();
92          config.setClasspath(_classpath);
93          config.setVerbose(true);
94          final GroovyClassLoader loader = new GroovyClassLoader(EFapsClassLoader.getInstance(), config, true);
95          final CompilationUnit unit = new CompilationUnit(loader);
96  
97          for (int i = 0; i < _units.length; i++) {
98              try {
99                  unit.addSource("calculator_" + _units[i].getName(), new ByteArrayInputStream(_units[i].getSourceCode()
100                                 .getBytes("UTF-8")));
101             } catch (final UnsupportedEncodingException e) {
102                 throw new JRException("Cannot add Source", e);
103             }
104         }
105 
106         final ClassCollector collector = new ClassCollector();
107         unit.setClassgenCallback(collector);
108         try {
109             unit.compile(Phases.CLASS_GENERATION);
110         } catch (final CompilationFailedException e) {
111             throw new JRException("Errors were encountered when compiling report expressions class file:\n"
112                             + e.toString());
113         }
114 
115         if (collector.classes.size() < _units.length) {
116             throw new JRException("Too few groovy class were generated.");
117         } else if (collector.classCount > _units.length) {
118             throw new JRException("Too many groovy classes were generated.\n"
119                       + "Please make sure that you don't use Groovy features such as closures "
120                       + "that are not supported by this report compiler.\n");
121         }
122 
123         for (int i = 0; i < _units.length; i++) {
124             _units[i].setCompileData((Serializable) collector.classes.get(_units[i].getName()));
125         }
126 
127         return null;
128     }
129 
130     /**
131      * Class is a exact copy of the inner class of this parent class. This is
132      * done due to private definition.
133      */
134     private static class ClassCollector extends CompilationUnit.ClassgenCallback
135     {
136         /**
137          * Name to classes.
138          */
139         private final Map<String, Object> classes = new HashMap<String, Object>();
140 
141         /**
142          * Count of classes.
143          */
144         private int classCount;
145 
146         /**
147          * @see org.codehaus.groovy.control.CompilationUnit.ClassgenCallback#call(groovyjarjarasm.asm.ClassVisitor,
148          * org.codehaus.groovy.ast.ClassNode)
149          * @param _writer   writer
150          * @param _node     node
151          * @throws CompilationFailedException on error
152          */
153         @Override
154         public void call(final ClassVisitor _writer,
155                          final ClassNode _node)
156         {
157             this.classCount++;
158             final String name = _node.getName();
159             if (!this.classes.containsKey(name)) {
160                 final byte[] bytes = ((ClassWriter) _writer).toByteArray();
161                 this.classes.put(name, bytes);
162             }
163         }
164     }
165 }