highsource / jaxb2-basics

Useful plugins and tools for JAXB2.
BSD 2-Clause "Simplified" License
109 stars 54 forks source link

toString code generated with 0.11.1 cannot run under 0.12.0 #95

Closed zman0900 closed 5 years ago

zman0900 commented 6 years ago

I have a project that generates JAXB code and publishes a maven artifact to be shared by several other projects. While attempting to update one of those downstream projects, this version incompatibility was found.

The common project generates JAXB code from a schema using jaxb2 plugin 0.13.3 and jaxb2-basics 0.11.1, with the "-XtoString" arg enabled. In the downstream project, I have updated to jaxb2 plugin 0.14.0 and jaxb2-basics 0.12.0. This project generates additional JAXB code from another schema which imports the common schema. This is done by including the "episode" of the common project's maven artifact for the jaxb2 plugin.

This leads to an error at runtime since classes generated with the older code from the common module contain this in their toString methods:

final ToStringStrategy2 strategy = JAXBToStringStrategy.INSTANCE;

while classes generated with the newer code contain:

final ToStringStrategy2 strategy = JAXBToStringStrategy.INSTANCE2;

In the 0.12.0 version of the runtime, the INSTANCE variable has type "ToStringStrategy" and INSTANCE2 has type "JAXBToStringStrategy", which implements "ToStringStrategy2". But in the 0.11.1 version, INSTANCE had type "JAXBToStringStrategy".

Can this be fixed, or will I be required to upgrade the version of all projects at once?

highsource commented 6 years ago

Which error are you getting, exactly? Please also post a sample class.

zman0900 commented 6 years ago

Error is java.lang.NoSuchFieldError: INSTANCE when calling the toString method of a generated JAXB class, where the error points to the first line of that generated toString method:

final ToStringStrategy2 strategy = JAXBToStringStrategy.INSTANCE;

Easiest way I have found to reproduce this is to create one maven project which generates any JAXB class(s) from a schema using the older 0.11.1 jaxb2-basics, including the -XtoString arg. Exact setup I used was this:

<plugin>
    <groupId>org.jvnet.jaxb2.maven2</groupId>
    <artifactId>maven-jaxb2-plugin</artifactId>
    <version>0.13.3</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <schemaDirectory>${project.build.outputDirectory}</schemaDirectory>
                <bindingDirectory>src/main/xjb</bindingDirectory>
                <args>
                    <arg>-mark-generated</arg>
                    <arg>-XtoString</arg>
                    <arg>-XsimpleEquals</arg>
                    <arg>-XsimpleHashCode</arg>
                    <arg>-Xsetters</arg>
                    <arg>-Xsetters-mode=direct</arg>
                    <arg>-Xcopyable</arg>
                    <arg>-enableIntrospection</arg>
                </args>
                <plugins>
                    <plugin>
                        <groupId>org.jvnet.jaxb2_commons</groupId>
                        <artifactId>jaxb2-basics</artifactId>
                        <version>0.11.1</version>
                    </plugin>
                </plugins>
            </configuration>
        </execution>
    </executions>
</plugin>

Then, in a second separate project, include the jar produced from the first project as a dependency, along with jaxb2-basics-runtime 0.12.0. Then create a simple class like this:

public class Junk {
    public static void main(String... args) {
        // MyJaxbClass is any of the classes generated from your other project.
        MyJaxbClass x = new MyJaxbClass();
        // This will throw the NoSuchFieldError at runtime.
        System.out.println(x.toString());
    }
}
java.lang.NoSuchFieldError: INSTANCE
    at org.mine.MyJaxbClass.toString(MyJaxbClass.java:nnn)
highsource commented 6 years ago

Could you please prepare a minimal reproducing project?

Am 11.06.2018 um 18:28 schrieb Dan Ziemba notifications@github.com:

Error is java.lang.NoSuchFieldError: INSTANCE when calling the toString method of a generated JAXB class, where the error points to the first line of that generated toString method:

final ToStringStrategy2 strategy = JAXBToStringStrategy.INSTANCE; Easiest way I have found to reproduce this is to create one maven project which generates any JAXB class(s) from a schema using the older 0.11.1 jaxb2-basics, including the -XtoString arg. Exact setup I used was this:

org.jvnet.jaxb2.maven2 maven-jaxb2-plugin 0.13.3 generate ${project.build.outputDirectory} src/main/xjb -mark-generated -XtoString -XsimpleEquals -XsimpleHashCode -Xsetters -Xsetters-mode=direct -Xcopyable -enableIntrospection org.jvnet.jaxb2_commons jaxb2-basics 0.11.1 Then, in a second separate project, include the jar produced from the first project as a dependency, along with jaxb2-basics 0.12.0. Then create a simple class like this: public class Junk { public static void main(String... args) { // MyJaxbClass is any of the classes generated from your other project. MyJaxbClass x = new MyJaxbClass(); // This will throw the NoSuchFieldError at runtime. System.out.println(x.toString()); } } java.lang.NoSuchFieldError: INSTANCE at org.mine.MyJaxbClass.toString(MyJaxbClass.java:nnn) — You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.
zman0900 commented 6 years ago

I'll try to throw something together in the next few days. It appears this also applies to the copyable plugin.

zman0900 commented 6 years ago

I put together an example. This was tested with Java 8.

  1. Checkout zman0900/jaxb2-basics-example-common. This is the "common" module that is built using the previous version of jaxb2-basics.
  2. Run mvn install for the common module so it is available in your local repo.
  3. Checkout zman0900/jaxb2-basics-example-user. This is the "downstream" project that depends on the common module and has been upgraded to the latest version of jaxb2-basics.
  4. Run mvn test for the user module. Two of the tests should print the NoSuchFieldErrors from the toString and clone failures.
java.lang.NoSuchFieldError: INSTANCE
    at com.example.common.Item.toString(Item.java:108)
    at zman0900.DemoTest.commonToStringFails(DemoTest.java:42)
java.lang.NoSuchFieldError: INSTANCE
    at com.example.common.Item.copyTo(Item.java:140)
    at com.example.common.Item.clone(Item.java:136)
    at zman0900.DemoTest.commonCloneFails(DemoTest.java:61)
highsource commented 6 years ago

Thank you, I will take a look the next days.

highsource commented 6 years ago

I've checked the code.

I'm afraid there's not much I can do. Seems you can't combine pre-0.12.0 with post-0.12.0 code.

zman0900 commented 6 years ago

Seems you can't combine pre-0.12.0 with post-0.12.0 code.

Turns out this is not entirely true. I realized I have another dependency on a library that contains JAXB classes generated with a much older version of jaxb2-basics, 0.6.4, with toString enabled. That code actually runs fine under the 0.12.0 runtime.

For anyone else reading this, I have worked around the 0.11 vs 0.12 issue by just disabling the toString and copyable plugins in my library code, then I wrote my own very simple runtime-free toString plugin. This only works since I know none of the users of my library depend on clone/copyTo, and because my schemas are simple enough for my toString plugin (i.e. nothing like List<JAXBElement> is generated).

highsource commented 6 years ago

Careful there.

What I've found out is that combining pre-0.12.0 (0.11.1) and 0.12.0 does not work for some reason I don't really quite understand. I get java.lang.NoSuchFieldError: INSTANCE on access to INSTANCE field of JAXBCopyStrategy (and likes) where the INSTANCE field does exist. I do not know how it can be so, I'll try to ask on StackOverflow. The problem appears when transitioning from 0.12.0-generated code to 0.11.1-generated code. The worst thing is that the problem appears in the runtime.

So unless we figure out what the problem is, it is basically a ticking bomb. From this point, there are only two safe approaches:

I think updating the code to 0.12.0 is absolutely the way to go. No matter how hard we try, there will definitely be breaking changes, from time to time. It is naive trying to stick with old APIs because of the "never touch the running system" sentiment. This will not work in the long run and I, sorry, will not be willing to provide support migrating 0.6.4 to 1.12.8 somewhen in the future.

As for not using runtime-dependent plugins, the primary focus of JAXB2 Basics will always be plugins which generate runtime-dependent code. There are many reasons for it:

I did implement SimpleEquals and SimpleHashCode due to popular demand, but to be honest these are second-class products.

I also started SimpleToString but did not get far with it. It is quite difficult to implement it correctly, with the consideration of JAXBElement or DOM etc.

highsource commented 5 years ago

I'm closing this as WONTFIX as I'm afraid there's not much I can do here.