jfree / jfreechart

A 2D chart library for Java applications (JavaFX, Swing or server-side).
http://www.jfree.org/jfreechart/
GNU Lesser General Public License v2.1
1.18k stars 454 forks source link

Using Thai display locale crashes XYPlot constructor when used with PeriodAxis #62

Open stevemcleod opened 6 years ago

stevemcleod commented 6 years ago

I discovered this unusual bug from user crash reports. Here's the minimum self-contained code sample that will reliably reproduce it.

I'm using Java 1.8.0_131 on Mac with JFreeChart 1.0.19. Problem occurs on Windows, too.


package com.barbarysoftware.pokercopilot;

import org.jfree.chart.axis.PeriodAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;

import java.util.Locale;

public class ScratchSpace {

    public static void main(String[] args) {
        // crash happens when display locale is default (eg en-AU), but format locale is th-TH
        Locale.setDefault(Locale.Category.FORMAT, new Locale.Builder().setLanguageTag("th-TH").build());

        final TimeSeries timeSeries = new TimeSeries("Series 1");
        timeSeries.add(new Day(), 0);
        PeriodAxis domainAxis = new PeriodAxis("domain label");

        // exception occurs here
        XYPlot plot = new XYPlot(new TimeSeriesCollection(timeSeries), domainAxis, null, null);

        System.out.println("plot = " + plot);
    }
}

Exception in thread "main" java.lang.NullPointerException
    at org.jfree.chart.axis.PeriodAxis.setRange(PeriodAxis.java:566)
    at org.jfree.chart.axis.PeriodAxis.autoAdjustRange(PeriodAxis.java:1081)
    at org.jfree.chart.axis.PeriodAxis.configure(PeriodAxis.java:578)
    at org.jfree.chart.axis.Axis.setPlot(Axis.java:1043)
    at org.jfree.chart.plot.XYPlot.<init>(XYPlot.java:668)

I've tracked it down to TimeSeriesCollection. In fact, there is already a FIXME comment in the code that predicted this problem:

    public TimeSeriesCollection(TimeSeries series, TimeZone zone) {
        // FIXME:  need a locale as well as a timezone
        if (zone == null) {
            zone = TimeZone.getDefault();
        }
        this.workingCalendar = Calendar.getInstance(zone);

whereas the PeriodAxis constructor correctly uses the locale to get a calendar instance:

this.calendar = Calendar.getInstance(timeZone, locale);

stevemcleod commented 6 years ago

A workaround is to check at app startup if your user's display locale is not the same as the format locale, AND if the format locale is th-TH. If so, then set the format locale to match the display locale.