MapTool - Development - Java 6 vs Java 7 coding

Progress reports and musings from the developers on the current gaming tools.

Moderators: dorpond, trevor, Azhrei

username
Dragon
Posts: 277
Joined: Sun Sep 04, 2011 7:01 am

Re: MapTool - Development - Java 6 vs Java 7 coding

Post by username »

Well. I don't have time to check this, but it used to work this way with Java 1.5 and 1.4, so I wonder why it should change with 6 and 7:

if you can get the compiler (j6 or j7) to create j6 code, it will run a priori on a jvm6. If you make use of classes that are available only in jre7 (or libs that are only delivered in j7 versions) than you will get "funny" errors during runtime. If you can't compile your code to j6, than extract the j7 code into a separate project, which you compile j7 style. Call an inconspicuous method in the other project so it can still compile to j6. (That method may use j7 behind the scenes.) After that you can package all classes into one jar.

Code: Select all

class J6 {
 static public void main(String[] argv) {
  if (version > 1.6) J7.inconspicuous();
 }
}

// -- different project

class J7 {
 public static void inconspicuous() {
  dubious();
 }
 private static void dubious() {
  // fancy J7 code
 }
}
If eclipse won't let you compile J6 in j6, then implement dubious() as empty in that compiler run, keep the classes and then make a j7 compiler run with J7 and implemented dubious(). Then combine the classes in one jar (or classpath). I'd be very suprised if that wouldn't run in a jvm6. (An important thing to keep in mind is that you need to avoid class constructors of j7 classes. They are sometimes called in unexpected places.)

User avatar
Lord.Ashes
Dragon
Posts: 350
Joined: Wed Jul 03, 2013 5:58 am

Re: MapTool - Development - Java 6 vs Java 7 coding

Post by Lord.Ashes »

username wrote:Well. I don't have time to check this, but it used to work this way with Java 1.5 and 1.4, so I wonder why it should change with 6 and 7:
Well once possibility is the coder (i.e. me). I am still a bit of a noob when it comes to Java and Eclipse. I did a bit of Java programming way back (many, many years ago) and now, with MapTool, I have come back to it but I typically code in other languages. All of the stuff that I did previously, in Java, was all by-the-book stuff...I never attempted anything like this (i.e. trying to implement different code for different versions of Java) before and thus it is possible that I am doing it wrong <grin>.
username wrote:if you can get the compiler (j6 or j7) to create j6 code, it will run a priori on a jvm6.
As far as I can tell, I can't use a J6 compiler because it "spots" the J7 code and refuses to compile (at least when done through Eclipse). I have not yet tried to use Eclipse to compile using J6 on a machine that has J7. It seems that Eclipse has an option for this although I am not sure if that is just the playback JRE or if it applies equally to compiling. A note in Eclipse says that I need to have a J6 JRE installed in order to use this option...I would have thought that this would be a J6 JDK requirement if the option applied equally to compiling.
username wrote:If you make use of classes that are available only in jre7 (or libs that are only delivered in j7 versions) than you will get "funny" errors during runtime.
Yes. I expected that. However the runtime errors seem to actually prevent running the code (it claims it can not find "main") instead of running the J6 portion of code and then throwing a catchable error when it gets to the J7 code (which could then be used to execute a J6 alternative to the J7 code). At least this is the results that I seem to be getting.
username wrote:If you can't compile your code to j6, than extract the j7 code into a separate project, which you compile j7 style. Call an inconspicuous method in the other project so it can still compile to j6. (That method may use j7 behind the scenes.) After that you can package all classes into one jar.
I see what you are saying but here comes the Noob question...So in the example code you gave, my separate J7 project would be called "J7" (held in a J7.jar file) and would contain a public method called, in your example, "inconspicuous". Is that correct? (i.e. to make a call to the method in the external separate J7 project, I just use the project name dot method).
(Please have patience with me <grin> and don't yell at me that this is Java basics...I confess that I am a noob when it comes to Java. I know that usually one uses the syntax of class.method such as MapTool.JAVA_VERSION but I am not familiar with how that works when calling classes in a separate project)
username wrote:

Code: Select all

class J6 {
 static public void main(String[] argv) {
  if (version > 1.6) J7.inconspicuous();
 }
}

// -- different project

class J7 {
 public static void inconspicuous() {
  dubious();
 }
 private static void dubious() {
  // fancy J7 code
 }
}
I am not sure I fully understand the reason behind the seeming one level of abstraction (i.e. why inconspicuous needs to call dubious instead of containing the J7 code directly). Is this to make the inconspicuous method J6 compliant and thus prevent the compiler from "catching-on" that the body is actually J7 code?
username wrote: If eclipse won't let you compile J6 in j6, then implement dubious() as empty in that compiler run, keep the classes and then make a j7 compiler run with J7 and implemented dubious().
Then combine the classes in one jar (or classpath)
Are you suggesting to create an empty dubious method when compiling the J6 version and then create a J7 version of dubious with my actual desired code...and then merge the results to get the J6 base with the J7 code? I'll give that a try if I can't get Eclipse to compile it and if the more simple straight forward approach (that you mentioned first) does not work.

Thanks.

Edit: Fixed start and end of Quotes in post
"We often compare ourselves to the U.S. and often they come out the best,
but they only have the right to bare arms, while we have the right to bare breasts"
The Right To Bare Breasts by Bowser & Blue

User avatar
Lord.Ashes
Dragon
Posts: 350
Joined: Wed Jul 03, 2013 5:58 am

Re: MapTool - Development - Java 6 vs Java 7 coding

Post by Lord.Ashes »

I did the following prototype test to investigate this situation:

1. I created a class called J7 which has a public method called entryPoint and a private method called process.
The method entryPoint just calls process. The method process does some J7 stuff (in this uses java.nio to reads lastAccessTime on some files)

2. I compiled the above J7 code into a J7.jar file using a Java 7 machine

3. I created an application called J6 which uses the public main method. In the main method I used URLClassLoader to load the J7 jar file, then I use Class and Method to get at the desired class and method inside the JAR file.

4. I compiled the J6 application using a Java 6 machine

(Note: I actually used two different machines to compile the J7 and J6 code but I am sure you could do it from the same machine if you have both a J6 and J7 version installed)

When I tried to compile the J6 application it complained about not providing a exception for Malformed URL, so I wrapped that portion of code in a Try/Catch.

This managed to compile but did not work as expected. It ran fine on the J7 machine but it still threw the Unsupported Java Version error (or whatever it was called) on the J6 machine.

However, unlike my previous attempts with MapTools where the application did not seem to run at all, this time the application ran (on the J6 machine) up to the point where I was trying to load the J7 code. It was erroring out at that point even though the section of code was enclosed in a try/catch with a generic Exception catch. However, this result indicated that if the code detects the java version and prevents a J6 client from actually trying to load the J7 code (as opposed to trying to determine that via the try/catch) then the desired results can be achieved.

To prove it, I added some code that checked the Java version and only ran the J7 class loading if the Java Version was 1.7 or higher. Otherwise the logic dropped out to the else part which, in my case, just identified that the J6 alternate was supposed to be used.

The code worked great. On the J7 machine it ran the J7 code and on the J6 machine the logic ensured that it did not try to run the J7 code and thus it also ran without errors.

BTW, it seems that a J6 client will trigger the class load error (above) regardless if the section of code, that is invoked in the called class, contains any actual J7 code. This leads me to believe that as soon as the class has any J7 imports (such as java.nio) the class will cause a failure on load when run on a J6 machine. This means, as far as I can see, that you can't create a J6 and J7 entry point in the same jar file and run the appropriate entry point because the J6 machine will error out even when running a J6 entry point in the called class (if that same class contains any J7 code).

So now I can implement the J7 Cache Cleaner as a pre-compiled Jar file library and use the Java version inside MapTools to determine if the J7 class in the library should be used or the J6 class compiled as part of my MapTools modifications.


Edit: Added J6 sample code for those that are interested. In the sample code I actually placed both the J6 and J7 code in external JAR files but each in a separate JAR file so that the J6 client never runs a J7 compiled JAR file
J6.java.txt
(2.23 KiB) Downloaded 148 times
(To use remove the ".txt" extension...I had to add it so that the forum would accept it as an attachment)
"We often compare ourselves to the U.S. and often they come out the best,
but they only have the right to bare arms, while we have the right to bare breasts"
The Right To Bare Breasts by Bowser & Blue

username
Dragon
Posts: 277
Joined: Sun Sep 04, 2011 7:01 am

Re: MapTool - Development - Java 6 vs Java 7 coding

Post by username »

Can you provide the stack trace of the class version error? A class that isn't used does not have it's (class) constructor run.

User avatar
Lord.Ashes
Dragon
Posts: 350
Joined: Wed Jul 03, 2013 5:58 am

Re: MapTool - Development - Java 6 vs Java 7 coding

Post by Lord.Ashes »

username wrote:Can you provide the stack trace of the class version error? A class that isn't used does not have it's (class) constructor run.
The error was provided in a previous post in this same thread. From observation (but with no real theory behind it), it seems that if a project is compiled on J7 then it seems to generate the error immediately when running on J6. Regardless if the execution would actually run any J7 only code. This error does not seem to be catchable.

If the J7 only portion is compiled in a separate JAR file and then loaded from the J6 code using Class/Method/Invoke then it seems to run the J6 code to the point where it tries to load the J7 class from the external JAR file and then generates the same error. Again, this error does not appear catchable.

However, this means that if you do a Java Version check, to avoid calling the J7 code on a J7 machine, you can get a program to execute either J6 or J7 code based on the Java version available on the machine.

These findings are based on a Win8 Java 7 machine and a Ubuntu Java 6 Virtual Machine. The above results could be specific to the machines I am using and not necessarily true for all machines.

From the results, I am assuming that as soon as a J6 machine is asked to load a J7 class it will error out. This is why I was not able to put the J6 and J7 code into the same class, suing different methods, because (it seems) that the J6 machine detected J7 code in the class somewhere (even if it would not actually run it) and errored out.

Edit: Added below understanding

Here is my understanding of what is happening...it has been a log time since I had studied things like what exactly happens during a compile, so if my information is wrong please feel free to correct me.

When a class is part of a application package (i.e. is not a referenced class in an external JAR file) it is compiled by the compiler along with the rest of the application package. Since, at compile time, this class may or may not be used (depending on the conditions of runtime) the class must be added in any case. If the class is added as an external reference to a class is a separate JAR file then the compiler does not need to compile it when compiling the application package because the external JAR file is already compiled.

If the class includes any J7 code, it needs a J7 compiler because a J6 (or earlier) compiler will not be able to properly compile the J7 only code. However, if the class is compiled using J7 it seems that the compiler version is somehow added to the compile or the runtime has some way to detect which version of Java the class requires. It seems that the implementation of this version check is independent of the actual code run in the class. Thus even if the runtime invokes a method in the class that contains purely J6 code, if the class contains any J7 code elsewhere (or may just the fact that it was compiled using J7) renders the class incompatible with a J6 runtime.

This explains why, when the entire application package is compiled using J7, the application package will not run at all on a J6 machine...because the main class itself is J7. This also explains why, when the J7 class is called from an external JAR file while the calling application is J6 compiled, the application runs until the J7 class call. The runtime checks the compile version of the application, which is J6, and allows it to run on a J6 machine. When it tries to load the external JAR file class, which is J7, it fails the version check and generates the error.

This also would explain why the external JAR file can not contain both the J6 and J7 class. Since the external JAR file would need to be compiled using a J7 compiler (to support the J7 code), when the J6 application package class the external JAR file class it will see that it has been compiled using J7 (or contains J7 code) and thus will refuse to run it (giving the error) even if the method being invoked may not actually use any J7 code.

This all seems to make sense to me...the only odd behavior is that the error (when trying to load the J7 class) is not catchable. Correct me if I am wrong but if a try/catch structure specifies the generic Exception (as opposed to a specific Exceoption such as IOException) then it should catch all exceptions, no? In such a case using the try/catch structure around the class call should allow recovery when the class load fails. This does not seem to be happening. As a result the user needs to use something like a Java Version check to determine if the J7 class call is to be made or not (as opposed to using the try/catch structure to make that decision).
"We often compare ourselves to the U.S. and often they come out the best,
but they only have the right to bare arms, while we have the right to bare breasts"
The Right To Bare Breasts by Bowser & Blue

username
Dragon
Posts: 277
Joined: Sun Sep 04, 2011 7:01 am

Re: MapTool - Development - Java 6 vs Java 7 coding

Post by username »

Every class file contains a version in its header. 51 means it was compiled for a j7 runtime, usually by a j7 compiler. The same situation is possible with j6 and j5, etc. You can get a j7 compiler to generate j6 code, btw. Whenever the classloader needs to load a class, it checks the version to see if it can handle it. A j6 loader cannot handle j7 format, so it complains.

You can catch errors in a similar manner as exceptions. The occassions where you can't catch an error are rare, and this is not one of them. Use Throwable or Error instead of Exception in your catch clause.

The interesting part is why your code loads a j7 class, when it is not needed. If your stack from before is still the "valid" one, then you have another class in your setup called "LaunchInstructions", which is compiled with j7. That must be remedied. This problem is common, in the sense that you fix the problem in one spot but overlook it in another.

There's nothing mystical about jars and classes. The only "mystical" thing is that the java compiler will compile everything it can get its hands on (but jar will not package that way) and eclipse concurs. So once you separate the things far enough (like in a different project) it should work the way described. Using different jar files is overkill (but if it works and can still be handled, okay, I guess that counts).

User avatar
Lord.Ashes
Dragon
Posts: 350
Joined: Wed Jul 03, 2013 5:58 am

Re: MapTool - Development - Java 6 vs Java 7 coding

Post by Lord.Ashes »

username wrote:Every class file contains a version in its header. 51 means it was compiled for a j7 runtime, usually by a j7 compiler. The same situation is possible with j6 and j5, etc. You can get a j7 compiler to generate j6 code, btw. Whenever the classloader needs to load a class, it checks the version to see if it can handle it. A j6 loader cannot handle j7 format, so it complains.
That is what I figured. That it is just a check against the version of Java that was used to compile it.
username wrote:You can catch errors in a similar manner as exceptions. The occassions where you can't catch an error are rare, and this is not one of them. Use Throwable or Error instead of Exception in your catch clause.
Thanks for that information. I will give that a try. I would still like to make the logic based on a try/catch type structure as opposed to reading the Java version just in case there is either a problem loading the class (even though the correct version of Java is present) or for cases where a Java implementation may use non-standard versioning.
username wrote:The interesting part is why your code loads a j7 class, when it is not needed.
I have written a MapTool modification (CacheCleaner) which uses java.nio (which is only available in J7) to obtain the LastAccessTime of files. However, I want my code to be usable by users with J6 so I wrote some fallback code which uses java.io to obtain LastModifiedTime (which is available in J6) to provide similar functionality. The J7 version that uses LastAccessTime is more ideal because it will delete assets from the cache based on when the file was last accessed (i.e. a file could have been copied to the machine a long time ago and thus would have a very old LastModifiedTime but if it is still used it would have a up-to-date LastAccessTime) as opposed to when it was last modified but the J6 fallback still works well enough to include it for J6 users. In worst case the J6 fallback might delete some asset that is still used frequently and, on its subsequent use, it will have to be reloaded.
username wrote:If your stack from before is still the "valid" one, then you have another class in your setup called "LaunchInstructions", which is compiled with j7. That must be remedied. This problem is common, in the sense that you fix the problem in one spot but overlook it in another.
I don't think so because I will be compiling the whole MapTool package using J6 to ensure that it can be used by J6 runtimes. Only the J7 code, which will be compiled on its own into an external independent JAR file, will be compiled using J7. The J6 code will use Java Version (or try/catch) to ensure that if the MapTool package is not running on a J7 machine, it will not call the external J7 class (it will use the J6 fallback code instead). So as long as the MapTool package is compiled in J6, there should not be any further issues as far as I can tell.
username wrote:There's nothing mystical about jars and classes. The only "mystical" thing is that the java compiler will compile everything it can get its hands on (but jar will not package that way) and eclipse concurs. So once you separate the things far enough (like in a different project) it should work the way described. Using different jar files is overkill (but if it works and can still be handled, okay, I guess that counts).
I figured that there was probably some was to make the J7 code just a different project as opposed to a separate JAR file which would probably achieve the same thing...however I was not sure exactly how to do that and just how much separation was needed. As far as I can tell the key is that the J6 and J7 code can not be compiled at the same time because on needs to be J6 compiled (to allow J6 runtimes to run it) and the other needs to be J7 compiled so that it can use J7 code. I also figured that this will clearly keep the J6 and J7 code separated. As far as I can tell, MapTool already uses some library JAR files (found in the lib sub-directory after "install") so adding an external JAR for the CacheCleaner code isn't too big of a departure from what MapTool is already doing.

Now I just need to put the above into practice...So far I just prototyped it using some sample J6 and J7 code but have not yet implemented it with MapTool and my CacheCleaner code.

Thanks for your advice.
"We often compare ourselves to the U.S. and often they come out the best,
but they only have the right to bare arms, while we have the right to bare breasts"
The Right To Bare Breasts by Bowser & Blue

Post Reply

Return to “Developer Notes”