Tuesday, November 20, 2007

Open file within a web-app - treat it as a resource

Opening a file within a web app is tricky, because you don't generally know the absolute path to the file and the relative path might not be immediately apparent.

Try loading it as a resource. A resource is some data (images, audio, text, etc) that can be accessed by class code in a way that is independent of the location of the code. The name of a resource is a '/'-separated path name that identifies the resource. -- Javadocs for ClassLoader in JDK 1.4.2.

In a web app, the path can be absolute, beginning from the WEB-INF/classes directory. If I have "myfile.txt" inside WEB-INF/classes, the following code should work ok.

BufferedReader reader = new BufferedReader(new InputStreamReader(
    getClass().getResourceAsStream("/myfile.txt")));

Monday, November 19, 2007

Print a stacktrace in your JSP

Update 4/03/2009 5:00:37 PM. Added notes about catching Throwable and not to leave this code in by mistake.

Sometimes I want to capture an error that destroys my JSP and display it right there. You can do this with JSTL and log4j as well, but this is the plain old vanilla JSP way.

<% try { %>
<p>You will see this.</p>
<% if (true) throw new IllegalStateException("Naughty!"); %>
<p>You will not see this.</p>
<%
} catch (Exception exception) {
out.println("<pre style=\"color: red\">");
exception.printStackTrace(new java.io.PrintWriter(out));
out.println("</pre>");
}
%>

The output is as per below.

You will see this.

java.lang.IllegalStateException: Naughty!
at org.apache.jsp.default_jsp._jspService(default_jsp.java:62)
at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:98)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:331)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:329)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:265)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:269)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:213)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:174)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:117)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:108)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:151)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:874)
at org.apache.coyote.http11.Http11BaseProtocol$Http11ConnectionHandler.processConnection(Http11BaseProtocol.java:665)
at org.apache.tomcat.util.net.PoolTcpEndpoint.processSocket(PoolTcpEndpoint.java:528)
at org.apache.tomcat.util.net.LeaderFollowerWorkerThread.runIt(LeaderFollowerWorkerThread.java:81)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:689)
at java.lang.Thread.run(Thread.java:595)

Warning: this is just a measure for quick diagnosis and trouble shooting during development. Never leave this stuff in your code unless you have very good reason. A user should only ever see a friendly message like "Operation failed. Please try again or contact support". They should never see stack traces; they can't do anything with that information and it is confusing (and thus un-professional). This output should go to your logs instead.

I have worked on applications before where I needed to resort to this; something else in the infrastructure or framework can be swallowing the exceptions, or it is unreasonably hard to get the logs quickly (and easier to edit JSP!).

For those really nasty errors, catching java.lang.Exception isn't enough. For example, a java.lang.NoClassDefFoundError isn't an Exception and won't be caught by this. It's a java.lang.Error (Exceptions's only sibling). To catch any Exception or Error, change the catch statement to catch (Throwable exception).

Double Warning: be doubly sure never to leave a catch Throwable in your code. In all cases it means something drastic is wrong - something that your code absolutely cannot recover from.

Tuesday, November 13, 2007

Enums in Java 5

Some example uses of Java 5's enums. Shows that enums are types which can have data and behaviour. They are instantiated and can can be imported into other classes.

This class creates an enum with some instances.

package org.rbram.tests.enumtest;
public final class FruitEnum {
  public enum Fruit {
    // Instances of the enum as a comma separated list of constructor calls.
    Apple("red"), Banana("yellow"), Grape("green");

    // Enums can have instance variables.
    private String color;
    private int count;

    // Enums can have multiple constructors.
    Fruit(final String theColor) {
      this.color = theColor;
      this.count = 0;
    }

    // Enums can have behaviour
    public String getColor() {
      return this.color;
    }

    public int getCount() {
      return this.count;
    }

    public synchronized int addFruit(final int howMany) {
      this.count += howMany;
      return this.count;
    }

    public synchronized int takeFruit(final int howMany) {
      if (howMany > this.count) {
        this.count = 0;
      } else {
        this.count -= howMany;
      }
      return count;
    }
  }
}

This class imports and tests a few aspects of the enums.

package org.rbram.tests.enumtest;
import org.rbram.tests.enumtest.FruitEnum.Fruit;
public final class FruitTest {

  public static void main(final String[] args) {
    Fruit fruit = Fruit.valueOf("Apple");
    fruit.addFruit(23);
    System.out.println("I have " + fruit.getCount() + " " + fruit
        + "s, which are all " + fruit.getColor() + " in color.");

    fruit = Fruit.valueOf("Grape");
    fruit.takeFruit(12);
    System.out.println("I have " + fruit.getCount() + " " + fruit
        + "s, which are all " + fruit.getColor() + " in color.");

    try {
      // You have to get the id right or catch the runtime exception.
      fruit = Fruit.valueOf("Durian");
    } catch (IllegalArgumentException exception) {
      System.err.println("Could not find fruit named Durian.");
    }

  }
}

Output of running the above code.

I have 23 Apples, which are all red in color.
I have 0 Grapes, which are all green in color.
Could not find fruit named Durian.

Other Resources

Monday, November 12, 2007

How to make a Jar File

Update. 15/12/2008 12:12:00 AM. Added trouble shooting section based on my first comment. Updated formatting.

Update. 18/12/2008 10:54:10 AM. Added to the trouble shooting section. Removed the Warning in the section "Run Executable JAR from Explorer (Windows)" - it was wrong!

Jar files provide a way to group (and compress) multiple Java files into one "archive" file. If you write an API made up of hundreds of Java files, it is much easier to distribute and import the API using one jar file than manage directories of Java files. Jar archives can be opened with any archive application like WinZip or WinRar (both for Windows). Rar and zip commands are usually native to Linux distro's without any downloads.

Jar files can also be executed, meaning that a complete Java application can be distributed as a single jar and run from the command line or by double clicking the file in your window manager. This tutorial shows you how.

Index


Continuation characters

In certain code snippets below, I use the continuation character "\". This enables a single command to be spread across multiple lines.

Bash continuation character (I use Bash/Cygwin/Windows). It means I can copy and paste the four lines below into a Bash terminal and have them executed as one command.

jar cf HelloWorld.jar \
  org/bram/helloworld/HelloWorld.class \
  org/bram/helloworld/SecondHelloWorld.class \
  org/bram/helloworld/ThirdHelloWorld.class

This shows the same code using the Windows-DOS continuation character. It means I can copy and paste the four lines below into a DOS command prompt and have them executed as one command.

jar cf HelloWorld.jar ^
  org/bram/helloworld/HelloWorld.class ^
  org/bram/helloworld/SecondHelloWorld.class ^
  org/bram/helloworld/ThirdHelloWorld.class

INDEX


Java Files

Gather your Java source. Here is a simple Hello World.

package org.bram.helloworld;
import javax.swing.JOptionPane;
public class HelloWorld {
  public static void main (String [] args) {
    JOptionPane.showMessageDialog(null, "Hello World!");
  }
}

A quick command line test shows it can be compiled and run ok.

javac org/bram/helloworld/HelloWorld.java
java org.bram.helloworld.HelloWorld

I have made a few copies of this file, so I have a few Java files now.

bash3.2 User rbram on host itvw1846 in dir /cygdrive/c/Temp/rbram
Mon Nov 12 09:13 AM> javac org/bram/helloworld/*.java

bash3.2 User rbram on host itvw1846 in dir /cygdrive/c/Temp/rbram
Mon Nov 12 09:13 AM> ls org/bram/helloworld/
.   HelloWorld.class  SecondHelloWorld.class  ThirdHelloWorld.class
..  HelloWorld.java   SecondHelloWorld.java   ThirdHelloWorld.java

INDEX


How To Use Jar Command

Now that we have the Java source and class files, we should look at the 'jar' command. It is a utility program that can be run from the command line (DOS prompt or bash terminal for example, depending on your OS). Here is how to create a compressed JAR file:

jar cf archive_name.jar files

Let's look at each part of that command line.

  • jar - the command to run the jar utility.
  • cf - "c" indicates that we wish to create a jar file (as opposed to extracting one, for example) and "f" indicates that we are going to specify a file name, i.e. "archive_name.jar".
    • Here is a list of the common options. I triggered the output below by issuing an invalid command to view the usage message.
    • bash3.2 User rbram on host itvw1846 in dir /cygdrive/c/Temp/rbram
        Mon Nov 12 09:02 AM> jar invalid
        Usage: jar {ctxu}[vfm0Mi] [jar-file] [manifest-file] [-C dir] files ...
        Options:
            -c  create new archive
            -t  list table of contents for archive
            -x  extract named (or all) files from archive
            -u  update existing archive
            -v  generate verbose output on standard output
            -f  specify archive file name
            -m  include manifest information from specified manifest file
            -0  store only; use no ZIP compression
            -M  do not create a manifest file for the entries
            -i  generate index information for the specified jar files
            -C  change to the specified directory and include the following file
        If any file is a directory then it is processed recursively.
        The manifest file name and the archive file name needs to be specified
        in the same order the 'm' and 'f' flags are specified.
      
        Example 1: to archive two class files into an archive called classes.jar:
               jar cvf classes.jar Foo.class Bar.class
        Example 2: use an existing manifest file 'mymanifest' and archive all the
                   files in the foo/ directory into 'classes.jar':
               jar cvfm classes.jar mymanifest -C foo/ .
      
  • archive_name.jar - name of the JAR file. This can only be included if you use the 'f' option.
  • files - names of all the files you want to put in the jar file. This could be just one name or a list of multiple names separated by space. Names can use pattern matching characters to match multiple files.

INDEX


Non Executable Examples of the Jar Command

So, I have a Java application consisting of three source files that I wish to distribute:

org/bram/helloworld/HelloWorld.java
org/bram/helloworld/SecondHelloWorld.java
org/bram/helloworld/ThirdHelloWorld.java

I also want to call my JAR file HelloWorld.jar. To make a JAR file with just HelloWorld.java:

jar cf HelloWorld.jar org/bram/helloworld/HelloWorld.java org/bram/helloworld/HelloWorld.class

This creates the following Jar

We can't run this file - it is not executable. All we have done is compress multiple Java files (a source and a class file) into a single artefact that you can distribute and include in other Java applications. For example, if someone wanted the magnificent HelloWorld functionality, they can include HelloWorld.jar in their project's classpath and use it in their own Java files.

import org.bram.helloworld.HelloWorld;

...

// Magnificent HelloWorld popup.
HelloWorld.main();

Here is how you can make a jar with all three files listed separately.

jar cf HelloWorld.jar \
  org/bram/helloworld/HelloWorld.class \
  org/bram/helloworld/SecondHelloWorld.class \
  org/bram/helloworld/ThirdHelloWorld.class

We could do the same thing using a pattern match.

jar cf HelloWorld.jar \
org/bram/helloworld/*.class

Of course, we want to include the source files too.

jar cf HelloWorld.jar \
org/bram/helloworld/*.*

It creates the following jar file.

Note that in this example, the source files are in the same directory as the class files are. This is a 'convenience' so that I have all the files in one place. It is common for publicly released API's to have two jars: one with the class files and a different jar with source files.

INDEX


Create An Executable JAR

In the screen shots above, you may have noticed the manifest files. The manifest file holds information about the jar that Java can use, such as the name of a Java class that should be run when if the jar file is executed. See Other Resources for some more detail about manifest files.

Create a text file that lists the "main" class, which references a Java class with a main method that will be run when the jar is executed. I create a text file called "manifest.txt" with the following contents.

Main-Class: org.bram.helloworld.HelloWorld

Note 1. This file MUST end with a blank line i.e. the text file I created had two lines - the line above plus a blank line.

Note 2. You must specify the fully qualified name of the class i.e. package + Java class name. You do not use the class file extension (.class) or source file extension (.java).

I can run the jar utility with a new option "m" and the name of the manifest file. This is the command I use.

jar cmf manifest.txt \
HelloWorld.jar \
org/bram/helloworld/*.java \
org/bram/helloworld/*.class

This is the result.

INDEX


Create An Executable JAR with Ant

Making jar files with a command line is easy for small projects. What happens when you need to jar an API that does have hundreds of files in different packages? The command line would start to get very large and unwieldly. You could put it into a .bat file (DOS) or .sh (*nix), but you still have the problem with the list of files getting large and hard to maintain.

A much better solution is to use an Ant build file. The advantage is that it lets you specify the contents in terms of directories, so it is much easier to maintain and repeat each time a new build is required.

The following would be saved as build.xml to the same base directory as the "org" folder is stored in.

<?xml version="1.0"?>
<project name="HelloWorld" default="buildJar" basedir=".">
  <target name="buildJar">
    <delete file="${basedir}/HelloWorld.jar" failonerror="false" />
    <jar destfile="${basedir}/HelloWorld.jar" basedir="."
        excludes="**/*.bak manifest.txt build.xml">
      <manifest>
        <attribute name="Built-By" value="${user.name}"/>
        <attribute name="Main-Class" value="org.bram.helloworld.HelloWorld"/>
      </manifest>
    </jar>
  </target>
</project>

To run this, simply run the command "ant" from the same base directory.

INDEX


Run Executable JAR from Command Line

I can run it from the command line like this.

java -jar HelloWorld.jar

Here is the result.

INDEX


Run Executable JAR from Explorer (Windows)

Tested on Windows XP If you find this is different on your version of Windows, email me at robertmarkbram AT gmail dot com and let me know.

Within Windows Explorer, you can double click on a jar file and have it automatically execute the main class. This makes jar files a suitable alternative to exe files - very handy for GUI applications distributed as jar files.

There are two caveats. Firstly, you need to have a JRE (or JDK) installed on the machine that will run the jar. Secondly, an association must be set up that tells Windows that .jar files should be run be run by "javaw.exe", which is part of any JRE or JDK installation.

Open Windows Explorer and select "Tools | Folder Options | File Types". You might see that jars are set up by default when you installed Java, as the below screen shot shows. Click on "Advanced" to check it out further.

Clicking "Advanced" shows you the "Edit File Type" dialog below. Click "Edit" on the "open" action.

This is the dialog showing the "open" action. Note the "Application used to perform action".

For me, that value is as below.

"c:\IBM\SDP70\jdk\bin\javaw.exe" -jar "%1" %*

If you don't see the mapping, click "New" in the "Folder Options" dialog. Type "jar" as the "File Extension" and press "OK". Then you can click "Advanced" on the jar association and set up the "open" action as per the screen shots above.

Double clicking on the file executes it.

INDEX


Quick Trouble Shooting Guide

  • You can open jar files with winzip to see what is inside them.
  • You can have .java and .class files in a jar.
  • You must have a .class file with a main method that is referenced in the manifest in order to create an executable jar.
  • Look for error messages - i.e. an error dialog. If you see a command prompt pop up and disappear too quickly for you to see what was in it, try running the jar from the commandline using "javaw.exe -jar" - then you will see the error message in the command window
  • Failed to load Main-Class manifest attribute. Your manifest file doesn't specify the Main-Class attribute. See the create an executable jar section.
  • java.io.IOException: "invalid header field". Did you write your manifest file in Word or WordPad? This might be caused by illegal characters being in this file: try creating and saving the file in a text editor. Found this suggested in a sun forum post.
  • I run the Jar from the command line and see the output ok, but when I double click on my jar file I see no output - no errors either, but still, no output! I am outputting text using System.out.println("") statements. The reason you can't see anything when you double click on the jar file is because System.out.println("") outputs to a console: you have no console, since you didn't launch the application from one, and a console isn't automatically created for you when you launch a file with a double click.
    If you want to see output by double clicking, you can try a JOptionPane - an easy to use GUI component. Try this: javax.swing.JOptionPane.showMessageDialog(null, "My Message");.
  • Could not find the main class. Program will exit. There are a few possibilities.
    • Check the class name listed in your manifest file.
      • Is the class spelt correctly?
      • Is the package correct?
      • You can use slashes instead of periods e.g. my/test/HelloWorld or my.test.HelloWorld will work, so this isn't an issue.
      • Have you mistakenly ended the class name with .class or .java? If so, remove it.
    • Check your Jar File Types setting (for Windows).
      • Have you mistakenly left out some crucial spaces in the arguments list? Make sure you have spaces as per the red highlighted spaces in the following "C:\Program Files\Java\j2re1.4.2_05\bin\javaw.exe" -jar "%1" %*
  • XXX is not recognized as an internal or external command, operable program or batch file. In short, Java's bin dir isn't on your PATh. See my other blog post on this issue.
    • Are you using JDK 1.4 or lower with spaces in the path? E.g. C:\Program Files\Java\j2re1.4.2_05\bin\javaw.exe might need to be shortened using the DOS short form to this instead: "C:\Progra~1\Java\j2re1.4.2_05\bin\javaw.exe

INDEX


Other Resources

INDEX


DOM elements case sensitive in FireFox, not IE

I like a robust application, and case (in)sensitivity is often a good candidate, i.e. where possible, let your users type Answer, answer, ANSWER or ANswer. As long as you know the answer, does it matter what case they use?

Sometimes it can get in the way. A couple of times I have stumbled on this one. Here is some Ajax style Javascript I was using in a page today. It worked in IE, but not in FireFox.

function updatePage() {
  if (request.readystate != 4) {
    return;
  }
  if (request.status != 200) {
    alert("Error with request.status [" + request.status + "].");
  }
  var customerAddress = request.responsetext;
  document.getElementById("address").value = customerAddress;
}

It was my sloppy typing of course, but I found it to be a hard mistake to track down, and annoying because I thought the code was working fine until I opened it in FireFox.

When I used readystate instead of readyState in FireFox, the function always returned.

When I used responsetext instead of responseText in FireFox, I would always see "undefined" in my address field.

Ultimately, this is one instance where I do wish IE was case insensitive, so it would be a lot harder to write code that works in one place and not the other. (Of course, you might argue that FireFox should be case insensitive... I won't, but you might. I like my scripting to be a little more precise.)

HTML DOCTYPE is important in IE

I put the following in a HTML page and display it in IE 6.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<style type="text/css" >
BODY {
 color: darkred;
 font-family: sans-serif;
 margin: 2em;
 padding: 1em;
 border: double darkred;
}
</style>
</head>
<body>
<p>Text</p>
<p>Text</p>
</body>
</html>

Here is what it looks like.

Now, here is what it looks like if you leave out or screw up the DOCTYPE:

There is no big difference in display with FireFox - the border seems to be displayed the same either way. But in IE, the CSS for the border is only correctly displayed with the DOCTYPE in place. The other style rules are still obeyed i.e. the text is still sans-serif, either way.

Sunday, November 11, 2007

Ajax Callback function not getting called

In a HTML page I was using a global Javascript request object.

<script type="text/javascript">
var request = null;
try {
  request = new XMLHttpRequest();
} catch (trymicrosoft) {
  try {
    request = new ActiveXObject("Msxml2.XMLHTTP");
  } catch (othermicrosoft) {
    try {
      request = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (failed) {
      request = null;
    }
  }
}
...
</script>

The first time I executed my Ajax Javascript to issue a request to the server, the callback function worked ok and callback function was called. Each subsequent request hit the server ok, but the callback function wasn't called again. I didn't see any Javascript errors either. Turned out I had my onreadystatechange and open calls in the wrong order. This is how they were:

request.onreadystatechange = updatePage;
request.open("GET", url, true);
request.send(null);

When I made the following change, it worked.

request.open("GET", url, true);
request.onreadystatechange = updatePage;
request.send(null);

Another fix was when I had the code to make a request inside a single function and being called before the three request lines, like below. Obviously, this was just hiding the problem though.

<script type="text/javascript">
function createRequest() {
  try {
    request = new XMLHttpRequest();
  } catch (trymicrosoft) {
    try {
      request = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (othermicrosoft) {
      try {
        request = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (failed) {
        request = null;
      }
    }
  }

  if (request == null) {
    alert("Error creating request object!");
  }
}

function doAjaxyThing() {
  var url = "...";
  createRequest();
  request.onreadystatechange = updatePage;
  request.open("GET", url, true);
  request.send(null);
}
</script>

Tuesday, November 06, 2007

Firebug for debugging Javascrpt - turn it off in Production

The Firebug and Web Developer plugins for Firefox are simply awe-inspiring. Using these two, I was able to show my page as it should look when printed (it had separate print media css) - Web Developer - and then use Firebug to dynamically update the CSS until I was happy with it.

Firebug lets you examine DOM, HTML, Scripts and Net statistics. The Developer Toolbar lets you take apart and examine your page in various funky ways. There is a lot in common between Firebug and the Developer Toolbar.

On IE, MS Developer Toolbar gives you some of these capabilities too, and I use it a lot.

One of the most useful features of Firebug is the Console API. It gives a small collection of files you put into your web app, and then you can log messages from Javascript - so much more powerful that using alert() calls or the status bar.

You can print out logging messages:

console.log("message");
console.debug("message");
console.warn("message");
console.error("message");

And there are more complicated uses like:

console.dir(object);

This prints an interactive listing of all properties of the object. This looks identical to the view that you would see in the DOM tab (in Firebug).

Or how about:

console.dirxml(node)

This prints the XML source tree of an HTML or XML element. This looks identical to the view that you would see in the HTML tab. You can click on any node to inspect it in the HTML tab (in Firebug).

Press F12 to show the console. Once you see the console, you can also enter any of the above commands in the command line shown.

You would generally use Firebug for debugging during some development stage and would remove the various console statements before migrating forwards. It is easy to forget this though, and find you have let code go into Production with a few console statements left in. I use the following JSP to import Firebug. This uses firebug.js if I am doing local development - i.e. accessing the page using "localhost". It uses firebugx.js for other environments - i.e. when the environment is accessed through proper host name - which provides versions of all calls that do nothing.

<% if (request.getServerName().equals("localhost")) { %>
  <script type="text/javascript" src="<%=request.getContextPath()%>/include/firebug/firebug.js"></script>
<% } else { %>
  <script type="text/javascript" src="<%=request.getContextPath()%>/include/firebug/firebugx.js"></script>
<% } %>

IE 6 caching my Ajax RPCs

I am writing some Ajax (GET) calls and Firefox is fine - each call returns the expected result. But under IE 6 it is returning cached results.

Turn off cache

I can turn caching off and this fixes it. Tools | Internet Options | General tab | Temporary Internet Files: click Settings | "Check for newer versions of stored pages": click "Every visit to the page".

But that's not nice - I don't want to turn caching off for all of my pages, and I don't want to ask users to turn their cache off either.

Use POST!

Yet if you read the HTTP specification, you'll learn that any resource got using GET method is due to be cached by any agent on the chain (browser, proxy, even web server); whereas if got using the POST method, the query will be resubmitted and reprocessed each and every time. - Ajax IE Caching Issue

Make your URLs unique

Make Your Remote Procedure Calls Have an Unprecedented URL  - from HowToAdvice.com.

If you have to use GET, add a new parameter to your URL to make it unique each time:

var url = "http://www.yourdomain.com/?id=47" + "&ms=" + new Date().getTime();

Personally, I changed to POST.

BTW - I found this problem while going through the examples in Head Rush Ajax, a very cool way to intro to this subject. After making this post, I flipped the page to page 59 and found they knew about this issue and deal with in Chapter 2. The say to add the dummy new Date().getTime() parameter. :)

Tuesday, August 21, 2007

Operation Aborted - dang Javascript!

There I was, innocently coding away at my Portal App in RAD 7, Portal 5.1, when suddenly my app refused to load in the browser any more.

Click "OK" on this dialog and I go straight to "The page cannot be displayed".

No error messages in the logs. Could this be a Javascript issue? Of course, I didn't get around to asking myself this question until I had uninstalled my EAR file twice, re-booted and re-created the server. *sigh*

I noticed that when the above dialog shows, IE 6.0 was also showing the exclamation mark icon in the bottom left hand corder indicating a Javascript error, but I couldn't get to see the error because it would divert to "The page cannot be displayed". So, I turned on rude Javascript notifications (I.E. 6.0): Tools > Internet Options > Advanced > Display a notification about every script error.

When I re-loaded the page, I saw a Javascript error alert first complaining about a missing semi-colon.

After that one, I got the "Operation Aborted" dialog. When I investigated I found the results of some very sloppy c&p on my behalf. The last line in the code below begins with a "+" ... syntax error.

if (theField) {
  theField.focus();
}

+ theField.name + " value: [" + theField.value

The point is that "Operation Aborted" should mean a really bad client side (Javascript) error rather than a really bad server side error.

Friday, August 17, 2007

com.ibm.etools.archive.exception.NoModuleFileException: A file does not exist for module element having uri:

Portal App in RAD 7.

I created an IBM Portlet with Struts portlet application for Portal 5.1. I used the wizard to give me a sample portlet.

Off the bat it worked. But it used WebContent, rather than web.

I then went into org.eclipse.wst.common.component and made two changes to give me:

<?xml version="1.0" encoding="UTF-8"?>
<project-modules id="moduleCoreId" project-version="1.5.0">
    <wb-module deploy-name="myApp">
        <wb-resource deploy-path="/" source-path="/web"/>
        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src"/>
        <property name="java-output-path" value="web/WEB-INF/classes"/>
        <property name="context-root" value="/myApp"/>
    </wb-module>
</project-modules>

I also went into Project Properties > Java Build Path > Source and changed default output folder to "myApp/web/WEB-INF/classes". Then, in Package Explorer I refactored WebContent and renamed it "web". I re-built the application and it correctly populated the "web/WEB-INF/classes" directory with the compile java classes.

Then I attempted to run the app and found this in my logs:

[17/08/07 12:09:42:626 EST] 6aa0f8d4 ApplicationMg E WSVR0100W: An error occurred initializing, myAppEAR
com.ibm.etools.archive.exception.NoModuleFileException: A file does not exist for module element having uri: eJobSucks.war
 at com.ibm.etools.commonarchive.impl.ModuleRefImpl.checkType(ModuleRefImpl.java:715)
 at com.ibm.etools.commonarchive.impl.ModuleRefImpl.initModuleFileFromEAR(ModuleRefImpl.java:270)
 at com.ibm.etools.commonarchive.impl.ModuleRefImpl.getModuleFile(ModuleRefImpl.java(Compiled Code))
 at com.ibm.ws.runtime.component.DeployedModuleImpl.isOpen(DeployedModuleImpl.java:111)
 at com.ibm.ws.runtime.component.DeployedModuleImpl.close(DeployedModuleImpl.java:152)
 at com.ibm.ws.runtime.component.DeployedModuleImpl.initialize(DeployedModuleImpl.java:293)
 at com.ibm.ws.runtime.component.DeployedApplicationImpl.initializeModule(DeployedApplicationImpl.java:765)
 at com.ibm.ws.runtime.component.DeployedApplicationImpl.initialize(DeployedApplicationImpl.java:427)
 at com.ibm.ws.runtime.component.ApplicationMgrImpl.initializeApplication(ApplicationMgrImpl.java(Compiled Code))
 at com.ibm.ws.runtime.component.ApplicationMgrImpl.startApplication(ApplicationMgrImpl.java:559)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java(Compiled Code))
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java(Compiled Code))
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java(Compiled Code))
 at java.lang.reflect.Method.invoke(Method.java(Compiled Code))
 at com.tivoli.jmx.modelmbean.MMBInvoker.invoke(MMBInvoker.java:46)
 at com.tivoli.jmx.modelmbean.MMBInvoker.invokeOperation(MMBInvoker.java:115)
 at com.tivoli.jmx.modelmbean.DynamicModelMBeanSupport.invoke(DynamicModelMBeanSupport.java:409)
 at javax.management.modelmbean.RequiredModelMBean.invoke(RequiredModelMBean.java:323)
 at com.tivoli.jmx.GenericMBeanSupport.invoke(GenericMBeanSupport.java:178)
 at com.tivoli.jmx.MBeanAccess.invoke(MBeanAccess.java:113)
 at com.tivoli.jmx.MBeanServerImpl.invoke(MBeanServerImpl.java:290)
 at com.ibm.ws.management.AdminServiceImpl.invoke(AdminServiceImpl.java:659)
 at com.ibm.ws.management.connector.AdminServiceDelegator.invoke(AdminServiceDelegator.java:130)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java(Compiled Code))
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java(Compiled Code))
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java(Compiled Code))
 at java.lang.reflect.Method.invoke(Method.java(Compiled Code))
 at com.ibm.ws.management.connector.soap.SOAPConnector.invoke(SOAPConnector.java:320)
 at com.ibm.ws.management.connector.soap.SOAPConnector.service(SOAPConnector.java:192)
 at com.ibm.ws.management.connector.soap.SOAPConnection.handleRequest(SOAPConnection.java:55)
 at com.ibm.ws.http.HttpConnection.readAndHandleRequest(HttpConnection.java:624)
 at com.ibm.ws.http.HttpConnection.run(HttpConnection.java:448)
 at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:912)

In the application.xml of my EAR file, this was the context root:

<context-root>.myApp</context-root>

Went to Project Properties > Web Project Settings. Changed ".myApp" to "/myApp". Go back into application.xml, Module tab. Click on the module for myApp, press "Refresh" on the context root.

At the same time I couldn't open web.xml or portlet.xml - web.xml would go back to an xml editor and portlet.xml simply wouldn't open. The project wasn't considered to be a portlet application anymore. I restarted RAD 7 and then I could open both files in their visual editors once more.

The app started up ok after that.

java.io.IOException: Cannot allocate memory

The machine is out of memory.. a Linux top command shows the CPUs are maxing out. Re-boot required.

java.io.IOException: Cannot allocate memory
   at java.lang.UNIXProcess.forkAndExec(Native Method)
   at java.lang.UNIXProcess.(UNIXProcess.java:157)
   at java.lang.ProcHelper.run(ProcHelper.java:60)

Tuesday, July 10, 2007

RAD 7 - install Merant Version Manager 8.0.2.3 plugin

At my work place, we are using Merant Version Manager 8.0.2.3 (Build 662) with RAD7.

Merant Version Manager standalone app works ok, but the plugin installer doesn't know how to find a valid Eclipse install any more, due to the way RAD packages Eclipse.

Instead, create an "Extension Loction" within RAD from which it will install the plugin for us. An extension location must be a folder named "eclipse" containing a special marker file called ".eclipseextension".

  1. Go to the following Merant directory, C:\Program Files\Merant\vm\devint\eclipse3.0\eclipse\plugins and copy the file .eclipseextension to C:\Program Files\Merant\vm\devint\eclipse3.0\eclipse.
  2. In RAD 7 and select Help > Software Updates > Manage Configuration.
  3. Right click on the root node, Rational Application Developer and select Add > Extension Location.
  4. Enter C:\Program Files\Merant\vm\devint\eclipse3.0\eclipse.
  5. RAD will ask you to re-start. Let it do that and when RAD comes back you should have Merant on your menu.

If you get an SCC_E_INITIALIZEFAILED error when you open RAD (or the Merant menu item), go to my RAD 7 - Merant Version Manager: SCC_E_INITIALIZEFAILED blog post.

Monday, July 09, 2007

DB2 - Cryptic error messages begone!

Found this on the blog of my friend Lars. I haven't got enough fingers to count the number of times I needed something like this, so I am putting on my own blog as well. :)

A typical cryptic DB2 error message contains something like the following.

Assignment of a NULL value to a NOT NULL column "TBSPACEID=3, TABLEID=14, COLNO=1" is not allowed.

Here is some SQL you can run to determine which table and column such an error is referring to:

SELECT tabschema, tabname, colname
FROM syscat.columns
WHERE colno = XXXX
AND ( tabschema, tabname )
IN (SELECT tabschema, tabname
   FROM syscat.tables
   WHERE tbspaceid = yyyy
   AND tableid = ZZZZ )

Replace XXXX with the column number, YYYY with the table space id and ZZZZ with the table id.

Wednesday, July 04, 2007

RAD 7 - Merant Version Manager: SCC_E_INITIALIZEFAILED

I recently installed RAD7 on a freshly formatted machine. Merant Version Manager 8.0.2.3 was pre-installed on the machine (company image).

When I opened RAD I got some error dialogs.. When I tried to open Merant > Open from Merant Team Provider. I got them again:

Merant SCCJ dialog.
Incorrect SCC provider version installed.

Followed by SCCJ dialog.
Failed to load SCC provider.

Followed by Merant Team Interface dialog.
Failed to Initialize Merant Team Provider - Error Code:
SCC_E_INITIALIZEFAILED

In the workspace/.metadata/.log I find the following error.

!ENTRY com.merant.team.pvcs 4 4 2007-07-04 21:29:58.167
!MESSAGE Failed to Initialize Merant Team Provider - Error Code: SCC_E_INITIALIZEFAILED
!STACK 0
merant.interfaces.scc.SccException: Failed to Initialize SCC
       at merant.interfaces.scc.SccInstance.Initialize(Unknown Source)
       at com.merant.team.pvcs.scc.PvcsSccTeamProvider.GetFromScc(Unknown Source)
       at com.merant.team.pvcs.ui.scc.actions.GetFromSccAction.run(Unknown Source)
       at org.eclipse.ui.internal.PluginAction.runWithEvent(Unknown Source)
       at org.eclipse.ui.internal.WWinPluginAction.runWithEvent(Unknown Source)
       at org.eclipse.jface.action.ActionContributionItem.handleWidgetSelection(Unknown Source)
       at org.eclipse.jface.action.ActionContributionItem.access$2(Unknown Source)
       at org.eclipse.jface.action.ActionContributionItem$5.handleEvent(Unknown Source)
       at org.eclipse.swt.widgets.EventTable.sendEvent(Unknown Source)
       at org.eclipse.swt.widgets.Widget.sendEvent(Unknown Source)
       at org.eclipse.swt.widgets.Display.runDeferredEvents(Unknown Source)
       at org.eclipse.swt.widgets.Display.readAndDispatch(Unknown Source)
       at org.eclipse.ui.internal.Workbench.runEventLoop(Unknown Source)
       at org.eclipse.ui.internal.Workbench.runUI(Unknown Source)
       at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Unknown Source)
       at org.eclipse.ui.PlatformUI.createAndRunWorkbench(Unknown Source)
       at org.eclipse.ui.internal.ide.IDEApplication.run(Unknown Source)
       at org.eclipse.core.internal.runtime.PlatformActivator$1.run(Unknown Source)
       at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(Unknown Source)
       at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(Unknown Source)
       at org.eclipse.core.runtime.adaptor.EclipseStarter.run(Unknown Source)
       at org.eclipse.core.runtime.adaptor.EclipseStarter.run(Unknown Source)
       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
       at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
       at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
       at java.lang.reflect.Method.invoke(Unknown Source)
       at org.eclipse.core.launcher.Main.invokeFramework(Unknown Source)
       at org.eclipse.core.launcher.Main.basicRun(Unknown Source)
       at org.eclipse.core.launcher.Main.run(Unknown Source)
       at org.eclipse.core.launcher.Main.main(Unknown Source)

The fix was supplied by Rob Noah on the Serena Message Boards. The fix is to un-register and re-register some *.dll and *.exe files

Save the following to two batch files within the devint\bin directory, which for me is C:\Program Files\Merant\vm\devint\bin.

Save this one as unReg_Devint.bat

REM save as unReg_Devint.bat
regsvr32 /u ActivityMonitor.dll
regsvr32 /u compcli.dll
regsvr32 /u COMSession.dll
regsvr32 /u DialogIntegration.dll
regsvr32 /u IFC_COM.dll
regsvr32 /u IFC_GUI.dll
regsvr32 /u IFC_SCC.dll
regsvr32 /u IFC_Services.dll
regsvr32 /u PvcsVmVCP.dll
regsvr32 /u SccSession.dll
pclioutproc.exe /unregserver
ActivityMonitorEXE.exe /unregister
pause;

Save this one as Reg_Devint.bat

REM save as Reg_Devint.bat
regsvr32 "C:\Program Files\Merant\vm\devint\bin\ActivityMonitor.dll"
regsvr32 "C:\Program Files\Merant\vm\devint\bin\compcli.dll"
regsvr32 "C:\Program Files\Merant\vm\devint\bin\COMSession.dll"
regsvr32 "C:\Program Files\Merant\vm\devint\bin\DialogIntegration.dll"
regsvr32 "C:\Program Files\Merant\vm\devint\bin\IFC_COM.dll"
regsvr32 "C:\Program Files\Merant\vm\devint\bin\IFC_GUI.dll"
regsvr32 "C:\Program Files\Merant\vm\devint\bin\IFC_SCC.dll"
regsvr32 "C:\Program Files\Merant\vm\devint\bin\IFC_Services.dll"
regsvr32 "C:\Program Files\Merant\vm\devint\bin\PvcsVmVCP.dll"
regsvr32 "C:\Program Files\Merant\vm\devint\bin\SccSession.dll"
"C:\Program Files\Merant\vm\devint\bin\pclioutproc.exe" /regserver
"C:\Program Files\Merant\vm\devint\bin\ActivityMonitorEXE.exe" /register
pause

Close all instances of RAD/Eclipse and Merant. Then run unReg_Devint.bat. Then Reg_Devint.bat. This fixed my problem.

Eclipse: project specific checkstyle configuration

Updates:
- 12th July, 2007 - use "Project Relative Configuration", don't use absolute paths.
- 21st March, 2008 - Modified JavadocMethod and RedundantThrows checks in checkstyleRules.xml (on box.net) as per this note.
- 24th May, 2009 - Modified linked checkstyleRules.xml (on box.net): a) sorted modules by name; b) added modifications I referred to in my first comment below.
- 28th February, 2010 - re-wrote post to use generated Checkstyle.xml and extra detail about customisation.
- 17th March, 2013 - Checkstyle Configuration now also in this pastebin: http://pastebin.com/hz77H62W

This is how I set up project specific Checkstyle configuration as files that you check in to source control so that every developer who works on the project will automatically use the same Checkstyle rules. This post assumes your team is using Eclipse (3 or greater) and has the Checkstyle plugin installed. Two files are involved: [projectRoot]/.checkstyle and checkstyle.xml. Eclipse projects with Checkstyle enabled will use a file called .checkstyle (in the project root dir) to record Eclipse specific checkstyle configuration settings and I always save the Checkstyle XML rules to [projectRoot]/tools/ide/checkstyle/checkstyle.xml..

Create a Checkstyle configuration for your project in the following way.

  1. Remove previous configurations and file sets.
    1. Select Project | Properties | Checkstyle | Main tab.
    2. If "Checkstyle active for this project" is not ticked, skip this step (Remove previous configurations...).
    3. Make sure "Use simple configuration" is not ticked.
    4. Un-tick "Enabled" and click Remove for any File Set entries in the top section (labeled "Advanced - configure file sets ...").
    5. Switch to the "Local Check Configurations" tab.
    6. Remove any "Check Configuration" entries you see there.
    7. Press "OK" to exit Project Properties.
  2. Generate the checkstyle XML rules file.
    1. In your project, make a directory like tools\ide\checkstyle or whatever makes sense to you.
    2. In Eclipse select Windows | Preferences | Checkstyle | select "Sun Checks (Eclipse" under "Global Check Configurations" | click Export | export to checkstyle.xml in the tools\ide\checkstyle directory (name and location can be anything, as long as it's in the project somewhere and has a name that makes sense).
  3. Set checkstyle.xml as your project local checkstyle configuration.
    1. Select Project | Properties | Checkstyle | Local Check Configurations.
    2. Press "New" and you should see the "Check Configuration Properties" dialog.
    3. Under "Type", select "Project Relative Configuration".
    4. Enter "Name" as "Local PROJECT X Checkstyle Configuration".
    5. Click "Browse" and navigate to your project's checkstyle xml file.
    6. Press "OK".
    7. Switch to the Main tab.
    8. If "Checkstyle active for this project" is not ticked, tick it.
    9. If "Use simple configuration" is not ticked, tick it. (See note on custom file sets below for more detail on this.
    10. In the drop down, select the "Local PROJECT X Checkstyle Configuration" you created earlier.
    11. Press "OK".
    12. Press "OK" (again).
    13. In the "Rebuild suggested" dialog, press "Yes".

Custom file sets. It is possible to use different Checkstyle rules (a different XML file) for different sets of Java files. For example, maybe you want to use more relaxed Checkstyle rules for your test cases (pesky Magic Numbers!). This is not hard to set up but it is harder to manage multiple Checkstyle Configurations than just one when things change. To use custom file sets, see below.

  1. Make a copy of checkstyle.xml for each file set you intend to use. Edit each XML as appropriate to apply the rules you want (see the note on customising checkstyle rules below.
  2. Create a Checkstyle Configuration for each file set (Project | Properties | Checkstyle | Local Check Configurations | New ...).
  3. Select Project | Properties | Checkstyle | Main.
  4. If "Checkstyle active for this project" is not ticked, tick it.
  5. If "Use simple configuration" is ticked, un-tick it.
  6. For each file set, press "Add" and fill out the "Checkstyle File Set Editor" dialog.
  7. Under "File Set Name" enter "PROJECT X Checkstyle File Set - [main, test, whatever]".
  8. Under "Check Configuration", select the Checkstyle Configuration you created earlier for this specific file set.
  9. Modify the regular expression patterns to define what files this Checkstyle Configuration should apply to. There is content assist to help building the regular expressions.

Personally, I have never had a real need for multiple Checkstyle configurations. I have just one configuration and use SuppressionCommentFilter to turn off Checkstyle in annoying chunks of test code (// CHECKSTYLE:OFF ... // CHECKSTYLE:ON.

Customising checkstyle rules. You can modify the rules in two ways: through Eclipse or by editing the XML file.

To edit them through Eclipse, select Project | Properties | Checkstyle | Local Check Configurations | click the one to edit | click Configure. On the left side you see categories of Checkstyle rules (Annotations, Javadoc Comments etc). Click on a category and you see over to the right side which individual rules for that category you are currently using. Tick or un-tick rules to use/ignore them. Click "Open" on any rule to modify the selected rule (there is a lot of room for customisation). Notice the icons on the left have a green tick if they are being used. Sometimes you don't see the item (ticked or not) on the right hand side. Click on it on the left had side and then click Add to use it (which also opens the customisation dialog which you have to accept). You see helpful notes against each item in the Description field, but sometimes it can be tricky working out which rule is the one you are after.

Whatever you do, don't select all on the left side and then click "Add". Eclipse makes you respond to every single customisation dialog for every single checkstyle rule, and I do believe there is an infinite number of them. I had to kill Eclipse and learn my lesson.

To edit them through the XML file, you need to... erm, edit the XML file. Refer to the Checkstyle Documentation for assistance. Each page has helpful (though at times sparse) examples.

As long as the plugin is installed, checkstyle will now work "out of the box" for any new developers.

Below is an example .checkstyle (xml) created for a project using the above process.

<?xml version="1.0" encoding="UTF-8"?>
<fileset-config file-format-version="1.2.0" simple-config="true">
    <local-check-config name="Local PROJECT X Checkstyle Configuration" location="tools/checkstyle/checkstyleRules.xml" type="project" description="">
        <additional-data name="protect-config-file" value="false"/>
    </local-check-config>
    <fileset name="all" enabled="true" check-config-name="Local PROJECT X Checkstyle Configuration" local="true">
        <file-match-pattern match-pattern="." include-pattern="true"/>
    </fileset>
</fileset-config>

You can also look at a sample Checkstyle xml file, checkstyleRules.xml (on box.net). Warning: this is valid under Checkstyle 4, but not 5 or later, where some schema changes were made - though most of the XML for individual rules should still be valid.

Eclipse: Shortcuts

I love my keyboard shortcuts and I actively seek out ways to use the mouse less. I find the keyboard to be more efficient than a mouse for so many tasks!

This article is about some of the very cool short-cuts that I use most, plus how you can make your own and export them for future use.

13/11/2007 12:31:46 PM Updated layout of this tutorial.

Index

  1. Open Files in the Workspace
  2. Views and Actions
  3. Navigate or Search within the Workspace
  4. Navigate between Editors, Views, Perspectives
  5. Show me the shortcuts
  6. Save your shortcuts

Open Files in the Workspace

Control+Shift+T to open up any type loaded by your projects. You can use * and ? as wild-cards here.

The same can be done for resources with Control+Shift+R. Resources is the superset of files i.e. you can find Java files through this shortcut too - by typing *.java for example.

Some perspectives don't have have this shortcut enabled. To enable it, go to Window > Customize Perspective > Commands tab. In the "Available command groups" pane, scroll down to "Resource Navigation" and check its box.

Index


Views and Actions

Alt+Q,Q brings up the "Show View" view, that allows you select any Eclipse view - and use * or ? to wild-card for their names.

This is the Alt+Shift+Q menu. By default It contains short-cuts to various views. Use the up/down keys to go through the options or press the final key in the key combinations listed.

This is the Alt+Shift+X menu. It contains short-cuts to various actions and works in the same way as the menu above.

Index


Navigate or Search within the Workspace

Press F3 while your cursor is on a variable, type or method reference and you will open up the declaration of the variable, type or method. If you use it to click on a reference to code held in an external Jar file, you might need to attach the source (if you have it) for this to work.

Perhaps the single most useful tool for jumping around a class or just examining what is in a class is the Quick Outline view, Control+O.

Type in the beginning of a method, again using ? and * wild cards, and the list shortens. This is the fastest way to move between methods in a large class. Control+O, type enough to narrow the methods down, arrow down and press enter. Much faster than the scrolling through a class or even scrolling through the outline view itself.

There are, of course, plenty of short-cuts I use that don't involve bringing up a pop-up of some sort.

Use Alt+Left and Alt+Right to navigate backward and forward respectively through your code "navigation" history. Control+Q visits the last edit location.

Control+Alt+G will do a file search on whatever token your cursor is within. Warning: by default, this will do a search through all files in your workspace so it might take a bit of time. Alternatively, select and copy that token, press Control+H (for the search dialog), select the tab for whatever search you want, fill it out, press enter and wait for those results. Most likely it will get your results faster, but requires a few more mouse and/or keyboard actions!

Control+Alt+H is one of my favourites: it will show the call hierarchy for whatever method your cursor happens to be within. This brings up the Call Hierarchy view and shows each chain of parseable method calls that reaches the current method: this is the caller hierarchy.

Flip it around by pressing a button in the Call Hierarchy view and you show the callee hierarchy - the methods called from the current method.

Unfortunately, this view won't help you identify methods called from within JSPs. This is the same for any other view that relies on being able to parse trees of Java elements.

Press F4 within a class to show the Hierarchy view, which shows an outline for the current class plus a tree of subclasses for the class. Again, you can flip it to show the tree of supertypes by pressing a button in the view.

Index


Navigate between Editors, Views, Perspectives

Control+F6 and Control+Shift+F6 to switch between all the editors you have open in a workspace. Personally I don't like having to use two hands for this, so I set Control+Tab and Control+Shift+Tab to this - see Show me the shortcuts!.

You can use a split screen function to display multiple editors at once. Do this by opening multiple files at once and clicking and dragging the tab of an editor to the left/right/tob/bottom of the editor view. You can switch between views in a particular partition using Control+E.

Press Control+Shift+E to bring up the "Switch to Editor" view. It has some nice functionality to let you manage editors you have open. But to be honest, if you need it, you are probably working on too many files at once!

Control+F7 and Control+Shift+F7 switches between views. I like this one enough that I changed it Control+~ and Control+Shift+~ - see Show me the shortcuts!

Control+F8 and Control+Shift+F8 switches between perspectives.

Index


Show me the shortcuts!

Sourceforge has a set of cheat sheets that tell you the default shortcuts for your version of Eclipse.

Check out your own shortcuts! Window > Preferences > General > Keys.

Go to the Modify tab to set your own shortcuts. I use this a lot.

In particular I set up the Shift+Alt+Q,X short cut to add all of the views that I switch between regularly. For example, to set a shortcut for the console, select the view as shown to the left. Put the cursor into the Key Sequence Name field, press Shift+Alt+Q, then C. Press Add to set the shortcut. Now, Shift+Alt+Q,C will open the Console View.

Index


Save your shortcuts

If you use multiple workspaces or use Eclipse on multiple machines and make lots of customizations to your shortcuts - or perspectives - you will know how hard it is to set up a workspace over and over. Thankfully, you can export all of these preferences and import them later. Go to File > Export > Preferences. Select "Export All" and fill out the destination path in the "To preference file" field. Click "Finish" to output a .epf file. Import these with File > Import > Preferences.

Index

Friday, June 29, 2007

Cygwin bash script: open windows explorer anywhere

Here is my take on this job. It is a small script to open Windows Explorer from any path I give it, or the current directory if I don't specify a path.

Updates.
7/07/2009 1:09:35 PM. Updated handling of paths according to Igor Mikushkin's suggestion in the comments - thanks Igor!

Purpose. Open Windows Explorer to any path from a Cygwin bash shell.

Input. Alternative path if current path is not to be used.

Output. Windows Explorer, opened to a given path.

Code.

#!/bin/bash
# I have called this script "windows" in my bin dir.
usage ()
{
 echo "Open Windows Explorer"
 echo "Usage: $0 [-help] [path]"
 echo "          [path]: folder at which to open Windows Explorer, will"
 echo "              default to current dir if not supplied."
 echo "          [-help] Display help (this message)."
}


location=.
case "$1" in
 ""                 ) location=.;;
 "-help"            ) usage; exit 0;;
 *                  ) location="${1}";;
esac

if [ -e "$location" ]
then
   WIN_PATH=`cygpath -w -a "${location}"`
   cmd /C start "" "$WIN_PATH"
else
 echo ${location} does not exist!
 exit 2
fi

One further advantage to this script is when I use it in conjunction with my script to search/list/open files, which I have named "u" in my bin directory. Using these two, I can open a Word doc, for example, with code :

u -e windows someWordDoc.doc

It will search for the file, so I don't need its path. It will then get the Windows OS to handle opening the file.

I could also set up an alias to word directly:

alias word="/cygdrive/b/Program\ Files/Microsoft\ Office/Office/winword.exe"

In this case I could use the following to open the word doc:

word /path/to/someWordDoc.doc

The problem I found with this is that I kept getting an "install" action occurring each time I used word this way - plus, I need to know the path each time. Thus, I found it less painful to use my earlier script. :)

Monday, June 25, 2007

RAD - Portal - can't delete log4j.properties

Sometimes when I am working in RAD (6 or 7) on a Portal (5.1) project, it fails to re-build, saying that it cannot delete ./web/WEB-INF/classes/log4j.properties.

Often I can solve this with rm -f ./web/WEB-INF/classes/log4j.properties, but not always. Other times I need to re-build the app and delete the command quickly while that starts!

Worst case: I have had to close the server. Once I had to close RAD itself.

Wednesday, June 20, 2007

Cygwin Bash script: search/list/open arbitrary files in directory tree

Update. 13/12/2008 13:40:44 PM. Updated formatting of this article and updated the script.

Update. 19/11/2007 12:31:44 PM. Added ability to follow (or not) symbolic links.

Update. 9/02/2009 9:50:16 PM. Fixed (v 1.8.2 according to Anon's suggestion (time stamp Monday, February 09, 2009 9:23:00 PM).

This script is actually version 2 of the Cygwin Bash script: open any file in directory tree. It solves a wider variety of problems for me now.

I work with Java projects where I frequently want to list or open files without having to bother remembering or typing their complete paths - or names! Eclipse (and most other IDEs I am sure) offer the very cool "Open Type" and "Open Resource" features that allow you to type in file names using * wild cards and see a dynamic list of matching results. This doesn't provide a total solution however. Two key cases in point:

  • I have one project where there are 10's of classes with the same name (and thus same file name) which are discriminated by their package name i.e. path. So while I can type in "CommandBean" I still have to navigate through a large list of results to get to the one I want.
  • Other times I don't want to open the file. I just want to list them with their paths so I can copy and paste their names/paths into something else, like a code review form or change log.

Vim with William Lee's FindFile plugin script almost satisfied me, but there were a number of usage points that put me off.

  • One more app to keep open. I would open Vim/Gvim separately to do a task I otherwise want to do within Cygwin - which I use daily for so many tasks. I already have an editor of choice - UltraEdit - so I don't really like having another editor open when I won't be using it for what it really wants to be used for.. I have a Porsche and a Mercedes, but I never the drive the Mercedes, I just sit in it when I want to listen to the radio!
  • The FindFile interface is nice in that you can type and it dynamically adjusts the results, but you can only open one file per usage so you have to keep typing until the list is down to one result. This didn't suit all the scenarios I wanted it for.
  • I like Vim/Gvim, I really do, but I like UltraEdit better, so I don't want to switch. :)

So, I re-wrote my old script and came up with own solution.

Purpose. List or open arbitrary files from a directory tree.

Input. Various options, path names and regular expressions.

Output. List of file paths/names. Opens files in your editor of choice.

Example 1:

  1. List all files in the directory tree with 'department' in their name/path.
  2. List only .java files whose class name ends with 'department'.
  3. List only .java files in a package name that includes 'load' and whose class name ends with 'department'.
  4. List the same files as above but offer to open them. Open all of them.

Example 2

  1. Find all .java files whose class name ends with 'department' or 'location'. Open two specific files in that list.
  2. Open a specific file using its relative path from the current directory.
  3. Find all .java files whose package name include the 'base' package. Open one specific file in the result set.

Code:

#!/bin/bash

#-------------------------------------------------------------------------------
#  Editor's Little Helper. :)
#-------------------------------------------------------------------------------
# Open and/or search for a set of files and open them in your favourite text
# editor.
#
# Author: Robert Mark Bram
# v 1.2 - 21/06/2007 1:03PM. Made the script interactive
# v 1.3 - 26/06/2007 7:46AM. Modified usage comments. Fixed handling of spaces
#  in files names.
# v 1.4 - 6/07/2007 3:16:51 PM. Modified to protect against invalid indexes
#  being entered by the user.
# v 1.5 - 8/07/2007 4:31:23 PM. Split openFile function into open file by index
#  or path where each method should be called explicitly. Fixed error where
#  script was opening all by index, assuming paths were indexes!
# v 1.6 - 8/07/2007 4:37:47 PM. Split printListOfFiles function into two
#  functions, one that would print the index of each file along with the file
#  path, and one that would only print the path. Useful if this command is to
#  be part of a pipe.
# v 1.7 - 19/11/2007 12:31:44 PM. Included the ability to follow (or not) symbolic
#   links by hooking into the find command's capability for this. By default, do
#   not follow symbolic links.
# v 1.8 - 29/02/2008 3:23:20 PM. Exclude files matching:
#  grep -v '[.]svn-base$\|zzbuild\|[.]class$\|[.]bak$'
# v 1.8.1 - 12/05/2008 5:16:51 PM. Added another path for ultra edit.
# v 1.8.2 - 9/02/2009 9:50:16 PM. Fixed IFS=.. it still broke on spaces.

#-------------------------------------------------------------------------------
#  Dependencies
#-------------------------------------------------------------------------------

#-------------------------------------------------------------------------------
#  Variables for this script.
#-------------------------------------------------------------------------------
# Directory from which we search for files.
baseDir=.
# Case insensitive by default
caseSensitive=N
# Absolute path (or command string) to the editor to use when opening up files.
editor=
# Use this with the find command to search for files by name within baseDir.
fileNamePattern=*
# Interactive mode: [F]ull, [L]ist, [N]one or [P]artial.
interactivityMode=P
# If in partial interactivity, how many files do we have to find in order to
# trigger interactivity?
minFilesInteractive=2
# Did we actually have to search for files? If no, then all trailing args to
# this script were valid relative paths from base dir or valid absolute paths.
# Needed to work out what to do in partial interactivity mode.
searchPerformed=N
# Follow symbolic links? -P (no) or -L (yes) - as per the find command.
symbolic=-P


#-------------------------------------------------------------------------------
#  Common functions for this script.
#-------------------------------------------------------------------------------

# Help!
function usage () {
less << STOP_HELP
usage: $0 [-d DIRECTORY] [-e EDITOR] [-f FILE_NAME_PATTERN] [-h]
        [-i MODE] [-L] [-P] [-s] [regular expression or file path] ...

Search for a set of files and open them20/06/2007 5:11PM in your
favourite text editor

==========================
-d DIRECTORY
    Use directory other than default - which is the current dir.

-e EDITOR
    Specify an editor path or command to use, other than UltraEdit
    or notepad.

-f FILE_NAME_PATTERN
    Search pattern for file names. Argument is as per
    "find . -name FILE_NAME_PATTERN".

-h, --help
    Displays this message and exits.

-i  Define mode of interactivity. Defaults to Partial.
    - [F]ull: display lists of files and ask user what ones to open.
    - [L]ist: lists files only. No other form of interaction.
    - [N]one: do not display list of files or ask the user anything:
         automatically open all files found.
    - [P]artial: can act as Full or None as per below.
      - act as None if number of files found <= MIN_FILES_INTERACTIVE
    - act as None if all trailing args were valid relative
      paths to files from DIRECTORY or valid absolute paths.

-L  Follow symbolic links (as per find command).

-m MIN_FILES_INTERACTIVE
    Set the minimum number of files that should be found in order
    to trigger interaction when in Partial interactivity mode.
    Defaults to 2.

-P  Never follow  symbolic  links (as per find command). DEFAULT is this.


-s  Case sensitive.

==========================
General process for searching for files.

1) Find superset of files from target directory with the find command.
    - Can apply arguments to modify find command file name pattern.
    - Can apply arguments to modify target directory.
2) Apply regular expressions to superset of files to get selection set.
    - Can use one or more regular expressions via trailing arguments
      to this command.
3) Ask what files you wish to open in the editor.
    - Can apply argument to turn off interactive mode - all files get
      opened.
    - Can apply argument to set your own editor.

==========================
Examples.

$0 -i l "commandbean.java$"
  List all files from the current directory down whose relative path
  (including file name) ends with "commandbean.java" - case
  insensitive. Here it is effectievly doing:
  find . -name "*" | grep -i "commandbean.java$"

$0 -i l -f "*.java" commandbean
  List all files from the current directory down whose file name
  ends with ".java" and whose relative path - including file name -
  contains "commandbean" - case insensitive. Here it is effectievly
  doing:
  find . -name "*.java" | grep -i "commandbean"

$0 "status.*/.*java$"
$0  -f "*.java" "status.*/"
  Ask me to open files from list of Java source files in a package
  that includes the token "status" somewhere in it -- assuming there
  are more than 1.

$0 ./some/path/to/MyClass1.java \\
./some/path/to/MyClass2.java \\
./some/path/to/MyClass3.java \\
  Open up the files specified in UltraEdit.

$0 ./some/path/to/MyClass1.java ".*.properties$"
  Ask me to open files from list of a specific Java source file and
  all properties file that are found in /base/dir -- assuming the
  list has more than 1 result.

STOP_HELP

}

# Process command line arg governing interactivity.
function setUpInteractivity() {
  case "$1" in
    "F" | "f" ) interactivityMode="F";;
    "L" | "l" ) interactivityMode="L";;
    "N" | "n" ) interactivityMode="N";;
    "P" | "p" ) interactivityMode="P";;
    *         ) echo "Invalid option for -i. [F]ull, [L]imited or [N]one." ;
          usage; exit 4;;
  esac
}

# Process command line arg governing minFilesInteractive.
function setUpMinFilesInteractive () {
  if [ -n "`echo "$1" | grep -E -e '^[0-9]+$'`" ]
  then
    minFilesInteractive="$1"
  else
    echo "Invalid option for -m; not a number."
    usage
    exit 5
  fi
}

# Work out what editor to use if the user didn't specify one.
# Set the path to that editor in a script variable, "editor".
function findEditor() {
  if [ -z "$editor" ]
  then
    # Work out what editor to use.
    # Set the path to that editor in a script variable, "editor".
    if [ -e /cygdrive/c/Program\ Files/IDM\ Computer\ Solutions/UltraEdit-32/uedit32.exe ]
    then
      editor=/cygdrive/c/Program\ Files/IDM\ Computer\ Solutions/UltraEdit-32/uedit32.exe
    elif [ -e /cygdrive/c/Program\ Files/IDM\ Computer\ Solutions/UltraEdit/Uedit32.exe ]
    then
      editor=/cygdrive/c/Program\ Files/IDM\ Computer\ Solutions/UltraEdit/Uedit32.exe
    elif [ -e /cygdrive/c/Program\ Files/UltraEdit/uedit32.exe ]
    then
      editor=/cygdrive/c/Program\ Files/UltraEdit/uedit32.exe
    else
      editor=/cygdrive/c/WINDOWS/system32/notepad
    fi
  fi
}

# Open requested file in chosen editor according to an index within fileSet.
function openFileByIndex () {
  if [ -z "${fileSet[$1]}" ]
  then
    echo "No file found with index $nextIndex"
    return
  fi
  echo File: "${fileSet[$1]}"
  "$editor" `cygpath -w -a "${fileSet[$1]}"` &
}

# Open requested file in chosen editor using its path.
function openFileByPath () {
  if [ -f "$1" ]
  then
    echo File: "$1"
    "$editor" `cygpath -w -a "$1"` &
  else
    echo "No file found $1"
  fi
}

# Open files that have been selected - filesToOpen must contain a list of space
# separated indexes to the associative array of files, fileSet.
function openSelectedFiles () {
  for nextIndex in $filesToOpen
  do
    case "$nextIndex" in
      *[!0-9]*|""   ) echo "Not a valid index: $nextIndex";;
      *             ) openFileByIndex "$nextIndex";;
    esac      #  Allows ranges of characters in [square brackets],
  done
}

# Open all files in the array of files, fileSet.
function openAllFiles () {
  index=0
  while [ "${index}" -lt "${#fileSet[@]}" ]
  do
    # echo File [${index}]: ${fileSet[$index]}
    openFileByPath "${fileSet[$index]}"
    let "index++"
  done
}

# Print list of files in the set - printing index for each entry.
function printListOfFilesWithIndex() {
  index=0
  while [ "${index}" -lt "${#fileSet[@]}" ]
  do
    printf "File %*d: ${fileSet[$index]}\n" 2 ${index}
    let "index++"
  done
}

# Print list of files in the set - without an index for each entry.
function printListOfFilesWithoutIndex() {
  index=0
  while [ "${index}" -lt "${#fileSet[@]}" ]
  do
    printf "${fileSet[$index]}\n"
    let "index++"
  done
}

# Find files from the trailing arguments to this script.
# Each argument could be a
# - valid relative path to a file from base dir.
# - valid absolute path to a file.
# - regular expression to be used to grep over results of a find.
# Fill up a variable called fileSet with the results.
function getFilesFromTrailingArgs() {
  # Make delimiter equal to whatever newline char we are using now
  oldIFS=$IFS
  IFS=$'\n'

  # Go through remaining arguments.
  fileIndex=0
  while [ $# -gt 0 ]
  do
    # If the argument is a file path that exists, put it in the file set
    if [ -f "$1" ]
    then

      # echo File path: "$1"
      fileSet[$fileIndex]="$1"
      let "fileIndex++"
    else
    # Otherwise, treat argument as a regular expression to modify superset of
    # files - which is itself the result of a find command.

      # We are searching..
      searchPerformed=Y

      if [ "$caseSensitive" = "Y" ]
      then
        files=`find ${symbolic} . -type f -name "$fileNamePattern" | grep "$1" | grep -v '[.]svn-base$\|zzbuild\|[.]class$\|[.]bak$'`
      else
        files=`find ${symbolic} . -type f -iname "$fileNamePattern" | grep -i "$1" | grep -v '[.]svn-base$\|zzbuild\|[.]class$\|[.]bak$'`
      fi

      # Put each file we found into file set.
      for file in $files ; do
         # echo File from expressio: "$file"
        fileSet[$fileIndex]="$file"
        let "fileIndex++"
      done
    fi
    shift
  done

  # Reset separator to what it was.
  IFS="$oldIFS"
}

# Find files using only a find - no grep, no file paths.
# Fill up a variable called fileSet with the results.
function getFilesWithoutTrailingArgs() {
  # No trailing args - we are searching..
  searchPerformed=Y

  # Make delimiter equal to whatever newline char we are using now
  oldIFS=$IFS
  IFS="
  "

  if [ "$caseSensitive" = "Y" ]
  then
    files=`find ${symbolic} . -type f -name "$fileNamePattern"`
  else
    files=`find ${symbolic} . -type f -iname "$fileNamePattern"`
  fi

  # Put each file we found into file set.
  for file in $files ; do
    # echo File from expressio: "$file"
    fileSet[$fileIndex]="$file"
    let "fileIndex++"
  done

  # Reset separator to what it was.
  IFS="$oldIFS"
}

# Display list of files and ask user which ones to open. Puts their answer into
# variable called fileToOpen.
function askUserWhatFilesToOpen() {
  printListOfFilesWithIndex
  Echo Specify files to open. [A]ll, [N]one or [x y z] space separated indexes.
  read filesToOpen
  # echo We will open these: ${filesToOpen}

  case "${filesToOpen}" in
    "n" | "N") ;; # echo Open no files;;
    "a" | "A") openAllFiles;;
      *        ) openSelectedFiles;;
  esac
}



#-------------------------------------------------------------------------------
#  Script Logic
#-------------------------------------------------------------------------------

# Must have *some* arguments.

if [ "$#" -eq 0 ]
then
  echo Must specify command line arguments.
  usage; exit 0;
fi

# Process all the opening arguments.
exitFlag=N
while [ "$exitFlag" = "N" ]
do    # Until exit flag is set to Y.
  case "${1}" in
    "-d"    )  baseDir="$2"; shift;shift;;
    "-e"    )  editor="$2"; shift;shift;;
    "-f"    )  fileNamePattern="$2"; shift;shift;;
    "-h"    )  usage; exit 0;;
    "-i"    )  setUpInteractivity "$2"; shift;shift;;
    "-L"    )  symbolic="-L"; shift;;
    "--help")  usage; exit 0;;
    "-m"    )  minFilesInteractive="$2"; shift;shift;;
    "-P"    )  symbolic="-P"; shift;;
    "-s"    )  caseSensitive=Y; shift;;
    *       ) exitFlag=Y;;
  esac
done

cd "$baseDir"

findEditor

if [ $# -gt 0 ]
then
  getFilesFromTrailingArgs "$@"
else
  getFilesWithoutTrailingArgs
fi

# echo Found ${#fileSet[@]} files.

if [ ${#fileSet[@]} -eq 0 ]
then
  echo No files found.
  exit 0
fi

# Just list the files?
if [ "$interactivityMode" = "L" ]
then
  printListOfFilesWithoutIndex


# Open all of the files without asking? Interactive mode is [N]one.
elif  [ "$interactivityMode" = "N" ]
then
  openAllFiles

# Interactive mode is [P]Artial.
# Ask the user what files to open if no search was performed or if number of
# files found was less than min required to trigger interactivity.
elif  [ "$interactivityMode" = "P" ]
then
  if [ "${#fileSet[@]}" -lt "$minFilesInteractive" -o "$searchPerformed" = "N" ]
  then
    openAllFiles
  else
    askUserWhatFilesToOpen
  fi

# Interactive mode is [F]ull.
# Ask the user what files to open if we are in interactive mode.
else
  askUserWhatFilesToOpen
fi


# echo Done.