net.sf.jasperreports.engine.util.JdkGlyphFixTextMeasurer.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jasperreports.engine.util.JdkGlyphFixTextMeasurer.java

Source

/*
 * 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.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.sf.jasperreports.annotations.properties.Property;
import net.sf.jasperreports.annotations.properties.PropertyScope;
import net.sf.jasperreports.engine.JRCommonText;
import net.sf.jasperreports.engine.JRPropertiesUtil;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.engine.fill.JRMeasuredText;
import net.sf.jasperreports.engine.fill.TextMeasurer;
import net.sf.jasperreports.properties.PropertyConstants;

/**
 * A text measurer implementation that extends
 * {@link TextMeasurer the default text measurer} and adds a workaround for
 * Sun JDK bug <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6367148">6367148</a>/
 * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6611637">6611637</a>.
 * 
 * <p>
 * The workaround consists of simply reattempting the text measuring when 
 * a <code>java.lang.NullPointerException</code> is thrown from
 * <code>sun.font.GlyphLayout</code>. 
 * </p>
 * 
 * @author Lucian Chirita (lucianc@users.sourceforge.net)
 * @see JdkGlyphFixTextMeasurerFactory
 */
public class JdkGlyphFixTextMeasurer extends TextMeasurer {

    private static final Log log = LogFactory.getLog(JdkGlyphFixTextMeasurer.class);

    protected static final String JDK_EXCEPTION_CLASS_PREFIX = "sun.font.GlyphLayout";

    /**
     * The default attempt count.
     */
    public static final int DEFAULT_ATTEMPTS = 20;

    /**
     * The default between attempts sleep time.
     */
    public static final int DEFAULT_ATTEMPT_SLEEP = 0;

    /**
     * A property that specifies the number of times the measurer should attempt
     * to measure a single text element before giving up.
     * 
     * The default value is 20.
     * 
     * @see #DEFAULT_ATTEMPTS
     */
    @Property(category = PropertyConstants.CATEGORY_OTHER, defaultValue = "20", scopes = {
            PropertyScope.CONTEXT }, sinceVersion = PropertyConstants.VERSION_3_0_0, valueType = Integer.class)
    public static final String PROPERTY_ATTEMPTS = JRPropertiesUtil.PROPERTY_PREFIX
            + "jdk.glyph.fix.text.measurer.attempts";

    /**
     * A property that specifies the number of milliseconds to sleep between
     * measuring reattempts.
     * 
     * The default value is 0, which means that the measurer will not pause
     * between reattempts.
     * 
     * @see #DEFAULT_ATTEMPT_SLEEP
     */
    @Property(category = PropertyConstants.CATEGORY_OTHER, defaultValue = "0", scopes = {
            PropertyScope.CONTEXT }, sinceVersion = PropertyConstants.VERSION_3_0_0, valueType = Integer.class)
    public static final String PROPERTY_ATTEMPT_SLEEP = JRPropertiesUtil.PROPERTY_PREFIX
            + "jdk.glyph.fix.text.measurer.sleep";

    /**
     * Whether <code>java.lang.NullPointer</code> exceptions with empty stacktraces
     * should be caught.
     * 
     * This is useful when running on a Sun server JVM (java -server), which might omit
     * exception stacktraces in some cases.
     * 
     * @deprecated replaced by {@link #PROPERTY_CATCH_EMPTY_STACKTRACE_EXCEPTION} which corrects
     * the typo in the property name.
     */
    @Deprecated
    @Property(category = PropertyConstants.CATEGORY_OTHER, defaultValue = PropertyConstants.BOOLEAN_FALSE, scopes = {
            PropertyScope.CONTEXT }, sinceVersion = PropertyConstants.VERSION_3_0_0, valueType = Boolean.class)
    public static final String PROPERTY_CATCH_EMPTY_STACKTRACE = JRPropertiesUtil.PROPERTY_PREFIX
            + "jdk.glyph.fix.text.measurer.catch.empty.stakctrace";

    /**
     * Whether <code>java.lang.NullPointer</code> exceptions with empty stacktraces
     * should be caught.
     * 
     * This is useful when running on a Sun server JVM (java -server), which might omit
     * exception stacktraces in some cases.
     */
    @Property(category = PropertyConstants.CATEGORY_OTHER, defaultValue = PropertyConstants.BOOLEAN_FALSE, scopes = {
            PropertyScope.CONTEXT }, sinceVersion = PropertyConstants.VERSION_6_4_3, valueType = Boolean.class)
    public static final String PROPERTY_CATCH_EMPTY_STACKTRACE_EXCEPTION = JRPropertiesUtil.PROPERTY_PREFIX
            + "jdk.glyph.fix.text.measurer.catch.empty.stacktrace";

    private final int attempts;
    private final int sleep;
    private final boolean catchEmptyStacktrace;

    /**
     * Create a text measurer for a text element.
     * 
     * @param textElement the text element
     */
    public JdkGlyphFixTextMeasurer(JasperReportsContext jasperReportsContext, JRCommonText textElement) {
        super(jasperReportsContext, textElement);

        JRPropertiesUtil properties = JRPropertiesUtil.getInstance(jasperReportsContext);
        attempts = properties.getIntegerProperty(PROPERTY_ATTEMPTS, DEFAULT_ATTEMPTS);
        sleep = properties.getIntegerProperty(PROPERTY_ATTEMPT_SLEEP, DEFAULT_ATTEMPT_SLEEP);

        String emptyStackProp = properties.getProperty(PROPERTY_CATCH_EMPTY_STACKTRACE_EXCEPTION);
        if (emptyStackProp != null) {
            catchEmptyStacktrace = JRPropertiesUtil.asBoolean(emptyStackProp);
        } else {
            //use the old property
            catchEmptyStacktrace = properties.getBooleanProperty(PROPERTY_CATCH_EMPTY_STACKTRACE);
        }
    }

    /**
     * Calls {@link TextMeasurer#measure(JRStyledText, int, int, boolean) super.measure}, catches
     * <code>sun.font.GlyphLayout</code> NPEs and reattempts the call.
     */
    @Override
    public JRMeasuredText measure(JRStyledText styledText, int remainingTextStart, int availableStretchHeight,
            boolean canOverflow) {
        int count = 0;
        do {
            try {
                ++count;

                return super.measure(styledText, remainingTextStart, availableStretchHeight, canOverflow);
            } catch (NullPointerException e) //NOPMD
            {
                if (isJdkGlyphError(e)) {
                    if (count >= attempts) {
                        log.error("JDK Glyph exception caught " + attempts + " times, giving up attempts");
                        throw e;
                    }
                } else {
                    throw e;
                }

                if (log.isDebugEnabled()) {
                    log.debug("Caught JDK Glyph exception " + e + " at attempt #" + count);
                }

                if (sleep > 0) {
                    try {
                        Thread.sleep(sleep);
                    } catch (InterruptedException ie) {
                        throw new JRRuntimeException(ie); //NOPMD
                    }
                }
            }
        } while (true);
    }

    protected boolean isJdkGlyphError(NullPointerException e) {
        StackTraceElement[] stackTrace = e.getStackTrace();
        if (stackTrace.length == 0) {
            if (log.isDebugEnabled()) {
                log.debug("Caught exception with no stacktrace; " + (catchEmptyStacktrace ? "" : "not ")
                        + "treating as JDK Glyph exception");
            }

            return catchEmptyStacktrace;
        }

        StackTraceElement top = stackTrace[0];
        return top.getClassName().startsWith(JDK_EXCEPTION_CLASS_PREFIX);
    }

}