|
|
||
Kirill Grouchnikov's BlogAugust 2005 ArchivesCollaboration between two look-and-feel projectsPosted by kirillcool on August 30, 2005 at 01:43 PM | Permalink | Comments (6)Werner Randelshofer is the developer of very popular Quaqua look and feel. As said on the project main page, Quaqua is focused on fixing minor bugs and glitches in Apple's implementation of the AHIG as well as providing enhancements that make your application fit nicely into Mac OS X. Unfortunately, this excellent look and feel is available for Macintosh only, and until the last release, there wasn't a lot of code which could have been shared with other platforms. However, the new release 3.3 features an excellent implementation of color chooser dialog UI delegate (see left screenshot below), the first sight of which immediately made me want to have such delegate in my own Substance look and feel (which takes many of its ideas from Mac 10.4 already). Two choices presented themselves immediately: reinvent the wheel or ask to share the code. Undoubtedly, the first choice would be the choice of many open-source developers - a nice-looking wheel waiting to be reinvented. This would mean, of course, an inevitable peek at the source code, and a well-known dilemma, to copy as is or to pretend that you are writing something completely new while you are looking at somebody else's code all along. The second way involves a simple question ("Would you be kind enough to allow sharing some of your code as long as the licenses match and the copyright / credits are in place?"), a short wait, and a sigh of relief when the positive answer comes through. The sigh of relief stems from many reasons.
You can try the Web Start version of Substance look and feel test application (go to Dialogs tab and click Color button). Note that you will need Tiger+ for running this application Technorati Profile Personalizing your java.net project site - using icons for address bar and bookmarksPosted by kirillcool on August 28, 2005 at 07:37 AM | Permalink | Comments (5)We are all familiar with custom icons for the webpages that we surf. These icons are shown in the address bar, in tab that shows the page (in Firefox) and in bookmarks. However, i haven't seen a single java.net community, blog or project page that features such an icon (save for the one in the comments :). The reason remains obscure, as all you have to do is a simple one-file upload to your CVS repository.
Technorati Profile How single can your singleton instance be?Posted by kirillcool on August 24, 2005 at 01:40 AM | Permalink | Comments (12)We are all familiar with the following straightforward implementation of lazy-loaded Singleton pattern:
public class TestSingleton {
private static TestSingleton instance;
private TestSingleton() {}
public static synchronized TestSingleton getInstance() {
if (instance == null) {
instance = new TestSingleton();
}
return instance;
}
}
The constructor is private, and a single public function is exposed. This function (which is also synchronized) tests whether there is already an instance and if not, it creates one. Item 57 in Joshua Bloch's "Effective Java" instructs us to add readResolve() function in case the singleton is also Serializable. Another version, which employs eager-loading is:
public class TestSingleton {
private static TestSingleton instance = new TestSingleton();
private TestSingleton() {}
public static TestSingleton getInstance() {
return instance;
}
}
It's even simpler than the previous one. You pay a one-time penalty of creating an object (even if you never use it), but the getInstance() is no longer synchronized. The constructor, is of course, private in both cases to prevent accessing it from the outside code. However, as shown before, you can use combination of getDeclaredConstructors and setAccessible to make the private constructor accessible via reflection. You can then create any number of instances of that singleton class. However, the author of the above failed to notice that the created instance may not be fully functional. The publically-accessible getInstance() function can perform additional manipulations with the created object, setting additional properties and attributes. In this case, the object you have may not function properly. Here, you need to call the getInstance() function and have it return a different instance (properly and fully initialized) each time you call it. The answer lies in Chapter 2 of excellent Component Development for the Java Platform book (which came out in 2001). This chapter describes the way the ClassLoaders work, and how you can have any number of Class object instances for the same class. Let's start with a small (and not real-life) example to show how it works, and then go to a real-life example. First, we create a new Java class at runtime:
public class SingletonMain {
public static void main(String... args) throws Exception {
Logger _logger = Logger.getLogger(SingletonMain.class.getName());
// create first version of class
String className = "TestSingleton";
String dir1 = "C:\\temp\\ver1";
new File(dir1).mkdirs();
BufferedWriter bw1 = new BufferedWriter(new FileWriter(dir1
+ File.separator + className + ".java"));
bw1.write("public class " + className + " {");
bw1.newLine();
bw1.write(" private static " + className + " instance;");
bw1.newLine();
bw1.write(" private " + className + "() {}");
bw1.newLine();
bw1.write(" public static synchronized " + className + " getInstance() {");
bw1.newLine();
bw1.write(" if (instance == null) {");
bw1.newLine();
bw1.write(" instance = new " + className + "();");
bw1.newLine();
bw1.write(" }");
bw1.newLine();
bw1.write(" return instance;");
bw1.newLine();
bw1.write(" }");
bw1.newLine();
bw1.write("}");
bw1.newLine();
bw1.close();
The class looks like this:
public class TestSingleton {
private static TestSingleton instance;
private TestSingleton() {}
public static synchronized TestSingleton getInstance() {
if (instance == null) {
instance = new TestSingleton();
}
return instance;
}
}
We now compile it and use URLClassLoader to load the compiled class:
// compile
int compStatus = com.sun.tools.javac.Main.compile(new String[] { dir1
+ File.separator + className + ".java" });
_logger.info("Compilation status is " + compStatus);
// load
ClassLoader classLoader1 = new URLClassLoader(
new URL[] { new File(dir1).toURL() });
Class clazz1 = classLoader1.loadClass(className);
_logger.info("Loaded " + clazz1.getName() + " [" + clazz1.hashCode()
+ "]");
The output is
INFO [11:05:27.553] [SingletonMain.main] Compilation status is 0 INFO [11:05:27.584] [SingletonMain.main] Loaded TestSingleton [24287316]Now, we use reflection to call the getInstance() function. In addition, we test that no public constructor is defined:
Method getter1 = clazz1.getMethod("getInstance", new Class[0]);
Constructor[] ctrs1 = clazz1.getConstructors();
_logger.info(ctrs1.length + " constructors");
Object val10 = getter1.invoke(null, new Object[0]);
_logger.info("Fetched object #" + val10.hashCode());
Object val11 = getter1.invoke(null, new Object[0]);
_logger.info("Fetched object #" + val11.hashCode());
Object val12 = getter1.invoke(null, new Object[0]);
_logger.info("Fetched object #" + val12.hashCode());
The result here is, as expected, the same instance on all calls to getInstance():
INFO [11:05:27.584] [SingletonMain.main] 0 constructors INFO [11:05:27.584] [SingletonMain.main] Fetched object #7254922 INFO [11:05:27.584] [SingletonMain.main] Fetched object #7254922 INFO [11:05:27.584] [SingletonMain.main] Fetched object #7254922As mentioned before, you can use getDeclaredConstructors() and setAccessible() on Constructor to make it accessible, but then you will miss the additional logic (empty in our sample case) in getInstance(). In order to create a second instance of TestSingleton class, we need to create additional ClassLoader that does not have our first ClassLoader as its ascendant. As described in the book, the ClassLoader first checks its cache to see if it already loaded the specified class, then asks its parent for the class (recursively until there is no parent), and only then (if parent tells that it doesn't have this class) it loads the class (from the URL in our case). Here, we need to copy the compiled class to another directory and create a second URLClassLoader. Both class loaders will have the same parent (system class loader), but this parent doesn't have the TestSingleton class, hence the second class loader will have its own copy:
// copy the class file to another directory
String dir2 = "C:\\temp\\ver2";
new File(dir2).mkdirs();
InputStream in = new FileInputStream(new File(dir1 + File.separator
+ className + ".class"));
OutputStream out = new FileOutputStream(new File(dir2 + File.separator
+ className + ".class"));
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
// load
ClassLoader classLoader2 = new URLClassLoader(
new URL[] { new File(dir2).toURL() });
Class clazz2 = classLoader2.loadClass(className);
_logger.info("Loaded " + clazz2.getName() + " [" + clazz2.hashCode()
+ "]");
The result is:
INFO [11:05:27.600] [SingletonMain.main] Loaded TestSingleton [33482492]As you can see, the hash code of the second class object is different - we have a distinct class. Let's test that it's really different:
Method getter2 = clazz2.getMethod("getInstance", new Class[0]);
Constructor[] ctrs2 = clazz1.getConstructors();
_logger.info(ctrs2.length + " constructors");
Object val20 = getter2.invoke(null, new Object[0]);
_logger.info("Fetched object #" + val20.hashCode());
Object val21 = getter2.invoke(null, new Object[0]);
_logger.info("Fetched object #" + val21.hashCode());
Object val22 = getter2.invoke(null, new Object[0]);
_logger.info("Fetched object #" + val22.hashCode());
And the result is:
INFO [11:05:27.600] [SingletonMain.main] 0 constructors INFO [11:05:27.600] [SingletonMain.main] Fetched object #32429958 INFO [11:05:27.600] [SingletonMain.main] Fetched object #32429958 INFO [11:05:27.600] [SingletonMain.main] Fetched object #32429958So, now we have two instances of (almost) the same class. It is true that the Class objects are different, but the functionality is the same. Now that we have seen the technique, it can be trivially applied to already existing classes. In order to do this, we scan the class path looking for either the .class on local computer or for jar file that contains the required class. After we find it, we copy the corresponding class to some directory, and load it using the same technique as above (passing null parent). Afterwards we create a second copy in another directory.
public class SingletonMain2 {
public static void main(String... args) throws Exception {
Logger _logger = Logger.getLogger(SingletonMain2.class.getName());
String className = "TestSingleton2";
String classSig = className + ".class";
File dir1 = new File("C:\\temp\\ver1");
dir1.mkdirs();
Now we start to scan the classpath. Note that here we show only Windows version. For cross-platform, you need to use ":;" pattern to break the classpath and assemble drive names back when you are under Windows:
String classpath = System.getProperty("java.class.path");
_logger.info("Classpath : " + classpath);
String[] pathComps = classpath.split(";");
boolean wasLocated = false;
For each classpath component, we check whether it is a jar file or a directory. The handling of jar files is left as a (simple) exercise:
for (String pathComp : pathComps) {
if (wasLocated)
break;
if (pathComp.endsWith(".jar")) {
// scan jar and look for the desired class.
// Use JarInputStream for reading jar file and
// JarEntry for checking the current entry.
// The, getInputStream() on the matching entry
// and copy it to the temp directory as below.
}
In case it's a directory, we scan it for the desired .class file (not that if the class belongs to some package, you'll need to adjust the scan code correspondingly):
// should be directory
File pathCompDir = new File(pathComp);
if (pathCompDir.exists() && pathCompDir.isDirectory()) {
// scan for the class
File[] allFiles = pathCompDir.listFiles();
for (File currFile : allFiles) {
if (currFile.getName().equals(classSig)) {
_logger.info("Located at "
+ currFile.getAbsolutePath());
wasLocated = true;
// copy to another directory
InputStream in = new FileInputStream(currFile);
OutputStream out = new FileOutputStream(new File(
dir1, classSig));
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
break;
}
}
}
As shown above, when the class is found, it is copied to a temporary directory. After we are done copying, we create the first URLClassLoader with null parent. This is crucial - the default parent of a new class loader is the system class loader. In this case, the parent class loader has access to the original class (since it's in the classpath), and the copied class will be simply ignored.
if (!wasLocated) {
_logger.info("Not located - quitting");
}
// very important - pass null parent class loader, otherwise
// the parent class loader will be system class loader and
// it will find the class in the classpath
ClassLoader classLoader1 = new URLClassLoader(
new URL[] { dir1.toURL() }, null);
Class clazz1 = classLoader1.loadClass(className);
_logger.info("Loaded " + clazz1.getName() + " [" + clazz1.hashCode()
+ "]");
_logger.info("Have " + clazz1.getName() + " [" + clazz1.hashCode()
+ "]");
The result is
INFO [11:27:39.598] [SingletonMain2.main] Loaded TestSingleton2 [4384790] INFO [11:27:39.598] [SingletonMain2.main] Have TestSingleton2 [4384790]Now, as before we call getInstance() a couple of times:
Method getter1 = clazz1.getMethod("getInstance", new Class[0]);
Constructor[] ctrs1 = clazz1.getConstructors();
_logger.info(ctrs1.length + " constructors");
Object val10 = getter1.invoke(null, new Object[0]);
_logger.info("Fetched object #" + val10.hashCode());
Object val11 = getter1.invoke(null, new Object[0]);
_logger.info("Fetched object #" + val11.hashCode());
Object val12 = getter1.invoke(null, new Object[0]);
_logger.info("Fetched object #" + val12.hashCode());
And see that the same instance is returned:
INFO [11:27:39.598] [SingletonMain2.main] 0 constructors INFO [11:27:39.598] [SingletonMain2.main] Fetched object #9634993 INFO [11:27:39.598] [SingletonMain2.main] Fetched object #9634993 INFO [11:27:39.598] [SingletonMain2.main] Fetched object #9634993Now, we copy the same class to another temporary directory
// copy the class file to another directory
String dir2 = "C:\\temp\\ver2";
new File(dir2).mkdirs();
InputStream in = new FileInputStream(new File(dir1 + File.separator
+ className + ".class"));
OutputStream out = new FileOutputStream(new File(dir2 + File.separator
+ className + ".class"));
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
And create additional class loader:
// load
ClassLoader classLoader2 = new URLClassLoader(
new URL[] { new File(dir2).toURL() }, null);
Class clazz2 = classLoader2.loadClass(className);
_logger.info("Loaded " + clazz2.getName() + " [" + clazz2.hashCode()
+ "]");
The class object is different:
INFO [11:27:39.614] [SingletonMain2.main] Loaded TestSingleton2 [14576877]Call getInstance() a couple of times on the new class:
Method getter2 = clazz2.getMethod("getInstance", new Class[0]);
Constructor[] ctrs2 = clazz2.getConstructors();
_logger.info(ctrs2.length + " constructors");
Object val20 = getter2.invoke(null, new Object[0]);
_logger.info("Fetched object #" + val20.hashCode());
Object val21 = getter2.invoke(null, new Object[0]);
_logger.info("Fetched object #" + val21.hashCode());
Object val22 = getter2.invoke(null, new Object[0]);
_logger.info("Fetched object #" + val22.hashCode());
And you get the same (but different from the first) object:
INFO [11:27:39.676] [SingletonMain2.main] 0 constructors INFO [11:27:39.676] [SingletonMain2.main] Fetched object #12677476 INFO [11:27:39.676] [SingletonMain2.main] Fetched object #12677476 INFO [11:27:39.676] [SingletonMain2.main] Fetched object #12677476And now, the big question - how single can your singleton instance be? See here how to create a true singleton. Technorati Profile Mustang synergy - the sum is less than the total of the parts?Posted by kirillcool on August 22, 2005 at 11:41 PM | Permalink | Comments (1)The early access and weekly builds of JAXB 2.0 have been available for quite a few months now right here on java.net. A few weeks ago JAXB 2.0 made its way to Mustang weeklies. As the minimal JRE requirement for JAXB 2.0 is Tiger (5.0), in case you wish to use it, you have to bundle JAXB 2.0 runtime binaries with your application (unless you are explicitly targeting Mustang). You would expect, of course, to run smoothly under Mustang with the same binaries (even if they provide the same classes). However, that is not the case with the latest build (48) of Mustang - a lot of class-related issues for both design-time (XJC) and runtime binaries of the latest JAXB 2.0 RI weekly. I wouldn't blog here on such a small issue, but it raises more interesting questions. What is the magnitude and the complexity of taking weeklies from java.net CVS-based projects and putting them to Mustang? Can they be synchronized weekly, or Mustang weeklies are bound to lag behind? Are there some minimal acceptance tests done? How many such java.net projects are integrated weekly (besides JAXB 2.0 and JAX-WS 2.0)? What could be done to ensure that all parts play well when i take the "latest of everything"? Eventually, i had to remove the Mustang b48 installation completely. Although in command-prompt and IDE i can choose the JRE myself, in Web Start i wasn't able to uncheck the "System" VM (6.0) and make 5.0 to be default. There went another free tester for Mustang - now I'll run all my development under Tiger :( Backstreet boyz "404 File not found" lyricsPosted by kirillcool on August 18, 2005 at 08:57 AM | Permalink | Comments (0)New web pages fill me up with hopes Distant servers, so many places to go Without surfing I can't find no rest Where I'm clicking is just Google's guess I've tried to go on like I never saw it I'm awake but my world is half asleep I pray for this message to be unbroken But all I can see on my screen is that "Page not found" Reason tells me I should carry on But I am swimming in the web all alone Server, my server, it vanished from your disk I still wonder if I typed wrong URL I've tried to go on like I never saw it I'm awake but my world is half asleep I pray for this message to be unbroken But all I can see on my screen is that "Page not found" I don't mean to refresh it on, but I can't seem to let it go I don't wanna go and face this world again I don't wanna see this page (again) I've tried to go on like I never saw it I'm awake but my world is half asleep I pray for this message to be unbroken But all I can see on my screen is that "Page not found" How to use "undocumented secret" Swing propertiesPosted by kirillcool on August 16, 2005 at 03:59 AM | Permalink | Comments (13)A spurious outburst of "undocumented secret" features of Swing keep popping up lately. The word "undocumented" seems to imply that no official Java documentation on Sun mentions them, and it's only after hours of poring over the JDK code the authors finally distilled these gems. Let's start then?
Showing licenses for your applicationPosted by kirillcool on August 13, 2005 at 08:46 AM | Permalink | Comments (11)Most of the nowadays open-source and commercial products extensively use other products. Although the "not invented here" syndrome is infective, things such as look-and-feel, layout managers, graph libraries, reporting tools etc. are taken from well-known and actively maintained third party sites. These tools come in variety of licenses, most of which require you to include the corresponding licenses along with the executable version of your own library. Here is a graphical way to organize all licenses in one user-friendly window. Here is a screenshot of license viewer for JAXB Workshop that uses ten other libraries (click screenshot to view full-scale version): The left panel shows tree with all third-party libraries (the root is your own library). Libraries can be grouped (in screenshot above, JUNG library uses three other libraries, which are grouped under it). Each tree entry shows the library name and the license abbreviation. Clicking on a tree entry shows the corresponding license in the right text pane, along with the library name, library URL and license full name above the license. The source for the license viewer can be found in the CVS repository at this link. Sample code for using the license viewer is:
LicenseDialog licenseDialog = new LicenseDialog(
ownerFrame, // your application frame
"JAXB Workshop", // library name
new LicenseDialog.LibraryDetails(
"https://jaxb-workshop.dev.java.net/", // library URL
LicenseDialog.LicenseType.BSD, // license enum
"license/bsd_license.txt")); // resource name for license text
The license viewer comes with about ten pre-defined licenses (such as Apache, BSD, LGPL etc.), which can be referenced using enum value as above. For licenses that are not pre-defined:
licenseDialog.addNewRootLibrary(
"SJSXP", // library name
new LicenseDialog.LibraryDetails(
"https://sjsxp.dev.java.net/", // library URL
"Sun BCL", // license abbreviation
"Sun Binary Code License", // license full name
LicenseDialog.LicenseType.getLicenseIcon("10"), // icon for license entry in the tree
"license/sun_bcl_license.txt")); // resource name for license text
The license viewer provides a helper function for creating standard-looking icon (getLicenseIcon used above). For defining library groups:
licenseDialog.addNewChildLibrary(
"JUNG", // parent library name
"Commons Collections", // library name
new LicenseDialog.LibraryDetails(
"http://jakarta.apache.org/commons/collections/", // library URL
LicenseDialog.LicenseType.APACHE2, // license enum
"license/apache2_license.txt")); // resource name for license text
As can be seen, you are responsible for bundling the license text itself as a resource with your application.
licenseDialog.expandAll(true);
licenseDialog.setLicenseTextAreaFont(
new Font("Tahoma", Font.PLAIN, 12));
These two functions expand all branches in the license tree and set font for the license text pane. Finally, set preferred size and location for the license viewer and show it:
Dimension dim = new Dimension(800, 600); licenseDialog.setSize(dim); licenseDialog.setPreferredSize(dim); licenseDialog.setResizable(false); licenseDialog.pack(); licenseDialog.setLocation(...); licenseDialog.setVisible(true);You can see the license viewer in action by WebStarting this link (requires JVM 5.0+) and going to Help -> View licenses. The next logical step would be to create a new project on java.net that will provide such viewer along with bundled versions of most-popular licenses. Volunteers (a slight chance to win immortal glory is implicitly guaranteed)? Reflection and dynamically changing classesPosted by kirillcool on August 10, 2005 at 08:29 AM | Permalink | Comments (0)Half the dynamic XML binding libraries use reflection to marshal Java object trees to XML and unmarshal XML back to objects. These libraries expect to work on JavaBean-compliant classes (or accompanying configuration files), and use reflection heavily both during marshaling (retrieving attributes and elements) and unmarshaling (creating elements and populating attributes). Since reflection is an expensive operation, most of the results are cached for future reuse. There are three main types of reflection operations:
One of the features of last-generation application servers is hot deploy, that allows swapping the class files without rebooting the whole application. In case of XML binders, this may mean either changes to existing get/set pairs implementation, or additional get/set pairs. Here is an example of such change, and the way it is handled in XStream: First, we generate very simple test class (during run-time) with a single integer value attribute:
Logger _logger = Logger.getLogger(Main.class.getName());
// create first version of class
String className = "TestClass";
String dir = "C:\\temp";
BufferedWriter bw1 = new BufferedWriter(new FileWriter(dir
+ File.separator + className + ".java"));
bw1.write("public class " + className + " {");
bw1.newLine();
bw1.write(" private int value;");
bw1.newLine();
bw1.write(" public int getValue() {");
bw1.newLine();
bw1.write(" return this.value;");
bw1.newLine();
bw1.write(" }");
bw1.newLine();
bw1.write(" public void setValue(int value) {");
bw1.newLine();
bw1.write(" this.value = value;");
bw1.newLine();
bw1.write(" }");
bw1.newLine();
bw1.write("}");
bw1.newLine();
bw1.close();
Here is how the generated class looks like:
public class TestClass {
private int value;
public int getValue() {
return this.value;
}
public void setValue(int value) {
this.value = value;
}
}
Now, we compile it using tools.jar:
// compile
compStatus = com.sun.tools.javac.Main.compile(new String[] { dir
+ File.separator + className + ".java" });
_logger.info("Compilation status is " + compStatus);
And load it using URLClassLoader:
// load
ClassLoader classLoader1 = new URLClassLoader(new URL[] { new File(dir)
.toURL() });
Class clazz1 = classLoader1.loadClass(className);
_logger.info("Loaded " + clazz1.getName() + " [" + clazz1.hashCode()
+ "]");
The resulting class is loaded, and its hash code is
INFO [18:04:27.480] [XMain.main] Loaded TestClass [13577344]Now, we fetch its constructor and get/set pair:
Constructor ctr1 = clazz1.getConstructor(new Class[0]);
Method getter1 = clazz1.getMethod("getValue", new Class[0]);
Method setter1 = clazz1
.getMethod("setValue", new Class[] { int.class });
Create new object, set value to 10 and check that the set succeeded:
Object obj1 = ctr1.newInstance(new Object[0]);
setter1.invoke(obj1, new Object[] { new Integer(10) });
Object val1 = getter1.invoke(obj1, new Object[0]);
_logger.info("Fetched value " + ((Integer) val1).intValue());
The result of get is, as expected, 10:
INFO [18:04:27.480] [XMain.main] Fetched value 10Now, we create a new XStream object, configure it to handle our loaded class and marshal our object to XML:
XStream xstream = new XStream();
xstream.alias("myobj", clazz1);
String xml1 = xstream.toXML(obj1);
_logger.info("First XML : " + xml1);
The result is, as expected:
INFO [18:04:27.652] [XMain.main] First XML : <myobj> <value>10</value> </myobj>Now, we unload the TestClass class (WeakReference is used to make sure that the class is unloaded):
ReferenceQueue
A few centiseconds later, the class is unloaded:
INFO [18:04:27.699] [XMain.main] Reference to the class cleanedNow, we generate a new version of the same class, this time changing the getter implementation:
BufferedWriter bw2 = new BufferedWriter(new FileWriter(dir
+ File.separator + className + ".java"));
bw2.write("public class " + className + " {");
bw2.newLine();
bw2.write(" private int value;");
bw1.newLine();
bw2.write(" public int getValue() {");
bw2.newLine();
bw2.write(" return (this.value + 10);");
bw2.newLine();
bw2.write(" }");
bw2.newLine();
bw2.write(" public void setValue(int value) {");
bw2.newLine();
bw2.write(" this.value = value;");
bw2.newLine();
bw2.write(" }");
bw2.newLine();
bw2.write("}");
bw2.newLine();
bw2.close();
New version of our data class is
public class TestClass {
private int value;
public int getValue() {
return (this.value + 10);
}
public void setValue(int value) {
this.value = value;
}
}
Compile and load it, using a new URLClassLoader:
// compile
compStatus = com.sun.tools.javac.Main.compile(new String[] { dir
+ File.separator + className + ".java" });
_logger.info("Compilation status is " + compStatus);
// load
ClassLoader classLoader2 = new URLClassLoader(new URL[] { new File(dir)
.toURL() });
// MyClassLoader classLoader2 = new MyClassLoader(Main.class
// .getClassLoader(), dir);
Class clazz2 = classLoader2.loadClass(className);
_logger.info("Loaded " + clazz2.getName() + " [" + clazz2.hashCode()
+ "]");
The new class is loaded, and its hash code is different (as expected):
INFO [18:04:28.011] [XMain.main] Loaded TestClass [26281671]Once again, we fetch the constructor and get/set pair:
Constructor ctr2 = clazz2.getConstructor(new Class[0]);
Method getter2 = clazz2.getMethod("getValue", new Class[0]);
Method setter2 = clazz2
.getMethod("setValue", new Class[] { int.class });
And test that the changes to the getter function were loaded:
Object obj2 = ctr2.newInstance(new Object[0]);
setter2.invoke(obj2, new Object[] { new Integer(10) });
Object val2 = getter2.invoke(obj2, new Object[0]);
_logger.info("Fetched value " + ((Integer) val2).intValue());
The result is, as expected, 20:
INFO [18:04:28.011] [XMain.main] Fetched value 20Now, we create a new XStream object and ask it to marshal the new object:
XStream xstream2 = new XStream();
xstream2.alias("myobj", clazz2);
String xml2 = xstream2.toXML(obj2);
_logger.info("Second XML : " + xml2);
The result is erroneous:
INFO [18:04:28.011] [XMain.main] Second XML : <myobj> <value>10</value> </myobj>In case we reuse the same XStream object, it throws exception:
// xstream.alias("myobj", clazz2);
String xml2 = xstream.toXML(obj2);
_logger.info("Second XML : " + xml2);
The exception is:
Exception in thread "main" com.thoughtworks.xstream.converters.reflection.ObjectAccessException: Could not get field class java.lang.reflect.Field.value : null at com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider. visitSerializableFields(PureJavaReflectionProvider.java:110) at com.thoughtworks.xstream.converters.reflection.ReflectionConverter. marshal(ReflectionConverter.java:44) at com.thoughtworks.xstream.core.ReferenceByXPathMarshaller. convertAnother(ReferenceByXPathMarshaller.java:36) at com.thoughtworks.xstream.core.TreeMarshaller.start(TreeMarshaller.java:46) at com.thoughtworks.xstream.core.ReferenceByXPathMarshallingStrategy. marshal(ReferenceByXPathMarshallingStrategy.java:17) at com.thoughtworks.xstream.XStream.marshal(XStream.java:461) at com.thoughtworks.xstream.XStream.marshal(XStream.java:451) at com.thoughtworks.xstream.XStream.toXML(XStream.java:432) at wr.XMain.main(XMain.java:134) Caused by: java.lang.IllegalArgumentException at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(Unknown Source) at sun.reflect.UnsafeIntegerFieldAccessorImpl.getInt(Unknown Source) at sun.reflect.UnsafeIntegerFieldAccessorImpl.get(Unknown Source) at java.lang.reflect.Field.get(Unknown Source) at com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider. visitSerializableFields(PureJavaReflectionProvider.java:108) ... 8 moreThis exception happens also when the alias call is uncommented. Few techniques can be employed here, one of them being WeakHashMap, and another not using class names as keys in reflection caches. In any case, when you use reflection, you can not assume that the cached results will always be valid. In addition, libraries such as JiBX that inject marshaling and unmarshaling code to the data classes must be used not only during the build, but each time the classes are changed and compiled at runtime. IntelliJ IDEA 5.0 and custom look-and-feelPosted by kirillcool on August 09, 2005 at 12:55 PM | Permalink | Comments (3)IntelliJ IDEA 5.0 is out, and since one of my projects is on the approved list, I seized an opportunity to start working with it. While some of the features are not for me (such as JSP 2.0, J2ME and CSS support), others come in very handy (one of my favourites is an insanely fast code inspector). One of the features (that was already present before) allows you to switch look-and-feels of the application (as it is completely written in Swing). While not as user-friendly as in JFormDesigner 2.0 (navigate to a jar file that implements the desired look-and-feel and it's loaded and referenced for all future invocations), it's not impossible. Here's how you can do it (based on my Substance look-and-feel):
For me as a look-and-feel developer, IDEA 5.0 comes very handy:
| ||
|
|