001: /*
002: * ============================================================================
003: * GNU Lesser General Public License
004: * ============================================================================
005: *
006: * JasperReports - Free Java report-generating library.
007: * Copyright (C) 2001-2006 JasperSoft Corporation http://www.jaspersoft.com
008: *
009: * This library is free software; you can redistribute it and/or
010: * modify it under the terms of the GNU Lesser General Public
011: * License as published by the Free Software Foundation; either
012: * 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,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017: * Lesser General Public License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
022: *
023: * JasperSoft Corporation
024: * 303 Second Street, Suite 450 North
025: * San Francisco, CA 94107
026: * http://www.jaspersoft.com
027: */
028: package net.sf.jasperreports.engine.design;
029:
030: import java.io.File;
031: import java.io.Serializable;
032: import java.util.Collection;
033: import java.util.Iterator;
034: import java.util.List;
035: import java.util.ListIterator;
036: import java.util.Random;
037:
038: import net.sf.jasperreports.crosstabs.JRCrosstab;
039: import net.sf.jasperreports.crosstabs.design.JRDesignCrosstab;
040: import net.sf.jasperreports.engine.JRDataset;
041: import net.sf.jasperreports.engine.JRException;
042: import net.sf.jasperreports.engine.JRExpressionCollector;
043: import net.sf.jasperreports.engine.JRReport;
044: import net.sf.jasperreports.engine.JRRuntimeException;
045: import net.sf.jasperreports.engine.JasperReport;
046: import net.sf.jasperreports.engine.fill.JREvaluator;
047: import net.sf.jasperreports.engine.util.JRProperties;
048: import net.sf.jasperreports.engine.util.JRSaver;
049: import net.sf.jasperreports.engine.util.JRStringUtil;
050:
051: /**
052: * Base class for report compilers.
053: *
054: * @author Lucian Chirita (lucianc@users.sourceforge.net)
055: * @version $Id: JRAbstractCompiler.java 1625 2007-03-09 17:21:31Z lucianc $
056: */
057: public abstract class JRAbstractCompiler implements JRCompiler {
058: private static final int NAME_SUFFIX_RANDOM_MAX = 1000000;
059: private static final Random random = new Random();
060:
061: private final boolean needsSourceFiles;
062:
063: /**
064: * Constructor.
065: *
066: * @param needsSourceFiles whether the compiler needs source files or is able to do in memory compilation
067: * <p>
068: * If true, the generated code is saved in source files to be used by the compiler.
069: */
070: protected JRAbstractCompiler(boolean needsSourceFiles) {
071: this .needsSourceFiles = needsSourceFiles;
072: }
073:
074: /**
075: * Returns the name of the expression evaluator unit for a dataset of a report.
076: *
077: * @param report the report
078: * @param dataset the dataset
079: * @return the generated expression evaluator unit name
080: */
081: public static String getUnitName(JasperReport report,
082: JRDataset dataset) {
083: return getUnitName(report, dataset, report
084: .getCompileNameSuffix());
085: }
086:
087: protected static String getUnitName(JRReport report,
088: JRDataset dataset, String nameSuffix) {
089: String className;
090: if (dataset.isMainDataset()) {
091: className = report.getName();
092: } else {
093: className = report.getName() + "_" + dataset.getName();
094: }
095:
096: className = JRStringUtil.getLiteral(className) + nameSuffix;
097:
098: return className;
099: }
100:
101: /**
102: * Returns the name of the expression evaluator unit for a crosstab of a report.
103: *
104: * @param report the report
105: * @param crosstab the crosstab
106: * @return the generated expression evaluator unit name
107: */
108: public static String getUnitName(JasperReport report,
109: JRCrosstab crosstab) {
110: return getUnitName(report, crosstab.getId(), report
111: .getCompileNameSuffix());
112: }
113:
114: protected static String getUnitName(JRReport report,
115: JRCrosstab crosstab,
116: JRExpressionCollector expressionCollector, String nameSuffix) {
117: Integer crosstabId = expressionCollector
118: .getCrosstabId(crosstab);
119: if (crosstabId == null) {
120: throw new JRRuntimeException("Crosstab ID not found.");
121: }
122:
123: return getUnitName(report, crosstabId.intValue(), nameSuffix);
124: }
125:
126: protected static String getUnitName(JRReport report,
127: int crosstabId, String nameSuffix) {
128: return JRStringUtil.getLiteral(report.getName()) + "_CROSSTAB"
129: + crosstabId + nameSuffix;
130: }
131:
132: public final JasperReport compileReport(JasperDesign jasperDesign)
133: throws JRException {
134: // check if the language is supported by the compiler
135: checkLanguage(jasperDesign.getLanguage());
136:
137: // collect all report expressions
138: JRExpressionCollector expressionCollector = JRExpressionCollector
139: .collector(jasperDesign);
140:
141: // verify the report design
142: verifyDesign(jasperDesign, expressionCollector);
143:
144: String nameSuffix = createNameSuffix();
145:
146: // check if saving source files is required
147: boolean isKeepJavaFile = JRProperties
148: .getBooleanProperty(JRProperties.COMPILER_KEEP_JAVA_FILE);
149: File tempDirFile = null;
150: if (isKeepJavaFile || needsSourceFiles) {
151: String tempDirStr = JRProperties
152: .getProperty(JRProperties.COMPILER_TEMP_DIR);
153:
154: tempDirFile = new File(tempDirStr);
155: if (!tempDirFile.exists() || !tempDirFile.isDirectory()) {
156: throw new JRException(
157: "Temporary directory not found : " + tempDirStr);
158: }
159: }
160:
161: List datasets = jasperDesign.getDatasetsList();
162: List crosstabs = jasperDesign.getCrosstabs();
163:
164: JRCompilationUnit[] units = new JRCompilationUnit[datasets
165: .size()
166: + crosstabs.size() + 1];
167:
168: // generating source code for the main report dataset
169: units[0] = createCompileUnit(jasperDesign, jasperDesign
170: .getMainDesignDataset(), expressionCollector,
171: tempDirFile, nameSuffix);
172:
173: int sourcesCount = 1;
174: for (Iterator it = datasets.iterator(); it.hasNext(); ++sourcesCount) {
175: JRDesignDataset dataset = (JRDesignDataset) it.next();
176: // generating source code for a sub dataset
177: units[sourcesCount] = createCompileUnit(jasperDesign,
178: dataset, expressionCollector, tempDirFile,
179: nameSuffix);
180: }
181:
182: for (Iterator it = crosstabs.iterator(); it.hasNext(); ++sourcesCount) {
183: JRDesignCrosstab crosstab = (JRDesignCrosstab) it.next();
184: // generating source code for a sub dataset
185: units[sourcesCount] = createCompileUnit(jasperDesign,
186: crosstab, expressionCollector, tempDirFile,
187: nameSuffix);
188: }
189:
190: String classpath = JRProperties
191: .getProperty(JRProperties.COMPILER_CLASSPATH);
192:
193: try {
194: // compiling generated sources
195: String compileErrors = compileUnits(units, classpath,
196: tempDirFile);
197: if (compileErrors != null) {
198: throw new JRException(
199: "Errors were encountered when compiling report expressions class file:\n"
200: + compileErrors);
201: }
202:
203: // creating the report compile data
204: JRReportCompileData reportCompileData = new JRReportCompileData();
205: reportCompileData.setMainDatasetCompileData(units[0]
206: .getCompileData());
207:
208: for (ListIterator it = datasets.listIterator(); it
209: .hasNext();) {
210: JRDesignDataset dataset = (JRDesignDataset) it.next();
211: reportCompileData.setDatasetCompileData(dataset,
212: units[it.nextIndex()].getCompileData());
213: }
214:
215: for (ListIterator it = crosstabs.listIterator(); it
216: .hasNext();) {
217: JRDesignCrosstab crosstab = (JRDesignCrosstab) it
218: .next();
219: Integer crosstabId = expressionCollector
220: .getCrosstabId(crosstab);
221: reportCompileData.setCrosstabCompileData(crosstabId
222: .intValue(), units[datasets.size()
223: + it.nextIndex()].getCompileData());
224: }
225:
226: // creating the report
227: JasperReport jasperReport = new JasperReport(jasperDesign,
228: getCompilerClass(), reportCompileData,
229: expressionCollector, nameSuffix);
230:
231: return jasperReport;
232: } catch (JRException e) {
233: throw e;
234: } catch (Exception e) {
235: throw new JRException("Error compiling report design.", e);
236: } finally {
237: if (needsSourceFiles && !isKeepJavaFile) {
238: deleteSourceFiles(units);
239: }
240: }
241: }
242:
243: private static String createNameSuffix() {
244: return "_" + System.currentTimeMillis() + "_"
245: + random.nextInt(NAME_SUFFIX_RANDOM_MAX);
246: }
247:
248: protected String getCompilerClass() {
249: return getClass().getName();
250: }
251:
252: private void verifyDesign(JasperDesign jasperDesign,
253: JRExpressionCollector expressionCollector)
254: throws JRException {
255: Collection brokenRules = JRVerifier.verifyDesign(jasperDesign,
256: expressionCollector);
257: if (brokenRules != null && brokenRules.size() > 0) {
258: throw new JRValidationException(brokenRules);
259: }
260: }
261:
262: private JRCompilationUnit createCompileUnit(
263: JasperDesign jasperDesign, JRDesignDataset dataset,
264: JRExpressionCollector expressionCollector,
265: File saveSourceDir, String nameSuffix) throws JRException {
266: String unitName = JRAbstractCompiler.getUnitName(jasperDesign,
267: dataset, nameSuffix);
268:
269: JRSourceCompileTask sourceTask = new JRSourceCompileTask(
270: jasperDesign, dataset, expressionCollector, unitName);
271: JRCompilationSourceCode sourceCode = generateSourceCode(sourceTask);
272:
273: File sourceFile = getSourceFile(saveSourceDir, unitName,
274: sourceCode);
275:
276: return new JRCompilationUnit(unitName, sourceCode, sourceFile,
277: expressionCollector.getExpressions(dataset));
278: }
279:
280: private JRCompilationUnit createCompileUnit(
281: JasperDesign jasperDesign, JRDesignCrosstab crosstab,
282: JRExpressionCollector expressionCollector,
283: File saveSourceDir, String nameSuffix) throws JRException {
284: String unitName = JRAbstractCompiler.getUnitName(jasperDesign,
285: crosstab, expressionCollector, nameSuffix);
286:
287: JRSourceCompileTask sourceTask = new JRSourceCompileTask(
288: jasperDesign, crosstab, expressionCollector, unitName);
289: JRCompilationSourceCode sourceCode = generateSourceCode(sourceTask);
290:
291: File sourceFile = getSourceFile(saveSourceDir, unitName,
292: sourceCode);
293:
294: return new JRCompilationUnit(unitName, sourceCode, sourceFile,
295: expressionCollector.getExpressions(crosstab));
296: }
297:
298: private File getSourceFile(File saveSourceDir, String unitName,
299: JRCompilationSourceCode sourceCode) throws JRException {
300: File sourceFile = null;
301: if (saveSourceDir != null) {
302: String fileName = getSourceFileName(unitName);
303: sourceFile = new File(saveSourceDir, fileName);
304:
305: JRSaver.saveClassSource(sourceCode.getCode(), sourceFile);
306: }
307: return sourceFile;
308: }
309:
310: private void deleteSourceFiles(JRCompilationUnit[] units) {
311: for (int i = 0; i < units.length; i++) {
312: units[i].getSourceFile().delete();
313: }
314: }
315:
316: public JREvaluator loadEvaluator(JasperReport jasperReport)
317: throws JRException {
318: return loadEvaluator(jasperReport, jasperReport
319: .getMainDataset());
320: }
321:
322: public JREvaluator loadEvaluator(JasperReport jasperReport,
323: JRDataset dataset) throws JRException {
324: String unitName = JRAbstractCompiler.getUnitName(jasperReport,
325: dataset);
326: JRReportCompileData reportCompileData = (JRReportCompileData) jasperReport
327: .getCompileData();
328: Serializable compileData = reportCompileData
329: .getDatasetCompileData(dataset);
330: return loadEvaluator(compileData, unitName);
331: }
332:
333: public JREvaluator loadEvaluator(JasperReport jasperReport,
334: JRCrosstab crosstab) throws JRException {
335: String unitName = JRAbstractCompiler.getUnitName(jasperReport,
336: crosstab);
337: JRReportCompileData reportCompileData = (JRReportCompileData) jasperReport
338: .getCompileData();
339: Serializable compileData = reportCompileData
340: .getCrosstabCompileData(crosstab);
341: return loadEvaluator(compileData, unitName);
342: }
343:
344: /**
345: * Creates an expression evaluator instance from data saved when the report was compiled.
346: *
347: * @param compileData the data saved when the report was compiled
348: * @param unitName the evaluator unit name
349: * @return an expression evaluator instance
350: * @throws JRException
351: */
352: protected abstract JREvaluator loadEvaluator(
353: Serializable compileData, String unitName)
354: throws JRException;
355:
356: /**
357: * Checks that the report language is supported by the compiler.
358: *
359: * @param language the report language
360: * @throws JRException
361: */
362: protected abstract void checkLanguage(String language)
363: throws JRException;
364:
365: /**
366: * Generates expression evaluator code.
367: *
368: * @param sourceTask the source code generation information
369: * @return generated expression evaluator code
370: * @throws JRException
371: */
372: protected abstract JRCompilationSourceCode generateSourceCode(
373: JRSourceCompileTask sourceTask) throws JRException;
374:
375: /**
376: * Compiles several expression evaluator units.
377: * <p>
378: * The result of the compilation should be set by calling
379: * {@link JRCompilationUnit#setCompileData(Serializable) setCompileData} on all compile units.
380: *
381: * @param units the compilation units
382: * @param classpath the compilation classpath
383: * @param tempDirFile temporary directory
384: * @return a string containing compilation errors, or null if the compilation was successfull
385: * @throws JRException
386: */
387: protected abstract String compileUnits(JRCompilationUnit[] units,
388: String classpath, File tempDirFile) throws JRException;
389:
390: /**
391: * Returns the name of the source file where generated source code for an unit is saved.
392: * <p>
393: * If the compiler needs source files for compilation
394: * or {@link JRProperties#COMPILER_KEEP_JAVA_FILE COMPILER_KEEP_JAVA_FILE} is set, the generated source
395: * will be saved in a file having the name returned by this method.
396: *
397: * @param unitName the unit name
398: * @return the source file name
399: */
400: protected abstract String getSourceFileName(String unitName);
401: }
|