Modular Java SE

From APIDesign

(Difference between revisions)
Jump to: navigation, search
(java.applet and java.beans)
Current revision (08:08, 1 February 2018) (edit) (undo)
(Changing Rules of the Game)
 
(33 intermediate revisions not shown.)
Line 1: Line 1:
-
I like puzzles that tease my mind (a bit). Last week I've been introduced to one. Modularize JDK (a described at [http://blogs.sun.com/mr/entry/modular_java_platform Mark Reinhold's blog]). This page will capture my thoughts on this topic.
+
I like puzzles that tease my mind (a bit). Last week I've been introduced to one. [[Modularize]] JDK (as described at [http://blogs.sun.com/mr/entry/modular_java_platform Mark Reinhold's blog]). This page will capture my thoughts and experiments on this topic. I don't know how much my work influenced actual [[Jigsaw]] work, but I know that at least Mandy has seen my results.
There can be many reasons for having [[module system|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.
There can be many reasons for having [[module system|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.
Line 7: Line 7:
Sometimes people expect to get better performance just by modularizing their application. This is probably a false expectation, at least in the initial step.
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.
+
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 overhead. Not much, but certainly something.
-
I have faced this when I split the monolithic '''openide.jar''' - a [[JAR]] with majority of [[NetBeans]] [[API]]s back in 2005 (see the [http://openide.netbeans.org/proposals/arch/modularize.html 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.
+
I have faced this when I split the monolithic '''openide.jar''' - a [[JAR]] with majority of [[NetBeans]] [[API]]s back in 2005 (see the [[Modularization of NetBeans Platform]] 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 [[JAR]]s at all. Thus we have the deployment benefits as claimed in [[NetBeans Runtime Container#Manifesto|manifesto of modular programming]], while during runtime the system behaves like a monolithic application.
+
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 [[JAR]]s at all. Thus we have the deployment benefits as claimed in [[Modularity|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.
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.
Line 21: Line 21:
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.
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''.
+
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 sets of files and feed them into the compiler.
 +
 
 +
The following part of ''build.xml'' defines three groups of sources: ''applet'', ''beans'' and the rest - called ''base''.
<source lang="xml">
<source lang="xml">
Line 77: Line 78:
With infrastructure like this one, you can start splitting your project apart.
With infrastructure like this one, you can start splitting your project apart.
 +
 +
=== Hudson Builder ===
 +
 +
There is a [http://hudson.apidesign.org/hudson/job/jdk/ hudson job] to build the whole system, but you shall be able to build the sources manually too. Here are the steps to follow:
 +
 +
* get the [[OpenJDK]] sources, see their website for more info
 +
$ hg fclone http://hg.openjdk.java.net/jdk7/jdk7
 +
* change subtree of the repository to our
 +
$ vi jdk/.hg/hgrc
 +
# change to:
 +
# default = http://source.apidesign.org/hg/jdk/
 +
* update to new version
 +
$ (cd jdk; hg pull -u)
 +
* build the sources of the JDK itself. This is tricky, but I succeeded with following steps (of course you need to download plugs and jibx according to the [[OpenJDK]] instructions):
 +
$ MYJDKROOT=`pwd`/..
 +
$ export ALT_BOOTDIR=/usr/java/jdk1.7.0/
 +
$ export ALT_HOTSPOT_IMPORT_PATH=/usr/java/jdk1.7.0/
 +
$ export ALT_BINARY_PLUGS_PATH=$MYJDKROOT/plugs
 +
$ export JAVA_HOME=""
 +
$ export ANT_HOME=/usr/share/java/ant/
 +
$ export ALT_JIBX_LIBS_PATH=$MYJDKROOT/jibx/lib/
 +
$ make sanity
 +
$ make all
 +
* And now we are ready to work with the separation itself. First of all copy all the sources spread around the Hg tree into one directory:
 +
$ cd jdk
 +
$ ant merge-sources
 +
* And now build your modularized [[JDK]]:
 +
$ ANT_OPTS=-mx900M ant clean all
 +
 +
After this initial sequence you can start to play with the [[OpenJDK]] sources, modify them, tweak the [[Ant]] build script (especially its selectors) and repeat only the last step to be sure that the system still continue to build.
== java.applet and java.beans ==
== java.applet and java.beans ==
Line 86: Line 117:
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 [[JavaBean]]s module in future, needs to install applet module as well?
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 [[JavaBean]]s module in future, needs to install applet module as well?
-
No. I have a solution: Let's use [[CodeInjection]]! 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 [http://hg.openjdk.java.net/jdk7/jdk7/jdk openjdk repository]:
+
No. I have a solution: Let's use [[CodeInjection]]! 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 out [http://source.apidesign.org/hg/jdk/ openjdk repository]:
-
<source lang="diff">
+
The diff is [http://source.apidesign.org/hg/jdk/rev/57914fd9382f here].
-
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>
+
-
</source>
+
The
The
Line 921: Line 129:
loader will find it.
loader will find it.
 +
=== Sneaking Simplicity In ===
 +
 +
So things are looking good. With just one problem: There is a static method in '''Beans''' class that takes '''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?
 +
 +
Well, the basic trick is to ''sneak in simplicity''. Of course ''simplicity'' can have various meanings, but in this context it means number of outgoing [[dependencies]]. The '''Beans''' class is not simple, because it has dependency on beans, as well as applet classes. If we can replace it with some other class, that does not depend on applet, then we will simplify the [[API]]. Sometimes this is called [[conceptual surface]] - the amount of concepts one needs to understand when dealing with an [[API]]. By removing the need for users of a class to know anything about applet, we ''simplify'' its surface. Not only that, we also allow it to be compilable without applet being around (which is actually the most important goal when modularizing an [[API]]).
 +
 +
The only question is how to ''simplify'' the ''Beans'' class? Of course, the simplest way is to remove the one static method that references '''Applet''' - however this is horribly [[BackwardCompatibility|backward incompatible]] and compatibility for existing clients in our highest priority. Thus the only compile time option is to deprecate the whole Beans class and replace it with some other, ''simplified'' one.
 +
 +
I did that by creating new '''BeanFactory''' class that does not reference applet at all and otherwise contains the same methods like '''Beans''' class.
 +
 +
=== There Will Be Victims ===
 +
 +
When modularizing an [[API]], get ready to have some victims - some classes that will need to be deprecated. Regardless of how well designed your [[API]] is, it will contain classes with not enough ''simplicity'', like the ''Beans'' class above. Prepare for that and create a trash for them - a ''deprecated'' module.
 +
 +
The purpose of such module is to keep [[BackwardCompatibility]] only. It will have dependencies on all other modules in your system, and as such it can contain classes that do not fit anywhere else. Users of previous version of your [[API]] should see this module by default, so their previous dependencies are satisfied.
 +
 +
On the other hand users of your new version, shall not care and shall use other classes in properly modularized [[API]]s that have smaller [[conceptual surface]] and smaller compile type and runtime [[dependencies]].
 +
 +
For those interested, here is the final diff of the java.beans and java.applet separation:
 +
[http://source.apidesign.org/hg/jdk/rev/57914fd9382f read it all]!
 +
 +
==== Changing Rules of the Game ====
 +
 +
I have just find out a trick to prevent the ''victims'' mentioned in the previous paragraph. The [[OpenJDK]] is considering to change the specification of the [[Java]] [[HotSpot|virtual machine]] to allow a class with not fully resolved methods to be loaded into the runtime and work fine. Everything is supposed to work until one calls the method which is not resolvable (for example '''AppletInitializer''' class would be missing for one of '''Beans''' methods), then an exception shall be thrown.
 +
 +
Very useful trick. This shows what one can achieve when thinking without boundaries and also, when one is allowed to ''change the rules of the game''. When I did the modularization of [[NetBeans]] [[API]]s, I obviously could not change the [[HotSpot|virtual machine]] spec. I was left with only one option: I had to ''sneak simplicity'' in. I had to deprecate classes with too large [[conceptual surface]] and replace them with similar classes without unneeded [[dependencies]]. This was fine in case of '''Beans''' like classes that no other [[API]] referred to (in signatures). However as this deprecation is transitive, it becomes quite hard to replace '''java.awt.Image''' - one would need to deprecate also '''java.awt.Toolkit''' and '''java.awt.Graphics''', and so on, so on transitively. It is impossible to get rid of a single method in '''java.lang.String''' using this style, obviously.
 +
 +
Thus it is good there will be chance to do ''partial resolution'' of classes by the [[HotSpot|virtual machine]]. Especially when one faces a ''sneak in simplicity'' problem with too big transitive closure, this can be very helpful. On the other hand I can imagine problems with complicated (e.g. not enough [[Cluelessness|clueless]]) [[Javadoc]] that will mention ''conditional'' method and conditions when they are allow to work, etc. That is why in the '''Beans''' case, it still may be better to ''sneak the simplicity in'' and classically deprecate the old class and provide a modern replacement.
 +
 +
== java.awt and javax.swing ==
 +
 +
I managed to remove dependency of the base Java on ''client'' packages this week too (see the [http://source.apidesign.org/hg/jdk/rev/393046bdec90 full patch]). Things were straightforward, just I had to include '''java.beans''' packages into the ''client'' module as well. They heavily depend on '''java.awt.Image''' and '''java.awt.Component''' - these classes are really unseparable from the rest of [[AWT]]. At the end this shall not be that big problem, as [[JavaBean]]s primary purpose is to provide means for visual manipulation with plain [[Java]] objects and this indeed needs to happen on some ''client''.
 +
 +
Well, except some other advanced usages, which rely on '''java.beans.FeatureDescriptor''' only, just like [[JMX]]. This subsystem definitely does not belong only to ''client'', it is even more useful in headless environments and as such I had to make it work without [[JavaBean]]s [[API]]. Again, I used [[CodeInjection]] - in this case with a fallback. If the [[JavaBean]]s [[API]] is loaded into the system, its own '''Introspector''' is used. In case the system runs without [[JavaBean]]s a simple implementation that identifies getters in their default way (either ''getXYZ'' or ''isXYZ'') is used (see the [http://source.apidesign.org/hg/jdk/diff/adeb4a4c4aa6/src/share/classes/com/sun/jmx/mbeanserver/IntrospectorProxy.java SimpleIntrospector patch]).
 +
 +
I believe this provides the best balance between [[BackwardCompatibility]] (if running with [[JavaBean]] the behaviour stays the same) and [[modularity]] (there is a reasonable default behaviour in small setup). Actually, the default behaviour is in fact also correct - the advanced definition of getters is possible only if one includes own '''java.beans.BeanInfo''' next to the bean's class. However this is possible only if one depends on [[JavaBean]] specification and as such it will be present in the runtime and thus the [[CodeInjection]] finds its provider and the behaviour stays fully [[BackwardCompatibility|compatible]].
 +
 +
== [[XML]] [[SAX]] and [[DOM]] 2 ==
 +
 +
The last part that deserves surgery from the ''small'' [[Java]] is support for processing of [[XML]]. I did this on Wednesday (see the [http://source.apidesign.org/hg/jdk/rev/75acc009e9d8 patch]). The [[XML]] [[API]] does not seem so big, but to my surprise there is really big implementation hidden behind it. When eliminated, I could reduce the number of [[Java]] files in base module from about 8000 to less then 6000. Looks like about 1/4 of base [[Java]] is dedicated to processing of [[XML]]. That is quite a lot given the fact that [[Java]] can be useful for other purposes as well. It is good the [[XML]] infrastructure is now a separate module.
 +
 +
There were two problems I had to face. '''java.util.prefs''' and '''java.util.Properties''' can be read and written into [[XML]]. Again I used the [[CodeInjection]], but in this case it may not be necessary. Writing the [[XML]] output is easy while using simple '''java.io.PrintWriter''' (see ''export'' method in following [http://source.apidesign.org/hg/jdk/rev/24b6c30fbf71 patch]). The only question is how to stick to the old behaviour as close a possible. Here I used [[ImplementationCompatibilityTest]] which writes the values of a '''Preferences''' into two strings (using the new and old implementation) and then compares the results are same (also available in the [http://source.apidesign.org/hg/jdk/rev/24b6c30fbf71 patch]). The test is extensible: Every time one notices a divergence in behaviour, one can easily demonstrate it in the test. Then it can be fixed for once and ever (stiffing the shape of its implementation [[Amoeba Model|amoeba]]).
 +
 +
Reading [[XML]] is slightly harder, and I definitely did not want to do it manually. In the name of [[cluelessness]] I wanted to assemble the solution, not write it in scratch. Thus I searched around and found a [http://devkix.com/nanoxml.php nanoxml] parser. Small, few kilobytes of code, that I used to parse the [[XML]] stream. The code remains the same and keeps the same functionality (I again used the [[ImplementationCompatibilityTest]]) and instead of 2MB of [[Xerces]] I need just about 10KB (e.g. [[cluelessness]] is good, but better to be [[cluelessness|clueless]] and small).
 +
 +
== Executive Summary ==
 +
 +
Those last fourteen days that I dedicated to prototype the [[modularity|modularization]] of [[JDK]] are over and it is time to do little recap.
 +
 +
The first achievement of this effort is narrowing the scope and expectations. The modularization is beneficial, but itself alone cannot speed up loading time of a Hello World! application.
 +
 +
To get the best from the modularization, it is not enough to split the code to modules. One also needs to improve the infrastructure behind (at least in long term). Proper runtime container needs [[CacheForModularity|various caches]] to optimize load time, runtime behaviour, etc.
 +
 +
This project demonstrated how to reuse [[Ant]] and create a reusable build infrastructure. With such infrastructure it is then easy to experiment with various groupings of classes into modules and immediately verify that these groupings are sane, remain compilable and usable.
 +
 +
The actual changes made to the source code of [[OpenJDK]] demonstrate few important aspects of [[modularity|modularization]]. First of all, be aware of benefits of [[CodeInjection]] that allows compile and deploy time separation, while keeping runtime cooperation.
 +
 +
Second: get ready for deprecations. Create a module that will contain all the deprecated [[API]] that don't fit anywhere else. This way it is possible to shrink the whole framework, while keeping its [[BackwardCompatibility]].
-
So things are looking good. With just one problem: There is a static method in Beans class that takes '''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?
+
Last but not least: Don't be afraid to re-implement existing implementations. With the help of [[ImplementationCompatibilityTest]]s one can create new, simplified implementations that remain totally compatible with previous ones (just like the prototype did for [[XML]]).
<comments/>
<comments/>

Current revision

I like puzzles that tease my mind (a bit). Last week I've been introduced to one. Modularize JDK (as described at Mark Reinhold's blog). This page will capture my thoughts and experiments on this topic. I don't know how much my work influenced actual Jigsaw work, but I know that at least Mandy has seen my results.

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.

Contents

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 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 Modularization of NetBeans Platform 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 sets of files and feed them into the compiler.

The following part of build.xml defines three 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.

Hudson Builder

There is a hudson job to build the whole system, but you shall be able to build the sources manually too. Here are the steps to follow:

  • get the OpenJDK sources, see their website for more info
$ hg fclone http://hg.openjdk.java.net/jdk7/jdk7
  • change subtree of the repository to our
$ vi jdk/.hg/hgrc
# change to: 
# default = http://source.apidesign.org/hg/jdk/
  • update to new version
$ (cd jdk; hg pull -u)
  • build the sources of the JDK itself. This is tricky, but I succeeded with following steps (of course you need to download plugs and jibx according to the OpenJDK instructions):
$ MYJDKROOT=`pwd`/..
$ export ALT_BOOTDIR=/usr/java/jdk1.7.0/
$ export ALT_HOTSPOT_IMPORT_PATH=/usr/java/jdk1.7.0/
$ export ALT_BINARY_PLUGS_PATH=$MYJDKROOT/plugs
$ export JAVA_HOME=""
$ export ANT_HOME=/usr/share/java/ant/
$ export ALT_JIBX_LIBS_PATH=$MYJDKROOT/jibx/lib/
$ make sanity
$ make all
  • And now we are ready to work with the separation itself. First of all copy all the sources spread around the Hg tree into one directory:
$ cd jdk
$ ant merge-sources
  • And now build your modularized JDK:
$ ANT_OPTS=-mx900M ant clean all

After this initial sequence you can start to play with the OpenJDK sources, modify them, tweak the Ant build script (especially its selectors) and repeat only the last step to be sure that the system still continue to build.

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 CodeInjection! 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 out openjdk repository:

The diff is here.

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.

Sneaking Simplicity In

So things are looking good. With just one problem: There is a static method in Beans class that takes 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?

Well, the basic trick is to sneak in simplicity. Of course simplicity can have various meanings, but in this context it means number of outgoing dependencies. The Beans class is not simple, because it has dependency on beans, as well as applet classes. If we can replace it with some other class, that does not depend on applet, then we will simplify the API. Sometimes this is called conceptual surface - the amount of concepts one needs to understand when dealing with an API. By removing the need for users of a class to know anything about applet, we simplify its surface. Not only that, we also allow it to be compilable without applet being around (which is actually the most important goal when modularizing an API).

The only question is how to simplify the Beans class? Of course, the simplest way is to remove the one static method that references Applet - however this is horribly backward incompatible and compatibility for existing clients in our highest priority. Thus the only compile time option is to deprecate the whole Beans class and replace it with some other, simplified one.

I did that by creating new BeanFactory class that does not reference applet at all and otherwise contains the same methods like Beans class.

There Will Be Victims

When modularizing an API, get ready to have some victims - some classes that will need to be deprecated. Regardless of how well designed your API is, it will contain classes with not enough simplicity, like the Beans class above. Prepare for that and create a trash for them - a deprecated module.

The purpose of such module is to keep BackwardCompatibility only. It will have dependencies on all other modules in your system, and as such it can contain classes that do not fit anywhere else. Users of previous version of your API should see this module by default, so their previous dependencies are satisfied.

On the other hand users of your new version, shall not care and shall use other classes in properly modularized APIs that have smaller conceptual surface and smaller compile type and runtime dependencies.

For those interested, here is the final diff of the java.beans and java.applet separation: read it all!

Changing Rules of the Game

I have just find out a trick to prevent the victims mentioned in the previous paragraph. The OpenJDK is considering to change the specification of the Java virtual machine to allow a class with not fully resolved methods to be loaded into the runtime and work fine. Everything is supposed to work until one calls the method which is not resolvable (for example AppletInitializer class would be missing for one of Beans methods), then an exception shall be thrown.

Very useful trick. This shows what one can achieve when thinking without boundaries and also, when one is allowed to change the rules of the game. When I did the modularization of NetBeans APIs, I obviously could not change the virtual machine spec. I was left with only one option: I had to sneak simplicity in. I had to deprecate classes with too large conceptual surface and replace them with similar classes without unneeded dependencies. This was fine in case of Beans like classes that no other API referred to (in signatures). However as this deprecation is transitive, it becomes quite hard to replace java.awt.Image - one would need to deprecate also java.awt.Toolkit and java.awt.Graphics, and so on, so on transitively. It is impossible to get rid of a single method in java.lang.String using this style, obviously.

Thus it is good there will be chance to do partial resolution of classes by the virtual machine. Especially when one faces a sneak in simplicity problem with too big transitive closure, this can be very helpful. On the other hand I can imagine problems with complicated (e.g. not enough clueless) Javadoc that will mention conditional method and conditions when they are allow to work, etc. That is why in the Beans case, it still may be better to sneak the simplicity in and classically deprecate the old class and provide a modern replacement.

java.awt and javax.swing

I managed to remove dependency of the base Java on client packages this week too (see the full patch). Things were straightforward, just I had to include java.beans packages into the client module as well. They heavily depend on java.awt.Image and java.awt.Component - these classes are really unseparable from the rest of AWT. At the end this shall not be that big problem, as JavaBeans primary purpose is to provide means for visual manipulation with plain Java objects and this indeed needs to happen on some client.

Well, except some other advanced usages, which rely on java.beans.FeatureDescriptor only, just like JMX. This subsystem definitely does not belong only to client, it is even more useful in headless environments and as such I had to make it work without JavaBeans API. Again, I used CodeInjection - in this case with a fallback. If the JavaBeans API is loaded into the system, its own Introspector is used. In case the system runs without JavaBeans a simple implementation that identifies getters in their default way (either getXYZ or isXYZ) is used (see the SimpleIntrospector patch).

I believe this provides the best balance between BackwardCompatibility (if running with JavaBean the behaviour stays the same) and modularity (there is a reasonable default behaviour in small setup). Actually, the default behaviour is in fact also correct - the advanced definition of getters is possible only if one includes own java.beans.BeanInfo next to the bean's class. However this is possible only if one depends on JavaBean specification and as such it will be present in the runtime and thus the CodeInjection finds its provider and the behaviour stays fully compatible.

XML SAX and DOM 2

The last part that deserves surgery from the small Java is support for processing of XML. I did this on Wednesday (see the patch). The XML API does not seem so big, but to my surprise there is really big implementation hidden behind it. When eliminated, I could reduce the number of Java files in base module from about 8000 to less then 6000. Looks like about 1/4 of base Java is dedicated to processing of XML. That is quite a lot given the fact that Java can be useful for other purposes as well. It is good the XML infrastructure is now a separate module.

There were two problems I had to face. java.util.prefs and java.util.Properties can be read and written into XML. Again I used the CodeInjection, but in this case it may not be necessary. Writing the XML output is easy while using simple java.io.PrintWriter (see export method in following patch). The only question is how to stick to the old behaviour as close a possible. Here I used ImplementationCompatibilityTest which writes the values of a Preferences into two strings (using the new and old implementation) and then compares the results are same (also available in the patch). The test is extensible: Every time one notices a divergence in behaviour, one can easily demonstrate it in the test. Then it can be fixed for once and ever (stiffing the shape of its implementation amoeba).

Reading XML is slightly harder, and I definitely did not want to do it manually. In the name of cluelessness I wanted to assemble the solution, not write it in scratch. Thus I searched around and found a nanoxml parser. Small, few kilobytes of code, that I used to parse the XML stream. The code remains the same and keeps the same functionality (I again used the ImplementationCompatibilityTest) and instead of 2MB of Xerces I need just about 10KB (e.g. cluelessness is good, but better to be clueless and small).

Executive Summary

Those last fourteen days that I dedicated to prototype the modularization of JDK are over and it is time to do little recap.

The first achievement of this effort is narrowing the scope and expectations. The modularization is beneficial, but itself alone cannot speed up loading time of a Hello World! application.

To get the best from the modularization, it is not enough to split the code to modules. One also needs to improve the infrastructure behind (at least in long term). Proper runtime container needs various caches to optimize load time, runtime behaviour, etc.

This project demonstrated how to reuse Ant and create a reusable build infrastructure. With such infrastructure it is then easy to experiment with various groupings of classes into modules and immediately verify that these groupings are sane, remain compilable and usable.

The actual changes made to the source code of OpenJDK demonstrate few important aspects of modularization. First of all, be aware of benefits of CodeInjection that allows compile and deploy time separation, while keeping runtime cooperation.

Second: get ready for deprecations. Create a module that will contain all the deprecated API that don't fit anywhere else. This way it is possible to shrink the whole framework, while keeping its BackwardCompatibility.

Last but not least: Don't be afraid to re-implement existing implementations. With the help of ImplementationCompatibilityTests one can create new, simplified implementations that remain totally compatible with previous ones (just like the prototype did for XML).

<comments/>

Personal tools
buy