<i18n dev> DateFormatSymbols triggers this.clone() in the constructor
Yoshito Umaoka
y.umaoka at gmail.com
Fri Jun 5 15:31:25 UTC 2015
Hi folks,
ICU4J implements Java locale service provider interface. I recently
received a problem report from our customer. When they upgraded Java
version to 8, the provider implementation stopped working because of NPE
thrown by our custom DateFormatSymbols subclass. I dug into the problem
and found that DateFormatSymbols caches its own instance in the
constructor. Our subclass implementation expects a field is always
initialized to non-null in the constructor. However, clone() method is
called for the super class's constructor, the field is not yet
initialized at the point.
It looks adding 'null' for the field in clone() method would resolve the
immediate problem, but I'm not comfortable that DateFormatSymbols
implementation cache a premature instance of my own subclass. If any
methods overridden by my own subclass is called on a premature instance,
it might cause another issue.
Anyway, I filed a bug in the Java bug database with the description
below. For meanwhile, adding simple 'null' check in our code would
suffice. But I would like to hear Java i18n team's opinion about this issue.
Thanks,
Yoshito
---------------------------
To implement my own locale service provider, I have a class extending
java.text.DateFormatSymbols. My custom subclass's constructor implicitly
invokes the no-args constructor in java.text.DateFormatSymbols. The
constructor calls a private method - initializeData(Locale).
It looks the implementation was updated in Java 8 and initializeData is
now trying to cache an instance of DateFormatSymbols at the end and
calls this.clone().
In my own subclass implements clone() method, which copies a field
initialized by a constructor in the class. For example,
===============
public class MyDateFormatSymbols extends DateFormatSymbols {
private final Foo foo;
public MyDateFormatSymbols(Foo foo) {
if (foo == null) {
this.foo = new Foo();
} else {
this.foo = foo;
}
}
@Override
public Object clone() {
MyDateFormatSymbols mdfs = (MyDateFormatSymbols)super.clone();
mdfs.foo = this.foo.clone();
}
}
===============
When the constructor MyDateFormatSymbols(Foo) is called, it triggers
no-args constructor of the super class - DateFormatSymbols() first. As I
explained earlier, Java 8 implementation calls this.clone() in
DateFormatSymbpls.initializeData(Locale). At that point, the field foo
in my class is not yet initialized, so this.foo.clone() will throw
NullPointerException.
My own code expects the field 'foo' is always non-null. I could change
clone() to check if this.foo is null or not, but I cannot control cached
'premature' instance held by Java DateFormatSymbols. At this moment, it
looks the cache is only used for copying field values maintained by
DateFormaSymbols itself and never call a method. So, even 'premature'
instance of my own subclass instance is referenced by DateFormatSymbols,
it won't cause any problems. However, if the Java's implementation is
changed to call any DateFormatSymbols method overridden by my own
subclass, it may not work (because my subclass expects the field foo is
non-null).
Such code above did not have any problems with earlier Java releases
(Java 6 / 7).
In my opinion, this.clone() should not be called in DateFormatSymbols
initialization code. Instead, it should create a private container class
for these symbols, and cache the object, not DateFormatSymbols itself.
More information about the i18n-dev
mailing list