1.2.19 Deploying Applications that use J/Link
This section discusses some issues relevant to developers who are creating add-ons for Mathematica that use J/Link.
J/Link uses its own custom class loader that allows it to find classes in a set of locations beyond the startup class path. As described in Section 1.2.1, users can grow this set of extra locations to search for classes by calling the AddToClassPath function. One of the motivations for having a custom class loader was to make it easy for application developers to distribute applications that have parts of their implementation in Java. If you structure your application directory properly, your users will be able to install it simply by copying it into any standard location for Mathematica applications. J/Link will be able to find your Java classes immediately, without users having to perform any classpath-related operations or even restart Java.
If your Mathematica application uses J/Link and includes its own Java components, you should create a Java subdirectory in your application directory. You can place any jar files that your application needs into this Java subdirectory. If you have loose class files (not bundled into a jar file), they should go into an appropriately nested subdirectory of the Java directory. "Appropriately nested" means that if your class is in the Java package com.somecompany.math, then its class file goes into the com/somecompany/math subdirectory of the Java directory. If the class is not in any package, it can go directly into the Java directory. J/Link can also find native libraries and resources your application needs. Native libraries must be in a subdirectory of your Java/Libraries directory that is named after the $SystemID of the platform on which it is installed. Here is an example directory structure for an application that uses J/Link:
... other files and directories used by the application ...
... and so on for other Unix platforms
Your application directory must be placed into one of the standard locations for Mathematica applications. These locations are listed below. In this notation, $TopDirectory/AddOns/Applications means "The AddOns/Applications subdirectory of the directory whose value is given by the Mathematica variable $TopDirectory."
$PreferencesDirectory/AddOns/Applications [Mathematica 4.1 and earlier only]
$UserAddOnsDirectory/Applications [Mathematica 4.2 and later only]
$AddOnsDirectory/Applications [Mathematica 4.2 and later only]
Here are a few tips on producing high-quality applications. These suggestions are guided by mistakes that developers frequently make.
Call InstallJava in the body of a function or functions, not when your package is read in. It is best to avoid side effects during the reading of a package. Users expect reading in a package to be fast and to do nothing but load definitions. If you launch Java at this time, and it fails, it could cause a mysterious hang in the loading process. It is better to call InstallJava in the code of one or more of your functions. You probably do not need to call InstallJava in every single function that uses Java. Most applications have a few "major" functions that users are likely to use almost exclusively, or at least at the start of their session. If your application does not have this property, then provide an initialization function that your users must call first, and call InstallJava inside it.
Call InstallJava with no arguments. You cannot know what options your users need for Java on their systems, so do not override what they may have set up. It is the user's responsibility to make sure that they call SetOptions to customize the options for InstallJava as necessary. Typically this would be done in their init.m file. The exception to this is if your application needs to use the COM-integration facilities of the Microsoft Java runtime. In this case, you would need to specify MicrosoftJava->True in the calls to InstallJava in your code.
Make sure you use JavaBlock and/or ReleaseJavaObject to avoid leaking object references. You cannot know how others will use your code, so you need to be careful to avoid cluttering up their sessions with a potentially large number of useless objects. Sometimes you need to create an object that persists beyond the lifetime of a single Mathematica function, like a viewer window. In such cases, use a MathFrame or MathJFrame as your top-level window and use its onClose() method to specify Mathematica code that releases all outstanding objects and unregisters kernel or front end sharing you may have used. If this is not possible, provide a cleanup function that users can call manually. Use PeekObjects to look at the list of objects referenced in Mathematica before and after your functions run; it should not grow in length.
If you use ShareKernel or ShareFrontEnd, make sure you save the return values from these functions and pass them as arguments to UnshareKernel and UnshareFrontEnd. Do not call UnshareFrontEnd or UnshareKernel with no arguments, as this will shut down sharing even if other applications are using it.
Do not assume that the Java runtime will not be restarted during the lifetime of your application. Although users are strongly discouraged to call UninstallJava or ReinstallJava, it happens. It is unavoidable that some applications will fail if the Java runtime is shut down at an inopportune time (e.g., when they have a Java window displayed), but there are steps you can take to increase the robustness of your application in the face of Java shutdowns and restarts. One step was already given as the first tip above—call InstallJava at the start of your "major" functions. Another step is to avoid caching JavaClass or JavaObject expressions unnecessarily, as these will become invalid if Java restarts. An example of this is calling InstallJava and then LoadJavaClass and JavaNew several times when your package file is read in, and storing the results in private variables for the lifetime of your package. This is problematic if Java is restarted. Never store JavaClass expressions—call LoadJavaClass whenever there is any doubt about whether a class has been loaded into the current Java runtime. Calling LoadJavaClass is very inexpensive if the class has already been loaded. If you have a JavaObject that is very expensive to create and therefore you feel it necessary to cache it over a long period of time in a user's session, consider using the following idiom to test whether it is still valid whenever it is used. The JavaObjectQ test will fail if Java has been shut down or restarted since the object was last created, so you can then restart Java and create and store a new instance of the object.
$myCachedExpensiveJavaObject = JavaNew[...];
... use $myCachedExpensiveJavaObject ...
Do not call UninstallJava or ReinstallJava in your application. You need to coexist politely with other applications that may be using Java. Do not assume that when your package is done with Java, the user is done with it as well. Only users should ever call UninstallJava, and they should probably never call it either. There is no cost to leaving Java running. Likewise, users will rarely call ReinstallJava unless they are doing active Java development and need to reload modified versions of their classes.