|
|
||
Kirill Grouchnikov's BlogCommunity: Java Tools ArchivesWhat's the big deal?Posted by kirillcool on February 16, 2007 at 10:05 PM | Permalink | Comments (23)I've been following Cafe au Lait for a few weeks now, and it really amuses me. Quotes like "the best Java can or should do is faithfully mimic the native user interface" and "the goal of a Java application is to fit in with other native applications, not to stand out" most certainly indicate that the writer is a strong proponent of a native look and feel, which is kind of bemusing. The Mac UI is shifting constantly, and the upcoming OS X release is supposed to feature a new UI (Illuminous?). Given the history of OS X for the past few years, and numerous native look and feels (Aqua, Graphite, Brushed Metal), it would appear that even Apple's UI designers don't consider the current state of affairs as the best. But i digress. The latest links to a well commented article on Swing / AWT against Cocoa which does have a lot of valid points, but the link itself concentrates on one of the more inaccurate and misleading quotes:
The obvious target of this quote is NetBeans Matisse which puts the UI-related code in the same file as the business logic (say, event handlers). However, that is misleading at best and fanboyish at worst.
So, what's the big deal? What's all the bickering about? That NetBeans doesn't use two separate files? Or that a major Java IDE finally provided a decent UI editor? Or that Java developers seem to treat a 5% market as what it is (a 5% market)? I don't want to reopen the entire native against cross-platform look-and-feel debate again. I just would like to understand the real difference between Interface Builder approach and Matisse approach, as long as each one of these is used as it's designed to. For some more thoughts read this and this.
Ye old faithful IDEPosted by kirillcool on January 18, 2007 at 10:03 AM | Permalink | Comments (15)My current work project involves extending an existing application and adding a few new features. All is well and good, but it's written in C++. Well, all is still well, since that has been my primary development from '99 to '02 (in Visual Studio 6). The code is written well, the original developers sit right next to me, C++ is very much like Java (thankfully they didn't use operator overloading), so what's the problem? Well, it all begins with the build configuration. Since there's no such thing as an Ant build script, you have all these in the project files (.sln and .vcproj). Now, the structure of these files changes between different Visual Studio versions, so if you want to upgrade to Visual Studio 2005, you'll have to upgrade your projects files as well. In this specific case, this is way over my head, since the build portions are very complicated and it would take too much time to port it to VS 2005. So, i'm stuck with 4-year old IDE. Now, back in the day (VS 6.0), i didn't notice it, but now, coming back from Eclipse / NetBeans / IDEA, this looks like an old relic from The Stone Age. Refactoring? Not yet. Find all uses? Not yet. Set breakpoint on field change? Just follow this sequence of 7-8 steps that involves writing down the field address and typing it later in some obscure screen. And the list goes on. Personally, i came to depend so much on key strokes in Eclipse, that coming back to an IDE that doesn't have the matching functionality is a serious shock. Now, this is obviously not a VS problem. This would be the same with much earlier versions of Eclipse, NetBeans and JBuilder. It's just that the coding doesn't really flow. If you want to add a new parameter to a method and change all the callers, you have the following options, none of which are very attractive:
With this, an extended "Thank You" goes to all Java IDE developers that continue adding new features to their IDEs. It's only when we stop using them, we can fully appreciate their advantages. You say Eclipse, I say IBMPosted by kirillcool on June 21, 2005 at 12:43 AM | Permalink | Comments (23)So, let's take a look at the list of 219 bugs fixed in the latest release candidate for 3.1 version. 159 (that's right, seventy two percent) have been fixed by IBM employees in four different countries. The list for EMF (open and closed bugs) contains 877 entries, all of them assigned to IBM mail addresses. The list for Hyades (open and closed) contains 3534 bugs, almost all of them for IBM addresses. They can babble all they want about gazillions of members on Eclipse board, but the fact still remains - it's open source (as much as Sun's projects on java.net), but it's powered exclusively by IBM. And another, more alarming thing. It is true that Eclipse is now much faster than it was a few months ago (thanks to the competition with NetBeans). It is true that on small screens (15") it has much better layout than IntelliJ. But at what price it comes?
100MB for an IDE? More than twice than the latest Mustang JDK that receives much blame for the bloat? Twice the size of IntelliJ? Twice the size of NetBeans? I don't see twice the features (although there are some nice ones that are only now being developed in other IDEs, such as CamelCase, undo refactor etc). And the last note - i guess the 3.1 version will make it just in time for the JavaOne presentations, but judging by the amount of bugs fixed (219 for RC3, 716 for RC2 and 805 for RC1) I would prefer focusing less on quantity and more on quality. Using Java compiler in your Web Start applicationPosted by kirillcool on May 27, 2005 at 05:56 AM | Permalink | Comments (6)In JAXB Workshop project, I was faced with the following problem: after the XJC generator produces a set of Java source files, I needed to compile them and load them into the running JVM in order to collect cross-reference information: The standard technique for compiling Java source files in regular standalone application is to use the tools.jar that resides under jdk/lib directory, and use com.sun.tools.javac.Main class and its compile function to do the job. Another, more sophisticated technique that doesn't require explicitly specifying the location of the tools.jar is to use java.home system property and deduce the location of tools.jar from it:
private static Method javac = null;
static {
try {
File jreHome = new File(System.getProperty("java.home"));
System.out.println("java.home = " + jreHome.getAbsolutePath());
File toolsJar = new File(jreHome.getParent(), "lib/tools.jar");
System.out.println("tools.jar = " + toolsJar.getAbsolutePath());
ClassLoader toolsJarLoader = new URLClassLoader(
new URL[] { toolsJar.toURL() });
javac = toolsJarLoader.loadClass("com.sun.tools.javac.Main")
.getMethod("compile", new Class[] { String[].class, PrintWriter.class });
hasToolsJar = true;
} catch (Exception exc) {
javac = null;
hasToolsJar = false;
}
}
In the code above lies our first problem when the code will be executed in Web Start environment - no JDK installation is needed for such environment. Moreover, all default installations of JDK 6.0 early builds (that install JRE also), point the javaws.exe to work with the JRE and not the JDK. In this case, the value of java.home will point to the JRE installation directory, that doesn't have tools.jar under lib.At this point it becomes obvious that the Java compiler must be bundled with your application. And now the bad news - the size of the tools.jar is staggering 6,590 KB! The reason - it contains a lot of tools, such as apt, javah and so on. A simple solution would be to leave only the classes necessary for javac, resulting in about 1.5 MB, but that would not sit good with the Java license, especially for commercial applications. Currently, the only fully-TCK compliant alternative (for Java 5.0 features) is the JDT compiler that comes with Eclipse 3.1M7. The relevant jar is org.eclipse.jdt.core_3.1.0.jar and its size is 3,430 KB, almost half that of tools.jar. The list of all available compiler options is available here. At this point, it appeared that all was left to do - replace the package name and the function parameters. However, the problem that laid ahead was unexpected - missing classes in the classpath. The files generated by JAXB's XJC generator, reference classes in jsr173_api.jar and jaxb-api.jar (both parts of JAXB 2.0 Early Access Reference Implementation). These files are, of course, properly referenced in the JNLP descriptor (along with the JDT compiler jar): <resources> <j2se version="1.5+" /> ... <jar href="jaxb-api.jar"/> <jar href="jsr173_api.jar"/> <jar href="org.eclipse.jdt.core_3.1.0.jar"/> ... </resources>Nonetheless, the compiler was failing with error messages indicating that the corresponding Java classes couldn't be found in the classpath. A quick look at the compiler parameters show that we can specify -cp followed by the list of jar files. The first attempt would be to simply provide the following value: -cp jaxb-api.jar;jar173_api.jarThis approach fails, because the Web Start cache (even if you manage to find its location on the remote computer) stores the file under very cryptic names. For example, on my machine the cache is under C:\Documents and Settings\Owner\Application Data\Sun\Java\Deployment\cache\6.0, and sample file name is 3595ab4e-24c448c4. The second approach attempts to retrieve the actual URL of these two jars in the current JVM. Here we are once again faced with a problem specific to Web Start - the remote jar files do not appear in the java.class.path system property. The only way to retrieve them is via manifest files. A manifest file is, in fact, the most prominent component that makes jar file different from zip file. It is located under META-INF directory, and its name is MANIFEST.MF. So, how can we obtain a list of all available jars? Very simple - ask the class loader to enumerate all META-INF/MANIFEST.MF resources:
try {
for (Enumeration<?> e = org.jvnet.jaxbw.Workspace.class
.getClassLoader().getResources("META-INF/MANIFEST.MF");
e.hasMoreElements();) {
URL url = (URL) e.nextElement();
_logger.info("Scanning " + url.getFile());
...
} catch (IOException exc) {
exc.printStackTrace();
}
Sample logger output from the above code in Web Start environment is
INFO: Scanning https://jaxb-workshop.dev.java.net/webstart/jaxb-api.jar!/META-INF/MANIFEST.MFFrom the above URL, we can obtain the URL of the jar itself (marked in red), and then give it to the compiler in the classpath: -cp https://jaxb-workshop.dev.java.net/webstart/jaxb-api.jar;https://jaxb-workshop.dev.java.net/webstart/jar173_api.jarHowever, the compiler still complains about the missing files. Obviously, the classpath should reference local files only. It becomes clear now that we must have local copies of the remote jars and provide their (local) filenames to the compiler. One way to do this is: download the relevant jar files based on their URLs and save them as temporary files. The files should be marked to be deleted on application exit. The code now becomes:
private static synchronized String getLocalClassPath(Logger _logger) {
if (isJarsLoadedLocal) {
return localJarsClassPath;
}
Set
Here, we use a static synchronized function. This function checks whether the classpath was already computed, and if so - returns the classpah immediately. Otherwise - it enumerates all available jars, and calls getLocalJarFilename function that creates a local temporary copy of the parameter jar URL (function shown below). Then, after all the jars that we were interested in have been downloaded, we create the final classpath (using File.pathSeparator). The code for getLocalJarFilename is:
private static String getLocalJarFilename(URL remoteManifestFileName,
Logger _logger) {
_logger.info("Trying to create local version of "
+ remoteManifestFileName);
// remove trailing
String urlStrManifest = remoteManifestFileName.getFile();
String urlStrJar = urlStrManifest.substring(0, urlStrManifest.length()
- MANIFEST.length() - 2);
_logger.info("Remote : " + urlStrJar);
InputStream inputStreamJar = null;
File tempJar;
FileOutputStream fosJar = null;
try {
URL urlJar = new URL(urlStrJar);
if (urlJar == null) {
_logger
.info("No remote version (probably not running in JNLP)");
return null;
}
inputStreamJar = urlJar.openStream();
String strippedName = urlStrJar;
int dotIndex = strippedName.lastIndexOf('.');
if (dotIndex >= 0) {
strippedName = strippedName.substring(0, dotIndex);
strippedName = strippedName.replace("/", File.separator);
strippedName = strippedName.replace("\\", File.separator);
int slashIndex = strippedName.lastIndexOf(File.separator);
if (slashIndex >= 0) {
strippedName = strippedName.substring(slashIndex + 1);
}
}
tempJar = File.createTempFile(strippedName, ".jar");
_logger.info("Created temporary file '" + tempJar.getAbsolutePath()
+ "'");
tempJar.deleteOnExit();
fosJar = new FileOutputStream(tempJar);
byte[] ba = new byte[1024];
int bytesWritten = 0;
while (true) {
int bytesRead = inputStreamJar.read(ba);
if (bytesRead < 0) {
break;
}
fosJar.write(ba, 0, bytesRead);
bytesWritten += bytesRead;
}
_logger.info("Written " + bytesWritten + " bytes to local file");
return tempJar.getAbsolutePath();
} catch (Exception ioe) {
System.out.println(ioe.getMessage());
ioe.printStackTrace();
} finally {
try {
if (inputStreamJar != null) {
inputStreamJar.close();
}
} catch (IOException ioe) {
}
try {
if (fosJar != null) {
fosJar.close();
}
} catch (IOException ioe) {
}
}
return null;
}
Important points - we create a temporary file using File.createTempFile (no need to guess the remote computer OS etc.), mark it with deleteOnExit() (can then reuse it as long as the application is running) and finally return its absolute path.The code for compiling a set of Java source files now becomes:
ByteArrayOutputStream baosMessages = new ByteArrayOutputStream();
ByteArrayOutputStream baosWarnings = new ByteArrayOutputStream();
long time0 = System.currentTimeMillis();
StringBuffer bigAllNames = new StringBuffer();
for (SingleClassInfo currClassInfo : allClasses) {
String currFilename = currClassInfo.getRootDirName()
+ File.separator + currClassInfo.getRelJavaFilename();
this._logger.info("Adding " + currFilename + " to compilation");
bigAllNames.append("\"" + currFilename + "\" ");
}
String warningSetting = new String("allDeprecation,"
+ "allJavadoc," + "assertIdentifier," + "charConcat,"
+ "conditionAssign," + "constructorName," + "deprecation,"
+ "emptyBlock," + "fieldHiding," + "finalBound,"
+ "finally," + "indirectStatic," + "intfNonInherited,"
+ "javadoc," + "localHiding," + "maskedCatchBlocks,"
+ "noEffectAssign," + "pkgDefaultMethod," + "serial,"
+ "semicolon," + "specialParamHiding," + "staticReceiver,"
+ "syntheticAccess," + "unqualifiedField,"
+ "unnecessaryElse," + "uselessTypeCheck," + "unsafe,"
+ "unusedArgument," + "unusedImport," + "unusedLocal,"
+ "unusedPrivate," + "unusedThrown");
// Create classpath. For compiling XJC-generated files we need two
// jar files, jaxb-api.jar and jsr173_api.jar. When Milano is run
// from the local computer, these jars should be on classpath
// (explicitly or on PATH varibable). However, when our application
// runs from Java Web Start, these jars are not available by their
// names. Here, we will try to fetch them as resources and create
// temporary files on the local machine.
String classpath = Workspace.getLocalClassPath(this._logger);
this._logger.info("Classpath is " + classpath);
// Start compilation using jdtcore
boolean compilationStatus = org.eclipse.jdt.internal.compiler.batch.Main
.compile("-1.5 -source 1.5 -warn:" + warningSetting + " "
+ classpath + " " + bigAllNames.toString(),
new PrintWriter(baosMessages), new PrintWriter(
baosWarnings));
// Parse the messages and the warnings.
long time1 = System.currentTimeMillis();
BufferedReader br = new BufferedReader(new InputStreamReader(
new ByteArrayInputStream(baosMessages.toByteArray())));
while (true) {
String str = br.readLine();
if (str == null) {
break;
}
this._logger.info("/javac/ " + str);
compilerMessages.addLast(str);
}
BufferedReader brWarnings = new BufferedReader(
new InputStreamReader(new ByteArrayInputStream(baosWarnings
.toByteArray())));
LinkedList<String> warnings = new LinkedList<String>();
while (true) {
String str = brWarnings.readLine();
if (str == null) {
break;
}
this._logger.warning("/javac/ " + str);
compilerMessages.addLast(str);
warnings.addLast(str);
}
this._logger.info((time1 - time0) + " milliseconds, "
+ (compilationStatus ? "success" : "failure"));
return compilationStatus;
AOP - a poor man's excuse for writing ugly codePosted by kirillcool on January 14, 2005 at 04:26 AM | Permalink | Comments (21)Let's take two examples that are given in any AOP language, logging and context passing. AOP takes pride of the fact that it allows "injecting" code at the beginning and at the end of any method (specified using sophisticated "regular expressions"). But does this really qualify as a logging and tracing mechanism? Not really. Any non-academic application has functions with multiple exit points (although this on itself can be called a bad programming practice) with multi-branch logics inside. Typically I would have 5-8 log points with various log levels. The important lines that i wish to log most certainly lie inside the code. Each line prints information on specific objects. Moreover, these messages must be localized using DB (or configuration XML file) and filtered based on the log level that can change at runtime (presumably after an erroneous behaviour is spotted). AspectJ most certainly does not provide any means to do this. Second, context passing. Now we have a stack of 10 functions and we need to pass additional parameter to the innermost function. Now we have two cases: there are already way too many parameters and we don't want to pass an additional one (this is called not modular in AspectJ tutorial); or we have some class that contains all the arguments and we are too lazy to change it. The second case is obvious - it costs next to nothing to put an additional member to this class without breaking the modularity. The first one is more tricky. If you have already 15-20 parameters for each function, than your coding practices really suck. If you are unwilling to add a parameter to a function that doesn't really need it, but has to pass it to another function that does, you should rethink the design of your functions. In any case, "pushing" a parameter into a code that wasn't designed to handle it is very bad design. Moreover, this forces reading multiple files in order to understand what each function does. Another examples of AspectJ from the tutorial in short:
| ||
|
|