Modular Java SE
From APIDesign
I like puzzles that tease my mind (a bit). Last week I've been introduced to one. Modularize JDK (a described at Mark Reinhold's blog). This page will capture my thoughts on this topic.
There can be many reasons for having modular JDK, but to simplify things, let's stick with one: we want to reach a point in future, when it will be enough to download just a limited set of JDK to execute an applet/application/server, etc.
False Expectations
Sometimes people expect to get better performance just by modularizing their application. This is probably a false expectation, at least in the initial step.
By trivially splitting a JAR into ten smaller ones, one can only increase the amount of work done by the system. Instead of opening just one JAR and reading list of its entries, one needs to do this operation ten times, and this is obviously slower, especially when operating system caches are empty (e.g. after boot). Also the mutual communication between the newly separated pieces of the application will require some some overhead. Not much, but certainly something.
I have faced this when I split the monolithic openide.jar - a JAR with majority of NetBeans APIs back in 2005 (see the project page for more details). When I divided the big JAR into fifteen smaller, the start time of NetBeans IDE increased by 5%. I was seeking for reasons of such slowdown for the whole release and managed to eliminate it somehow, but the proper fix was still waiting to be discovered.
These days the NetBeans IDE starts faster then it used to before its modularization - we improved the infrastructure and made it more module friendly. We created various caches (for content of META-INF/MANIFEST.MF files of all modules, for META-INF/services, for classes loaded during start, for layout of files on disk, NetBeansLayers, etc.) and these days (NetBeans IDE 6.5, or 6.7) we don't open the modularized JARs at all. Thus we have the deployment benefits as claimed in manifesto of modular programming, while during runtime the system behaves like a monolithic application.
Modularization really pays off (we can easily deprecate obsoleted modules and make the system smaller), but it may take a little while. If you are seeking immediate improvements in terms of ms spend while loading a Hello World! application, you'd better refactor your code and classes. Modularization is not going to help you. Modularization is for those who seek long term improvements in deployment, ability to deprecate and more rapidly evolve the framework.
Infrastructure
Motto: I don't like to do useless work. - As a result I always seek for some test that will ensure my work really leads somewhere (like the guards described in section Path of a lost warrior in Chapter 11).
What is the foremost check to ensure your code is split into pieces? Well, each piece needs to compile separately. Thus, before modularization, I tweak the build infrastructure to make sure it really compiles the pieces and not everything at once.
To do this, you very likely don't want to mangle with location of your sources in your version control system. This would be premature, as the final division of the units is not yet know and your version history would be full of useless moves and renames. Luckily Ant offers powerful enough way to define set of files and feed it into the compiler.
The following part of build.xml defines there groups of sources: applet, beans and the rest - called base.
<!-- this is the core of the separation - definition of what classes belong into what compilation group. --> <selector id="applet"> <or> <filename name="java/beans/AppletInitializer*"/> <filename name="java/applet/**"/> <filename name="sun/applet/**"/> <filename name="META-INF/services/sun.beans.AppletProxy"/> </or> </selector> <selector id="beans"> <and> <or> <filename name="java/beans/**"/> <filename name="sun/beans/**"/> <filename name="com/sun/beans/**"/> </or> <none> <selector refid="applet"/> </none> </and> </selector> <selector id="base"> <none> <selector refid="applet"/> <selector refid="beans"/> </none> </selector>
Please note that the selectors are referring to each other. The beans group explicitly says it wants nothing from the applet group and the base group is solitelly defined as everything not included in the previous groups.
Then you need to start Java compiler on each of this group. An important step is to disable the search functionality of javac. By default the compiler looks for additional classes in the sourcepath and loads them as necessary. This needs to be prevented, as that might accidentally load classes from some other group of sources. To do this use the sourcepath="" parameter:
<javac bootclasspath="${build.dir}/base.jar" sourcepath="" destdir="${build.dir}/classes/${module}" classpath="${module.cp}" includejavaruntime="false" includeantruntime="false" > <src refid="src.path"/> <selector refid="${module}"/> </javac>
With infrastructure like this one, you can start splitting your project apart.
java.applet and java.beans
The biggest obstacle preventing creation of limited parts of JDK that really work is to define such limited pieces, make them independent and yet keep binary compatibility for the whole Java SE. Let's look at one such problem and seek a solution.
Obviously you may be interested in using JavaBeans specification and you may not want to know anything about applets. Also you may want to use applets and don't care about introspection and BeanInfos provided by JavaBeans. Is this possible?
Well, there is a problem. The java.beans.AppletInitializer interface. It resides in beans, yet its signature contains java.applet.Applet. This means that the java.beans package has compile time dependency on java.applet. Does that mean whoever uses JavaBeans module in future, needs to install applet module as well?
No. I have a solution: Let's use Code Injection! Let's change Beans code to not talk directly to Applet, but rather create a code slot that can be injected by the applet module. Here is the diff against openjdk repository:
diff -r f72c0dc047b9 -r 57914fd9382f src/share/classes/META-INF/services/sun.beans.AppletProxy --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/META-INF/services/sun.beans.AppletProxy Tue Jun 16 17:53:32 2009 +0200 @@ -0,0 +1,1 @@ +sun.applet.BeansBridge diff -r f72c0dc047b9 -r 57914fd9382f src/share/classes/java/beans/Beans.java --- a/src/share/classes/java/beans/Beans.java Thu Jun 11 10:54:22 2009 -0700 +++ b/src/share/classes/java/beans/Beans.java Tue Jun 16 17:53:32 2009 +0200 @@ -27,13 +27,7 @@ import com.sun.beans.finder.ClassFinder; -import java.applet.Applet; -import java.applet.AppletContext; -import java.applet.AppletStub; -import java.applet.AudioClip; - import java.awt.GraphicsEnvironment; -import java.awt.Image; import java.beans.beancontext.BeanContext; @@ -43,17 +37,15 @@ import java.io.ObjectStreamClass; import java.io.StreamCorruptedException; -import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; -import java.util.Enumeration; -import java.util.Hashtable; import java.util.Iterator; -import java.util.Vector; +import java.util.ServiceLoader; import sun.awt.AppContext; +import sun.beans.AppletProxy; /** * This class provides some general purpose beans control methods. @@ -155,8 +147,14 @@ * @exception IOException if an I/O error occurs. */ - public static Object instantiate(ClassLoader cls, String beanName, BeanContext beanContext, AppletInitializer initializer) - throws IOException, ClassNotFoundException { + public static Object instantiate(ClassLoader cls, String beanName, BeanContext beanContext, + /** TBD: Oops , this is bad. The AppletInitializer is used from + * public API. I have not noticed that sooner. Opps. + AppletInitializer initializer + * meanwhile turning into object, but this cannot be final solution: + */ + Object initializer + ) throws IOException, ClassNotFoundException { InputStream ins; ObjectInputStream oins = null; @@ -231,7 +229,7 @@ * Try to instantiate the class. */ - try { + try { result = cl.newInstance(); } catch (Exception ex) { // We have to remap the exception to one in our signature. @@ -243,108 +241,13 @@ if (result != null) { // Ok, if the result is an applet initialize it. - - AppletStub stub = null; - - if (result instanceof Applet) { - Applet applet = (Applet) result; - boolean needDummies = initializer == null; - - if (needDummies) { - - // Figure our the codebase and docbase URLs. We do this - // by locating the URL for a known resource, and then - // massaging the URL. - - // First find the "resource name" corresponding to the bean - // itself. So a serialzied bean "a.b.c" would imply a - // resource name of "a/b/c.ser" and a classname of "x.y" - // would imply a resource name of "x/y.class". - - final String resourceName; - - if (serialized) { - // Serialized bean - resourceName = beanName.replace('.','/').concat(".ser"); - } else { - // Regular class - resourceName = beanName.replace('.','/').concat(".class"); - } - - URL objectUrl = null; - URL codeBase = null; - URL docBase = null; - - // Now get the URL correponding to the resource name. - - final ClassLoader cloader = cls; - objectUrl = (URL) - AccessController.doPrivileged - (new PrivilegedAction() { - public Object run() { - if (cloader == null) - return ClassLoader.getSystemResource - (resourceName); - else - return cloader.getResource(resourceName); - } - }); - - // If we found a URL, we try to locate the docbase by taking - // of the final path name component, and the code base by taking - // of the complete resourceName. - // So if we had a resourceName of "a/b/c.class" and we got an - // objectURL of "file://bert/classes/a/b/c.class" then we would - // want to set the codebase to "file://bert/classes/" and the - // docbase to "file://bert/classes/a/b/" - - if (objectUrl != null) { - String s = objectUrl.toExternalForm(); - - if (s.endsWith(resourceName)) { - int ix = s.length() - resourceName.length(); - codeBase = new URL(s.substring(0,ix)); - docBase = codeBase; - - ix = s.lastIndexOf('/'); - - if (ix >= 0) { - docBase = new URL(s.substring(0,ix+1)); - } - } - } - - // Setup a default context and stub. - BeansAppletContext context = new BeansAppletContext(applet); - - stub = (AppletStub)new BeansAppletStub(applet, context, codeBase, docBase); - applet.setStub(stub); - } else { - initializer.initialize(applet, beanContext); - } - - // now, if there is a BeanContext, add the bean, if applicable. - - if (beanContext != null) { - beanContext.add(result); - } - - // If it was deserialized then it was already init-ed. - // Otherwise we need to initialize it. - - if (!serialized) { - // We need to set a reasonable initial size, as many - // applets are unhappy if they are started without - // having been explicitly sized. - applet.setSize(100,100); - applet.init(); - } - - if (needDummies) { - ((BeansAppletStub)stub).active = true; - } else initializer.activate(applet); - - } else if (beanContext != null) beanContext.add(result); + Iterator<AppletProxy> it = ServiceLoader.load(AppletProxy.class).iterator(); + AppletProxy ap = it.hasNext() ? it.next() : null; + if (ap != null || !ap.initialize( + result, initializer, serialized, beanName, beanContext, cls + )) { + if (beanContext != null) beanContext.add(result); + } } return result; @@ -504,134 +407,3 @@ } } -/** - * Package private support class. This provides a default AppletContext - * for beans which are applets. - */ - -class BeansAppletContext implements AppletContext { - Applet target; - Hashtable imageCache = new Hashtable(); - - BeansAppletContext(Applet target) { - this.target = target; - } - - public AudioClip getAudioClip(URL url) { - // We don't currently support audio clips in the Beans.instantiate - // applet context, unless by some luck there exists a URL content - // class that can generate an AudioClip from the audio URL. - try { - return (AudioClip) url.getContent(); - } catch (Exception ex) { - return null; - } - } - - public synchronized Image getImage(URL url) { - Object o = imageCache.get(url); - if (o != null) { - return (Image)o; - } - try { - o = url.getContent(); - if (o == null) { - return null; - } - if (o instanceof Image) { - imageCache.put(url, o); - return (Image) o; - } - // Otherwise it must be an ImageProducer. - Image img = target.createImage((java.awt.image.ImageProducer)o); - imageCache.put(url, img); - return img; - - } catch (Exception ex) { - return null; - } - } - - public Applet getApplet(String name) { - return null; - } - - public Enumeration getApplets() { - Vector applets = new Vector(); - applets.addElement(target); - return applets.elements(); - } - - public void showDocument(URL url) { - // We do nothing. - } - - public void showDocument(URL url, String target) { - // We do nothing. - } - - public void showStatus(String status) { - // We do nothing. - } - - public void setStream(String key, InputStream stream)throws IOException{ - // We do nothing. - } - - public InputStream getStream(String key){ - // We do nothing. - return null; - } - - public Iterator getStreamKeys(){ - // We do nothing. - return null; - } -} - -/** - * Package private support class. This provides an AppletStub - * for beans which are applets. - */ -class BeansAppletStub implements AppletStub { - transient boolean active; - transient Applet target; - transient AppletContext context; - transient URL codeBase; - transient URL docBase; - - BeansAppletStub(Applet target, - AppletContext context, URL codeBase, - URL docBase) { - this.target = target; - this.context = context; - this.codeBase = codeBase; - this.docBase = docBase; - } - - public boolean isActive() { - return active; - } - - public URL getDocumentBase() { - // use the root directory of the applet's class-loader - return docBase; - } - - public URL getCodeBase() { - // use the directory where we found the class or serialized object. - return codeBase; - } - - public String getParameter(String name) { - return null; - } - - public AppletContext getAppletContext() { - return context; - } - - public void appletResize(int width, int height) { - // we do nothing. - } -} diff -r f72c0dc047b9 -r 57914fd9382f src/share/classes/java/beans/beancontext/BeanContextSupport.java --- a/src/share/classes/java/beans/beancontext/BeanContextSupport.java Thu Jun 11 10:54:22 2009 -0700 +++ b/src/share/classes/java/beans/beancontext/BeanContextSupport.java Tue Jun 16 17:53:32 2009 +0200 @@ -29,16 +29,12 @@ import java.awt.Container; import java.beans.Beans; -import java.beans.AppletInitializer; -import java.beans.DesignMode; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeSupport; import java.beans.VetoableChangeListener; -import java.beans.VetoableChangeSupport; import java.beans.PropertyVetoException; import java.beans.Visibility; diff -r f72c0dc047b9 -r 57914fd9382f src/share/classes/sun/applet/BeansBridge.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/sun/applet/BeansBridge.java Tue Jun 16 17:53:32 2009 +0200 @@ -0,0 +1,298 @@ +/* + * Copyright 1996-2009 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.applet; + +import java.applet.Applet; +import java.applet.AppletContext; +import java.applet.AppletStub; +import java.applet.AudioClip; +import java.awt.Image; +import java.beans.AppletInitializer; +import java.beans.beancontext.BeanContext; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Vector; +import sun.beans.AppletProxy; + +/** + * Implementaiton of the bridge between + * java.beans module and java.applet module. + * + * @author Jaroslav Tulach + */ + +public class BeansBridge extends AppletProxy { + public boolean initialize( + Object result, Object init, boolean serialized, + String beanName, BeanContext beanContext, ClassLoader cls + ) throws MalformedURLException { + if (!(result instanceof Applet)) { + return false; + } + AppletInitializer initializer = (AppletInitializer)init; + + AppletStub stub = null; + Applet applet = (Applet) result; + boolean needDummies = initializer == null; + + if (needDummies) { + + // Figure our the codebase and docbase URLs. We do this + // by locating the URL for a known resource, and then + // massaging the URL. + + // First find the "resource name" corresponding to the bean + // itself. So a serialzied bean "a.b.c" would imply a + // resource name of "a/b/c.ser" and a classname of "x.y" + // would imply a resource name of "x/y.class". + + final String resourceName; + + if (serialized) { + // Serialized bean + resourceName = beanName.replace('.', '/').concat(".ser"); + } else { + // Regular class + resourceName = beanName.replace('.', '/').concat(".class"); + } + + URL objectUrl = null; + URL codeBase = null; + URL docBase = null; + + // Now get the URL correponding to the resource name. + + final ClassLoader cloader = cls; + objectUrl = (URL) AccessController.doPrivileged(new PrivilegedAction() { + + public Object run() { + if (cloader == null) { + return ClassLoader.getSystemResource(resourceName); + } else { + return cloader.getResource(resourceName); + } + } + }); + + // If we found a URL, we try to locate the docbase by taking + // of the final path name component, and the code base by taking + // of the complete resourceName. + // So if we had a resourceName of "a/b/c.class" and we got an + // objectURL of "file://bert/classes/a/b/c.class" then we would + // want to set the codebase to "file://bert/classes/" and the + // docbase to "file://bert/classes/a/b/" + + if (objectUrl != null) { + String s = objectUrl.toExternalForm(); + + if (s.endsWith(resourceName)) { + int ix = s.length() - resourceName.length(); + codeBase = new URL(s.substring(0, ix)); + docBase = codeBase; + + ix = s.lastIndexOf('/'); + + if (ix >= 0) { + docBase = new URL(s.substring(0, ix + 1)); + } + } + } + + // Setup a default context and stub. + BeansAppletContext context = new BeansAppletContext(applet); + + stub = (AppletStub) new BeansAppletStub(applet, context, codeBase, docBase); + applet.setStub(stub); + } else { + initializer.initialize(applet, beanContext); + } + + // now, if there is a BeanContext, add the bean, if applicable. + + if (beanContext != null) { + beanContext.add(result); + } + + // If it was deserialized then it was already init-ed. + // Otherwise we need to initialize it. + + if (!serialized) { + // We need to set a reasonable initial size, as many + // applets are unhappy if they are started without + // having been explicitly sized. + applet.setSize(100, 100); + applet.init(); + } + + if (needDummies) { + ((BeansAppletStub) stub).active = true; + } else { + initializer.activate(applet); + } + + return true; + } +} + + +/** + * Package private support class. This provides a default AppletContext + * for beans which are applets. + */ + +class BeansAppletContext implements AppletContext { + Applet target; + Hashtable imageCache = new Hashtable(); + + BeansAppletContext(Applet target) { + this.target = target; + } + + public AudioClip getAudioClip(URL url) { + // We don't currently support audio clips in the Beans.instantiate + // applet context, unless by some luck there exists a URL content + // class that can generate an AudioClip from the audio URL. + try { + return (AudioClip) url.getContent(); + } catch (Exception ex) { + return null; + } + } + + public synchronized Image getImage(URL url) { + Object o = imageCache.get(url); + if (o != null) { + return (Image)o; + } + try { + o = url.getContent(); + if (o == null) { + return null; + } + if (o instanceof Image) { + imageCache.put(url, o); + return (Image) o; + } + // Otherwise it must be an ImageProducer. + Image img = target.createImage((java.awt.image.ImageProducer)o); + imageCache.put(url, img); + return img; + + } catch (Exception ex) { + return null; + } + } + + public Applet getApplet(String name) { + return null; + } + + public Enumeration getApplets() { + Vector applets = new Vector(); + applets.addElement(target); + return applets.elements(); + } + + public void showDocument(URL url) { + // We do nothing. + } + + public void showDocument(URL url, String target) { + // We do nothing. + } + + public void showStatus(String status) { + // We do nothing. + } + + public void setStream(String key, InputStream stream)throws IOException{ + // We do nothing. + } + + public InputStream getStream(String key){ + // We do nothing. + return null; + } + + public Iterator getStreamKeys(){ + // We do nothing. + return null; + } +} + +/** + * Package private support class. This provides an AppletStub + * for beans which are applets. + */ +class BeansAppletStub implements AppletStub { + transient boolean active; + transient Applet target; + transient AppletContext context; + transient URL codeBase; + transient URL docBase; + + BeansAppletStub(Applet target, + AppletContext context, URL codeBase, + URL docBase) { + this.target = target; + this.context = context; + this.codeBase = codeBase; + this.docBase = docBase; + } + + public boolean isActive() { + return active; + } + + public URL getDocumentBase() { + // use the root directory of the applet's class-loader + return docBase; + } + + public URL getCodeBase() { + // use the directory where we found the class or serialized object. + return codeBase; + } + + public String getParameter(String name) { + return null; + } + + public AppletContext getAppletContext() { + return context; + } + + public void appletResize(int width, int height) { + // we do nothing. + } +} diff -r f72c0dc047b9 -r 57914fd9382f src/share/classes/sun/beans/AppletProxy.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/sun/beans/AppletProxy.java Tue Jun 16 17:53:32 2009 +0200 @@ -0,0 +1,42 @@ +/* + * Copyright 1996-2009 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.beans; + +import java.beans.beancontext.BeanContext; +import java.net.MalformedURLException; + + +/** + * Bridge between java.beans module and java.applet module. + * @author Jaroslav Tulach + */ + +public abstract class AppletProxy { + public abstract boolean initialize( + Object result, Object init, boolean serialized, + String beanName, BeanContext beanContext, ClassLoader cls + ) throws MalformedURLException; +} diff -r f72c0dc047b9 -r 57914fd9382f build.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/build.xml Tue Jun 16 17:53:32 2009 +0200 @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="modularize" default="all" basedir="."> + <description>Scripts to build JDK Java sources in modularized way</description> + + <target name="all"> + <antcall target="base"/> + <antcall target="beans"/> + <antcall target="applet"/> + </target> + <!-- basic parameters --> + <path id="src.path"> + <pathelement location="src/share/classes"/> + <pathelement location="src/windows/classes"/> + <pathelement location="src/solaris/classes"/> + </path> + <property name="build.dir" location="build/modularize"/> + + <!-- this is the core of the separation - definition + of what classes belong into what compilation group. + --> + <selector id="applet"> + <or> + <filename name="java/beans/AppletInitializer*"/> + <filename name="java/applet/**"/> + <filename name="sun/applet/**"/> + <filename name="META-INF/services/sun.beans.AppletProxy"/> + </or> + </selector> + <selector id="beans"> + <and> + <or> + <filename name="java/beans/**"/> + <filename name="sun/beans/**"/> + <filename name="com/sun/beans/**"/> + </or> + <none> + <selector refid="applet"/> + </none> + </and> + </selector> + + <selector id="corba"> + <or> + <filename name="org/omg/**"/> + <filename name="com/sun/corba/**"/> + </or> + </selector> + + <selector id="base"> + <none> + <selector refid="applet"/> + <selector refid="beans"/> + <selector refid="corba"/> + </none> + </selector> + + <!-- individual compilation tasks --> + + <target name="applet"> + <antcall target="-compile-one-module"> + <param name="module" value="applet"/> + <param name="depends" value="beans"/> + </antcall> + </target> + <target name="beans"> + <antcall target="-compile-one-module"> + <param name="module" value="beans"/> + </antcall> + </target> + + + <!-- as I am currently unable to build JDK myself, I'll start + the modularization by extracting the base module from an existing + classes + --> + <target name="base"> + <property name="rt.jar" location="${java.home}/lib/rt.jar"/> + <fail message="Cannot find rt.jar, specify with -Drt.jar=..."> + <condition><not><available file="${rt.jar}"/></not></condition> + </fail> + + <mkdir dir="${build.dir}/rt"/> + <unzip src="${rt.jar}" dest="${build.dir}/rt"/> + <jar file="${build.dir}/base.jar" compress="false"> + <fileset dir="${build.dir}/rt"> + <selector refid="base"/> + </fileset> + </jar> + </target> + + <!-- shared routine to compile one of the modules --> + <target name="-compile-one-module"> + <mkdir dir="${build.dir}/classes/${module}"/> + <pathconvert pathsep="," property="module.cp"> + <path path="${depends}"/> + <mapper type="regexp" from=".*[/\\]([^/\\]*)" to="${build.dir}/\1.jar"/> + </pathconvert> + <javac + bootclasspath="${build.dir}/base.jar" + sourcepath="" + destdir="${build.dir}/classes/${module}" + classpath="${module.cp}" + includejavaruntime="false" + includeantruntime="false" + > + <src refid="src.path"/> + <selector refid="${module}"/> + </javac> + <copy todir="${build.dir}/classes/${module}"> + <fileset dir="src/share/classes"> + <and> + <selector refid="${module}"/> + <not> + <filename name="**/*.java"/> + </not> + </and> + </fileset> + </copy> + + <jar + basedir="${build.dir}/classes/${module}" + destfile="${build.dir}/${module}.jar" + compress="false" + /> + </target> + + <!-- clean everything --> + <target name="clean"> + <delete dir="${build.dir}"/> + </target> +</project>
The idea is that when the applet module is not installed, there is no AppletProxy provider meaning that the application would not reference any types in the applet module. When the applet module is installed, it will install the provider and update META-INF/services/sun.beans.AppletProxy and thereafter the service loader will find it.
So things are looking good. With just one problem: There is a static method in Beans class that takes Template:AppletInitializer parameter. Right now it is commented out, but for the sake of BackwardCompatibility I need to return it back? Another puzzle! What shall I do now?
<comments/>