/* * Copyright 2014 Jocki Hendry * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package simple.escp.fill; import simple.escp.dom.Page; import simple.escp.dom.Report; import simple.escp.data.DataSource; import simple.escp.fill.function.AsciiFunction; import simple.escp.fill.function.AutoIncrementFunction; import simple.escp.fill.function.BoldFunction; import simple.escp.fill.function.DoubleStrikeFunction; import simple.escp.fill.function.Function; import simple.escp.fill.function.GlobalLineNoFunction; import simple.escp.fill.function.ItalicFunction; import simple.escp.fill.function.LineNoFunction; import simple.escp.fill.function.PageNoFunction; import simple.escp.fill.function.SubscriptFunction; import simple.escp.fill.function.SuperscriptFunction; import simple.escp.fill.function.UnderlineFunction; import simple.escp.placeholder.BasicPlaceholder; import simple.escp.placeholder.Placeholder; import simple.escp.placeholder.ScriptPlaceholder; import simple.escp.util.EscpUtil; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * <code>FillJob</code> represent the process of filling a <code>Report</code> with one or more * <code>DataSource</code>. The result of this process is a <code>String</code> that may contains ESC/P commands * for printing. * * <p>A new instance of <code>FillJob</code> should be created for every process of <code>Report</code>'s filling. * The new instance can reuse existing <code>Report</code> or <code>DataSource</code>. */ public class FillJob { private static final Logger LOG = Logger.getLogger("simple.escp"); public static final Pattern BASIC_PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{(.+?)\\}"); public static final Pattern SCRIPT_PLACEHOLDER_PATTERN = Pattern.compile("\\{\\{(.+?)\\}\\}"); public static final List<Function> FUNCTIONS; static { FUNCTIONS = new ArrayList<>(); FUNCTIONS.add(new BoldFunction()); FUNCTIONS.add(new ItalicFunction()); FUNCTIONS.add(new UnderlineFunction()); FUNCTIONS.add(new DoubleStrikeFunction()); FUNCTIONS.add(new SuperscriptFunction()); FUNCTIONS.add(new SubscriptFunction()); FUNCTIONS.add(new PageNoFunction()); FUNCTIONS.add(new AsciiFunction()); FUNCTIONS.add(new AutoIncrementFunction()); FUNCTIONS.add(new GlobalLineNoFunction()); FUNCTIONS.add(new LineNoFunction()); } protected Report report; protected DataSource[] dataSources; protected Map<String, Placeholder> placeholders = new HashMap<>(); protected ScriptEngine scriptEngine; /** * Create a new <code>FillJob</code> with empty data source. * * @param report the <code>Report</code> that will be filled. */ public FillJob(Report report) { this(report, new DataSource[0]); } /** * Create a new <code>FillJob</code> with single <code>DataSource</code>. * * @param report the <code>Report</code> that will be filled. * @param dataSource the <code>DataSource</code> that contains values for filling. */ public FillJob(Report report, DataSource dataSource) { this(report, new DataSource[] {dataSource}); } /** * Create a new <code>FillJob</code> with multipe <code>DataSource</code>. * * @param report the <code>Report</code> that will be filled. * @param dataSources array that contains <code>DataSource</code> as the source values for filling. */ public FillJob(Report report, DataSource[] dataSources) { this.report = report; this.dataSources = Arrays.copyOf(dataSources, dataSources.length); // Create script engine for ScriptPlaceholder ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); scriptEngineManager.setBindings(new DataSourceBinding(this.dataSources)); this.scriptEngine = scriptEngineManager.getEngineByName("groovy"); if (this.scriptEngine == null) { LOG.fine("Can't find Groovy script engine, will use JavaScript script engine."); this.scriptEngine = scriptEngineManager.getEngineByName("JavaScript"); } // Reset functions for (Function function : FUNCTIONS) { function.reset(); } } /** * Register a new global function. This function will have a lower priority compared to built-in function. * * @param function a new function that will be available for current and subsequent executions. */ public static void addFunction(Function function) { if (!FUNCTIONS.contains(function)) { FUNCTIONS.add(function); } } /** * Remove a registered global function. * * @param function an existing function that will be removed from list available of functions. */ public static void removeFunction(Function function) { FUNCTIONS.remove(function); } /** * Add a new variable to current script engine that can be used by script placeholders later. * * @param variableName the new variable's name. * @param value the value of this new variable. */ public void addScriptVariable(String variableName, Object value) { scriptEngine.put(variableName, value); } /** * Remove a variable from current script engine. This method will remove variable that was in engine scope. * It can't be used to remove built-in variables in global scope. * * @param variableName the name of variable that will be removed. */ public void removeScriptVariable(String variableName) { scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE).remove(variableName); } /** * Retrieve the report that will be filled by this <code>FillJob</code>. * * @return an instance of <code>Report</code>. */ public Report getReport() { return report; } /** * Retrieve all data source for this <code>FillJob</code>. * * @return an array of <code>DataSource</code>. */ public DataSource[] getDataSources() { return Arrays.copyOf(dataSources, dataSources.length); } /** * Get available <code>Placeholder</code> in this report. * * @return a <code>Map</code> that contains all <code>Placeholder</code> in this report. */ public Map<String, Placeholder> getPlaceholders() { return placeholders; } /** * Find and return a <code>Placeholder</code> by its text. * @param text the placeholder's text. A placeholder text appears as is in template. For example, * text for <code>${\@name}</code> is <code>"@name"</code>. * @return a <code>Placeholder</code> if it is found, or <code>null</code> if no placeholder with the specified * text is exists in this report. */ public Placeholder getPlaceholder(String text) { return placeholders.get(text); } /** * This method will fill placeholders with value from both supplied <code>Map</code> and Java Bean object. * * @param text the source text that has placeholders. * @return source with placeholders replaced by actual value. */ protected String fillBasicPlaceholder(String text) { StringBuffer result = new StringBuffer(); Matcher matcher = BASIC_PLACEHOLDER_PATTERN.matcher(text); while (matcher.find()) { String placeholderText = matcher.group(1); LOG.fine("Found basic placeholder text [" + placeholderText + "]"); Placeholder placeholder = placeholders.get(placeholderText); if (placeholder == null) { placeholder = new BasicPlaceholder(placeholderText); placeholders.put(placeholderText, placeholder); } matcher.appendReplacement(result, placeholder.getValueAsString(dataSources)); } matcher.appendTail(result); return result.toString(); } /** * This method will fill placeholders by executing the script inside that placeholder. * * @param text the source text that has placeholders. * @return source with placeholders replaced by actual value. */ protected String fillScriptPlaceholder(String text) { StringBuffer result = new StringBuffer(); Matcher matcher = SCRIPT_PLACEHOLDER_PATTERN.matcher(text); while (matcher.find()) { String placeholderText = matcher.group(1); LOG.fine("Found script placeholder text [" + placeholderText + "]"); Placeholder placeholder = placeholders.get(placeholderText); if (placeholder == null) { placeholder = new ScriptPlaceholder(placeholderText, scriptEngine); placeholders.put(placeholderText, placeholder); } matcher.appendReplacement(result, placeholder.getValueAsString(dataSources)); } matcher.appendTail(result); return result.toString(); } /** * Execute this <code>FillJob</code> action. This will perform the action of filling <code>Report</code> with * one or more <code>DataSource</code>. This method will not modify the original <code>Report</code>. * * @return a <code>String</code> that may contains ESC/P commands and can be printed. */ public String fill() { Report parsedReport = new Report(report); // Second phase: fill dynamic line, change last page footer, etc. if (parsedReport.hasDynamicLine()) { LOG.fine("This report has dynamic line."); TableFillJob tableFillJob = new TableFillJob(parsedReport, dataSources); ListFillJob listFillJob = new ListFillJob(parsedReport, dataSources); tableFillJob.fill(); listFillJob.fill(); } int lastPageFooterLength = parsedReport.getLastPageFooter().length; if (lastPageFooterLength > 0) { Page lastPage = parsedReport.getPage(parsedReport.getLastPageNumber()); lastPage.setFooter(parsedReport.getLastPageFooter()); if (lastPage.isOverflow()) { lastPage.setFooter(parsedReport.getFooter()); parsedReport.newPage(false).setFooter(parsedReport.getLastPageFooter()); } } StringBuilder result = new StringBuilder(); boolean isAutoLineFeed = parsedReport.getPageFormat().isAutoLineFeed(); boolean isAutoFormFeed = parsedReport.getPageFormat().isAutoFormFeed(); result.append(parsedReport.getPageFormat().build()); // process functions for (Function function : FUNCTIONS) { LOG.fine("Executing function [" + function + "]"); function.process(parsedReport); } // process placeholders for (Page page : parsedReport) { String pageText = page.convertToString(isAutoLineFeed, isAutoFormFeed); pageText = fillBasicPlaceholder(pageText); pageText = fillScriptPlaceholder(pageText); result.append(pageText); } if (isAutoFormFeed && !result.toString().endsWith(EscpUtil.CRFF)) { result.append(EscpUtil.CRFF); } result.append(EscpUtil.escInitalize()); return result.toString(); } }