Java tutorial
/* * JasperReports - Free Java Reporting Library. * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved. * http://www.jaspersoft.com * * Unless you have purchased a commercial license agreement from Jaspersoft, * the following license terms apply: * * This program is part of JasperReports. * * JasperReports is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * JasperReports is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with JasperReports. If not, see <http://www.gnu.org/licenses/>. */ package net.sf.jasperreports.engine.fill; import java.text.DecimalFormat; import java.text.Format; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; import java.util.TimeZone; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import net.sf.jasperreports.engine.JRException; import net.sf.jasperreports.engine.JRExpression; import net.sf.jasperreports.engine.JRExpressionCollector; import net.sf.jasperreports.engine.JRGroup; import net.sf.jasperreports.engine.JRHyperlinkParameter; import net.sf.jasperreports.engine.JRPrintElement; import net.sf.jasperreports.engine.JRPrintHyperlinkParameters; import net.sf.jasperreports.engine.JRPrintText; import net.sf.jasperreports.engine.JRStyle; import net.sf.jasperreports.engine.JRTextField; import net.sf.jasperreports.engine.JRVisitor; import net.sf.jasperreports.engine.type.EvaluationTimeEnum; import net.sf.jasperreports.engine.type.HyperlinkTypeEnum; import net.sf.jasperreports.engine.type.PositionTypeEnum; import net.sf.jasperreports.engine.type.RotationEnum; import net.sf.jasperreports.engine.util.JRDataUtils; import net.sf.jasperreports.engine.util.Pair; /** * @author Teodor Danciu (teodord@users.sourceforge.net) */ public class JRFillTextField extends JRFillTextElement implements JRTextField { protected static final Log log = LogFactory.getLog(JRFillTextField.class); protected final Map<Pair<JRStyle, TextFormat>, JRTemplateElement> textTemplates; /** * */ private JRGroup evaluationGroup; /** * */ private Object value; private TimeZone ownTimeZone; /** * */ private TextFormat textFormat; /** * */ private String pattern; /** * */ private String anchorName; private String hyperlinkReference; private Boolean hyperlinkWhen; private String hyperlinkAnchor; private Integer hyperlinkPage; private String hyperlinkTooltip; private JRPrintHyperlinkParameters hyperlinkParameters; private static final String NULL_VALUE = new String(); private final Map<String, String> localizedProperties; //FIXME keep these in the filler/context private Map<String, TimeZone> generalPatternTimeZones = new HashMap<String, TimeZone>(); /** * */ protected JRFillTextField(JRBaseFiller filler, JRTextField textField, JRFillObjectFactory factory) { super(filler, textField, factory); this.textTemplates = new HashMap<Pair<JRStyle, TextFormat>, JRTemplateElement>(); evaluationGroup = factory.getGroup(textField.getEvaluationGroup()); this.localizedProperties = new HashMap<String, String>(); } protected JRFillTextField(JRFillTextField textField, JRFillCloneFactory factory) { super(textField, factory); this.textTemplates = textField.textTemplates; this.evaluationGroup = textField.evaluationGroup; this.localizedProperties = textField.localizedProperties; } @Override public boolean isStretchWithOverflow() { return ((JRTextField) parent).isStretchWithOverflow(); } @Override public void setStretchWithOverflow(boolean isStretchWithOverflow) { } @Override public EvaluationTimeEnum getEvaluationTimeValue() { return ((JRTextField) parent).getEvaluationTimeValue(); } /** * */ protected TextFormat getTextFormat() { return textFormat; } @Override public String getPattern() { if (getPatternExpression() == null) { return getStyleResolver().getPattern(this); } return pattern; } protected String getDatePattern(Object value) { String pattern = getPattern(); if (pattern != null || value == null) { return pattern; } String property; if (value instanceof java.sql.Date) { property = JRTextField.PROPERTY_PATTERN_DATE; } else if (value instanceof java.sql.Time) { property = JRTextField.PROPERTY_PATTERN_TIME; } else { property = JRTextField.PROPERTY_PATTERN_DATETIME; } return getLocalizedProperty(property); } protected String getNumberPattern(Object value) { String pattern = getPattern(); if (pattern != null || value == null) { return pattern; } String property; if (value instanceof java.lang.Byte || value instanceof java.lang.Short || value instanceof java.lang.Integer || value instanceof java.lang.Long || value instanceof java.math.BigInteger) { property = JRTextField.PROPERTY_PATTERN_INTEGER; } else { property = JRTextField.PROPERTY_PATTERN_NUMBER; } return getLocalizedProperty(property); } protected String getLocalizedProperty(String property) { // caching locally because it's not cached in JRPropertiesUtil String value = localizedProperties.get(property); if (value == null) { value = filler.getPropertiesUtil().getLocalizedProperty(property, filler.getLocale()); localizedProperties.put(property, value == null ? NULL_VALUE : value); } else if (value == NULL_VALUE) { value = null; } return value; } @Override public String getOwnPattern() { return providerStyle == null || providerStyle.getOwnPattern() == null ? ((JRTextField) this.parent).getOwnPattern() : providerStyle.getOwnPattern(); } @Override public void setPattern(String pattern) { } @Override public boolean isBlankWhenNull() { return getStyleResolver().isBlankWhenNull(this); } @Override public Boolean isOwnBlankWhenNull() { return providerStyle == null || providerStyle.isOwnBlankWhenNull() == null ? ((JRTextField) this.parent).isOwnBlankWhenNull() : providerStyle.isOwnBlankWhenNull(); } @Override public void setBlankWhenNull(boolean isBlank) { } @Override public void setBlankWhenNull(Boolean isBlank) { } /** * @deprecated Replaced by {@link #getHyperlinkTypeValue()}. */ public byte getHyperlinkType() { return getHyperlinkTypeValue().getValue(); } @Override public HyperlinkTypeEnum getHyperlinkTypeValue() { return ((JRTextField) parent).getHyperlinkTypeValue(); } @Override public byte getHyperlinkTarget() { return ((JRTextField) parent).getHyperlinkTarget(); } @Override public String getLinkTarget() { return ((JRTextField) parent).getLinkTarget(); } @Override public JRGroup getEvaluationGroup() { return evaluationGroup; } @Override public JRExpression getExpression() { return ((JRTextField) parent).getExpression(); } @Override public JRExpression getPatternExpression() { return ((JRTextField) parent).getPatternExpression(); } @Override public JRExpression getAnchorNameExpression() { return ((JRTextField) parent).getAnchorNameExpression(); } @Override public JRExpression getHyperlinkReferenceExpression() { return ((JRTextField) parent).getHyperlinkReferenceExpression(); } @Override public JRExpression getHyperlinkWhenExpression() { return ((JRTextField) parent).getHyperlinkWhenExpression(); } @Override public JRExpression getHyperlinkAnchorExpression() { return ((JRTextField) parent).getHyperlinkAnchorExpression(); } @Override public JRExpression getHyperlinkPageExpression() { return ((JRTextField) parent).getHyperlinkPageExpression(); } /** * */ protected Object getValue() { return value; } /** * */ protected String getAnchorName() { return anchorName; } /** * */ protected String getHyperlinkReference() { return hyperlinkReference; } /** * */ protected String getHyperlinkAnchor() { return hyperlinkAnchor; } /** * */ protected Integer getHyperlinkPage() { return hyperlinkPage; } protected String getHyperlinkTooltip() { return hyperlinkTooltip; } /** * */ protected JRTemplateText getJRTemplateText() { return (JRTemplateText) getElementTemplate(); } @Override protected JRTemplateElement createElementTemplate() { JRTemplateText template = new JRTemplateText(getElementOrigin(), filler.getJasperPrint().getDefaultStyleProvider(), this); template.copyParagraph(getPrintParagraph()); template.copyLineBox(getPrintLineBox()); template.setTextFormat(textFormat); return template; } protected void evaluateTextFormat(Format format, Object value, TimeZone ownTimeZone) { if (value != null) // if (getExpression() != null) { if (value instanceof String) { textFormat = null; } else { SimpleTextFormat simpleTextFormat = new SimpleTextFormat(); simpleTextFormat.setValueClassName(value.getClass().getName()); String pattern = getTemplatePattern(format, value); if (pattern != null) { simpleTextFormat.setPattern(pattern); } if (!filler.hasMasterFormatFactory()) { simpleTextFormat.setFormatFactoryClass(filler.getFormatFactory().getClass().getName()); } if (!filler.hasMasterLocale()) { simpleTextFormat.setLocaleCode(JRDataUtils.getLocaleCode(filler.getLocale())); } if (value instanceof java.util.Date) { // the element's format timezone property has precedence over the report timezone TimeZone formatTimeZone = ownTimeZone == null ? filler.getTimeZone() : ownTimeZone; // check if the current format timezone differs from the master report timezone if (!formatTimeZone.equals(filler.fillContext.getMasterTimeZone())) { simpleTextFormat.setTimeZoneId(JRDataUtils.getTimeZoneId(formatTimeZone)); } } textFormat = simpleTextFormat; } } } @Override protected JRTemplateElement getTemplate(JRStyle style) { Pair<JRStyle, TextFormat> key = new Pair<JRStyle, TextFormat>(style, textFormat); return textTemplates.get(key); } @Override protected void registerTemplate(JRStyle style, JRTemplateElement template) { Pair<JRStyle, TextFormat> key = new Pair<JRStyle, TextFormat>(style, textFormat); textTemplates.put(key, template); if (log.isDebugEnabled()) { log.debug("created " + template + " for " + key); } } @Override protected boolean delayedEvaluationUpdatesTemplate() { // since the text format is evaluated during the delayed evaluation, // we need to always attempt to update the template. // we could test whether the value is String, but there might be some exotic // cases in which the same text field is used for both Strings and numbers. return true; } protected TimeZone toFormatTimeZone(String timezoneId) { JRFillDataset dataset = expressionEvaluator.getFillDataset(); // not sure whether the dataset can be null, let's be safe TimeZone reportTimeZone = dataset == null ? filler.getTimeZone() : dataset.timeZone; return JRDataUtils.resolveFormatTimeZone(timezoneId, reportTimeZone); } @Override public void evaluate(byte evaluation) throws JRException { initDelayedEvaluations(); reset(); evaluatePrintWhenExpression(evaluation); if (isPrintWhenExpressionNull() || isPrintWhenTrue()) { if (isEvaluateNow()) { evaluateText(evaluation); } } } /** * */ protected void evaluateText(byte evaluation) throws JRException { evaluateProperties(evaluation); value = evaluateExpression(getExpression(), evaluation); determineOwnTimeZone(); evaluateStyle(evaluation); String strValue = null; pattern = (String) evaluateExpression(getPatternExpression(), evaluation); if (value == null) { if (isBlankWhenNull()) { strValue = ""; } else { strValue = null; } } else { Format format = getFormat(value, ownTimeZone); evaluateTextFormat(format, value, ownTimeZone); if (format == null) { strValue = value.toString(); } else { strValue = format.format(value); if (value instanceof java.util.Date && log.isDebugEnabled()) { log.debug(getUUID() + ": formatted value " + value + " (" + value.getClass().getName() + "/" + ((java.util.Date) value).getTime() + ")" + " to " + strValue); } } } String crtRawText = getRawText(); String newRawText = processMarkupText(String.valueOf(strValue)); setRawText(newRawText); resetTextChunk(); setValueRepeating((crtRawText == null && newRawText == null) || (crtRawText != null && crtRawText.equals(newRawText))); anchorName = (String) evaluateExpression(getAnchorNameExpression(), evaluation); hyperlinkReference = (String) evaluateExpression(getHyperlinkReferenceExpression(), evaluation); hyperlinkWhen = (Boolean) evaluateExpression(getHyperlinkWhenExpression(), evaluation); hyperlinkAnchor = (String) evaluateExpression(getHyperlinkAnchorExpression(), evaluation); hyperlinkPage = (Integer) evaluateExpression(getHyperlinkPageExpression(), evaluation); hyperlinkTooltip = (String) evaluateExpression(getHyperlinkTooltipExpression(), evaluation); hyperlinkParameters = JRFillHyperlinkHelper.evaluateHyperlinkParameters(this, expressionEvaluator, evaluation); } @Override protected TimeZone getTimeZone() { return ownTimeZone == null ? super.getTimeZone() : ownTimeZone; } protected void determineOwnTimeZone() { ownTimeZone = null; if (value instanceof java.util.Date) { // read the element's format timezone property String ownTimezoneId = hasProperties() ? getPropertiesMap().getProperty(PROPERTY_FORMAT_TIMEZONE) : null; ownTimeZone = toFormatTimeZone(ownTimezoneId); if (ownTimeZone == null) { // trying to get a timezone for the specific date/time type. // should we have timezones for arbitrary types a la oracle.sql.DATE? if (value instanceof java.sql.Date) { ownTimeZone = getPatternTimeZone(PROPERTY_SQL_DATE_FORMAT_TIMEZONE); } else if (value instanceof java.sql.Timestamp) { ownTimeZone = getPatternTimeZone(PROPERTY_SQL_TIMESTAMP_FORMAT_TIMEZONE); } else if (value instanceof java.sql.Time) { ownTimeZone = getPatternTimeZone(PROPERTY_SQL_TIME_FORMAT_TIMEZONE); } } if (ownTimeZone == null) { // using a general timezone ownTimeZone = getPatternTimeZone(PROPERTY_FORMAT_TIMEZONE); } } } protected TimeZone getPatternTimeZone(String property) { if (generalPatternTimeZones.containsKey(property)) { return generalPatternTimeZones.get(property); } String propertyVal = filler.propertiesUtil.getProperty(filler.getMainDataset(), property); TimeZone timeZone = toFormatTimeZone(propertyVal); generalPatternTimeZones.put(property, timeZone); if (log.isDebugEnabled()) { log.debug(getUUID() + ": pattern timezone property " + property + " is " + propertyVal + ", resolved to " + timeZone); } return timeZone; } @Override public boolean prepare(int availableHeight, boolean isOverflow) throws JRException { boolean willOverflow = false; super.prepare(availableHeight, isOverflow); if (!isToPrint()) { return willOverflow; } boolean isToPrint = true; boolean isReprinted = false; if (isEvaluateNow()) { if (isOverflow) { if (getPositionTypeValue() == PositionTypeEnum.FIX_RELATIVE_TO_BOTTOM) { // the content of the band bottom text fields is not // consumed during overflows, because they only appear on the last overflow resetTextChunk(); } if (getTextEnd() >= getTextString().length() || !isStretchWithOverflow() || !getRotationValue().equals(RotationEnum.NONE)) { // there is no more text left in the text field to overflow // on the new page, or the text field is not stretchable if (isAlreadyPrinted()) { // the text field has already displayed all its content // on the previous page even if it not stretchable if (isPrintWhenDetailOverflows()) { // the whole content is reprinted resetTextChunk(); isReprinted = true; } else { isToPrint = false; } } // else // { // // the text field did not print on the previous page. // // we let it go since it is its first time anyway // } } // else // { // // there is text left inside the stretchable text field. // // we simply let it go // } if (isToPrint && isPrintWhenExpressionNull() && !isPrintRepeatedValues() && isValueRepeating()) { isToPrint = false; // FIXME, shouldn't we test for the first whole band and the other exceptions to the rule? } } else { if (isPrintWhenExpressionNull() && !isPrintRepeatedValues() && isValueRepeating()) { if ((!isPrintInFirstWholeBand() || !getBand().isFirstWholeOnPageColumn()) && (getPrintWhenGroupChanges() == null || !getBand().isNewGroup(getPrintWhenGroupChanges()))) { isToPrint = false; } } } if (isToPrint) { if (availableHeight >= getRelativeY() + getHeight()) { // the available vertical space is sufficient if (getTextEnd() < getTextString().length() || getTextEnd() == 0) { // there is still some text left in the text field or // the text field is empty if (isStretchWithOverflow() && getRotationValue().equals(RotationEnum.NONE)) { // the text field is allowed to stretch downwards in order to // display all its content chopTextElement(availableHeight - getRelativeY() - getHeight()); if (getTextEnd() < getTextString().length())// - 1) { // even after the current chop operation there is some text left // that will overflow on the next page willOverflow = true; } } else { // the text field is not allowed to stretch downwards in order to // display all its content chopTextElement(0); } } else { // there is no text left in the text field and the text field was not empty // this section is probably unreachable since it is most likely that // the isToPrint flag was already set on false in the code above. isToPrint = false; } } else { // the available vertical space is not sufficient // no matter if there is some text left inside or not, // there was an explicit request to display it, // even if we are on an overflow. // since there is no space available, it will overflow isToPrint = false; willOverflow = true; } } if (isToPrint && isRemoveLineWhenBlank() && //FIXME if the line won't be removed due to other elements getTextString().substring( // present on that line, the background does not appear getTextStart(), getTextEnd()).trim().length() == 0) { isToPrint = false; } } else { if (isOverflow && isAlreadyPrinted()) { if (isPrintWhenDetailOverflows()) { isReprinted = true; } else { isToPrint = false; } } if (isToPrint && availableHeight < getRelativeY() + getHeight()) { isToPrint = false; willOverflow = true; } } setToPrint(isToPrint); setReprinted(isReprinted); return willOverflow; } @Override public JRPrintElement fill() throws JRException { EvaluationTimeEnum evaluationTime = getEvaluationTimeValue(); JRTemplatePrintText text; JRRecordedValuesPrintText recordedValuesText; if (isEvaluateAuto()) { text = recordedValuesText = new JRRecordedValuesPrintText(getJRTemplateText(), printElementOriginator); } else { text = new JRTemplatePrintText(getJRTemplateText(), printElementOriginator); recordedValuesText = null; } text.setUUID(getUUID()); text.setX(getX()); text.setY(getRelativeY()); text.setWidth(getWidth()); // if (getRotation() == ROTATION_NONE) // { //text.setHeight(getPrintElementHeight()); text.setHeight(getStretchHeight()); // } // else // { // text.setHeight(getHeight()); // } text.setRunDirection(getRunDirectionValue()); text.setBookmarkLevel(getBookmarkLevel()); if (isEvaluateNow()) { copy(text); } else if (isEvaluateAuto()) { initDelayedEvaluationPrint(recordedValuesText); } else { filler.addBoundElement(this, text, evaluationTime, getEvaluationGroup(), band); } return text; } /** * */ protected void copy(JRPrintText text) { text.setLineSpacingFactor(getLineSpacingFactor()); text.setLeadingOffset(getLeadingOffset()); text.setTextHeight(getTextHeight()); //FIXME rotation and run direction? //FIXME do we need to do this when the value is String? text.setValue(getValue()); setPrintText(text); text.setAnchorName(getAnchorName()); if (getHyperlinkWhenExpression() == null || Boolean.TRUE.equals(hyperlinkWhen)) { text.setHyperlinkReference(getHyperlinkReference()); text.setHyperlinkAnchor(getHyperlinkAnchor()); text.setHyperlinkPage(getHyperlinkPage()); text.setHyperlinkTooltip(getHyperlinkTooltip()); text.setHyperlinkParameters(hyperlinkParameters); } else { if (text instanceof JRTemplatePrintText)//this is normally the case { ((JRTemplatePrintText) text).setHyperlinkOmitted(true); } text.setHyperlinkReference(null); } transferProperties(text); } @Override protected void setPrintText(JRPrintText printText, String text) { // checking if the text is identical to the one set via setValue. // note that we're assuming that this method is called after printText.setValue(). // JRStyledText no longer creates String copies for simple texts, but keeping this to cover other cases. Object printValue = printText.getValue(); String textObj = text; if (text != null && printValue != null && printValue instanceof String && text.equals(printValue)) { textObj = (String) printValue; } super.setPrintText(printText, textObj); } /** * */ protected Format getFormat(Object value, TimeZone ownTimeZone)//FIXMEFORMAT optimize this with an interface { Format format = null; if (value instanceof java.util.Date) { format = filler.getDateFormat(getDatePattern(value), ownTimeZone); } else if (value instanceof java.lang.Number) { format = filler.getNumberFormat(getNumberPattern(value)); } return format; } /** * */ protected String getTemplatePattern(Format format, Object value)//FIXMEFORMAT optimize this with an interface { String pattern = null; String originalPattern; if (value instanceof java.util.Date) { originalPattern = getDatePattern(value); if (format instanceof SimpleDateFormat) { pattern = ((SimpleDateFormat) format).toPattern(); } } else if (value instanceof Number) { originalPattern = getNumberPattern(value); if (format instanceof DecimalFormat) { pattern = ((DecimalFormat) format).toPattern(); } } else { originalPattern = getPattern(); } if (pattern == null)//fallback to the original pattern { pattern = originalPattern; } return pattern; } @Override public void collectExpressions(JRExpressionCollector collector) { collector.collect(this); } @Override public void visit(JRVisitor visitor) { visitor.visitTextField(this); } @Override protected void resolveElement(JRPrintElement element, byte evaluation) throws JRException { evaluateText(evaluation); chopTextElement(0); copy((JRPrintText) element); //FIXME put this somewhere else, e.g. in ElementEvaluationAction filler.updateBookmark(element); } @Override public int getBookmarkLevel() { return ((JRTextField) parent).getBookmarkLevel(); } @Override public JRFillCloneable createClone(JRFillCloneFactory factory) { return new JRFillTextField(this, factory); } @Override protected void collectDelayedEvaluations() { super.collectDelayedEvaluations(); collectDelayedEvaluations(getExpression()); collectDelayedEvaluations(getPatternExpression()); collectDelayedEvaluations(getAnchorNameExpression()); collectDelayedEvaluations(getHyperlinkReferenceExpression()); collectDelayedEvaluations(getHyperlinkWhenExpression()); collectDelayedEvaluations(getHyperlinkAnchorExpression()); collectDelayedEvaluations(getHyperlinkPageExpression()); } @Override public JRHyperlinkParameter[] getHyperlinkParameters() { return ((JRTextField) parent).getHyperlinkParameters(); } @Override public String getLinkType() { return ((JRTextField) parent).getLinkType(); } @Override public JRExpression getHyperlinkTooltipExpression() { return ((JRTextField) parent).getHyperlinkTooltipExpression(); } @Override protected boolean canOverflow() { return isStretchWithOverflow() && getRotationValue().equals(RotationEnum.NONE) && isEvaluateNow() && filler.isBandOverFlowAllowed(); } }