eyt*

Find your next scenic drive!

May 23, 2008

Using Ant with Cygwin

If you have ever tried building something with ant under Cygwin, you probably have noticed an error such as the following:

build.xml:35: c:\cygdrive\c\Progra~1\java/SunAppServer\jdk\lib\J2EE.jar

The current Ant documentation suggests that you should change your build.xml to wrap all such environment labels and class paths with code that executes cygpath, but this turns out to create some ugly code, especially if your build scripts use a lot of environment labels.

An alternative scheme that I have just come up with is to use a layer of indication. You can create an ant shell script that translates all of your necessary Cygwin environment labels using cygpath, and then call the ant.bat to do the actual build. The advantage of this approach is that more build.xml files are cross-platform out of the box and require very few, if any, tweaks.

The following file is an example of my approach:

#!/bin/sh
# OS-Specific Stuff
export PATH=`/usr/bin/cygpath --path --windows $PATH`
export TEMP=`/usr/bin/cygpath --path --windows $TEMP`
export TMP=`/usr/bin/cygpath --path --windows $TMP`

# Java Specific Stuff
export JAVA_HOME=`/usr/bin/cygpath --windows $JAVA_HOME`
export J2EE_HOME=`/usr/bin/cygpath --windows $J2EE_HOME`
export TOMCAT_HOME=`/usr/bin/cygpath --path --windows $TOMCAT_HOME`
export CLASSPATH=`/usr/bin/cygpath --path --windows $CLASSPATH`

# Ant Specific Stuff
ORIGINAL_ANT_HOME=$ANT_HOME
export ANT_HOME=`/usr/bin/cygpath --path --windows $ANT_HOME`

# Project-Specific Stuff
export WHATEVER_YOU_NEED=`/usr/bin/cygpath --windows $WHATEVER_YOU_NEED`

# Invoke the ANT.BAT via the original ANT_HOME value
$ORIGINAL_ANT_HOME/bin/ant.bat $1 $2 $3 $4 $5 $6 $7 $8 $9

Of course, this technique can be generally applied to other applications that use environment labels. Hope that helps.

February 11, 2007

C++ Strongly-Typed Enumerations...

With the release on Java 5, we finally have enumerations in Java, but they really outdone themselves, in that an enumerated type is actually a class, where you can add your own methods. Take, for example, this code:

  1. public enum Color {
  2.   Red,
  3.   Yellow,
  4.   Orange,
  5.   Green,
  6.   Blue,
  7.   Brown,
  8.   Black,
  9.   Purple;
  10.  
  11.   public boolean isPrimaryColor() {
  12.     switch ( this ) {
  13.     case Red:
  14.     case Blue:
  15.     case Yellow:
  16.       return true;
  17.  
  18.     default:
  19.       return false;
  20.     }
  21.   }
  22. }

Instead of having a utility class off to the side that takes an enumeration as a parameter, you can now add that method directly to the enumeration, and I do not think that there would be much controversy that color.isPrimaryColor(); reads better than ColorUtils.isPrimaryColor( color );.

Putting this aside for a moment, C++09 is cleaning up their enumerations by adding support for strongly type enums (PDF Proposal).

In the current version of C++, we have problems with code like this:

  1. enum Color { RED, YELLOW, ORANGE, GREEN, BLUE, BROWN, BLACK, PURPLE };
  2. enum TerrorAlertLevel { RED, ORANGE, YELLOW, BLUE, GREEN };

This does not compile, because by the time it gets to TerrorAlertLevel, RED, ORANGE, YELLOW, BLUE, and GREEN are already defined, thanks to the Color enumeration. And worst off, if you have a coding standard that does not require you to have your enumerations all in caps (which makes sense, since part of the reason that Macros are usually done in caps is to discourage their use), then you could have an issue with any variables, such as int red = 0xFF0000; would not compile if the enumeration was all in lower-case.

As such, developers in C++ tend to add a prefix to the definition, such as:

  1. enum Color { COLOR_RED, COLOR_YELLOW, COLOR_ORANGE, COLOR_GREEN, COLOR_BLUE, COLOR_BROWN, COLOR_BLACK, COLOR_PURPLE };
  2. enum TerrorAlertLevel { TAL_RED, TAL_ORANGE, TAL_YELLOW, TAL_BLUE, TAL_GREEN };

Oh, that looks friendly.

Well, to help in that friendliness, C++09 is adding strongly typed enums. This is done by adding the class keyword to the enum definition. For example, the first C++ example above could compile under C++09 as this:

  1. enum class Color { RED, YELLOW, ORANGE, GREEN, BLUE, BROWN, BLACK, PURPLE };
  2. enum class TerrorAlertLevel { RED, ORANGE, YELLOW, BLUE, GREEN };

The bold emphasizes the difference.

Conceptually, I think this is a great addition to C++, making it easier to reuse enumerations and avoiding the crazy compilation errors that you can get if you accidentally name your variable a name that has already been defined in an enum.

The part that I do not like about this proposal, however, is the fact that it uses the keyword class but it is not a class. You cannot add member functions to it, which because of the power of the Java enumerations, I think this is confusing for developers which experience with Java enumerations.

C# enumerations are strongly typed but you cannot add any methods to them. But at least, they do not use the word class in their definition.

While I more than welcome the strongly-typed nature of enumerations in C++, I think that the implementation could have overloaded a better keyword than class. Since it does seem like this has been accepted already, hopefully a future version of the standard will add the ability to add member functions to the enumeration, minimizing some of this confusion.

August 2, 2006

Searching for nil in all the wrong places...

A few days ago, I was adding some migrations to a Ruby on Rails project, but when I went to initialize a newly created database, I saw this error:

  • rake aborted!
  • You have a nil object when you didn't expect it!
  • You might have expected an instance of Array.
  • The error occured while evaluating nil.first

This being the third or so time working in Ruby, I immediately thought that it was something I did, so I started up the automatically generated scripts/application, pasted my new code into there, and proceeded to use it. The code had a couple issues that I fixed and I was confident enough to try again.

But again, I got the same error message. I then decided to add a couple prints around the new code, and re-run it, but the error above occurred without my print outs. Not sure how to proceed, I asked for some assistance from some co-workers. The advice was great, and I learned a lot about Ruby and Rails, but it still did not resolve my problem.

Finally someone recommended adding --trace to the my command:

  • eyt@zaterdag 5031% rake --trace migrate
  • ** Invoke migrate (first_time)
  • ** Invoke environment (first_time)
  • ** Execute environment
  • ** Execute migrate
  • rake aborted!
  • You have a nil object when you didn't expect it!
  • You might have expected an instance of Array.
  • The error occured while evaluating nil.first
  • .../lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/active_record/migration.rb:253:in `migration_files'
  • .../lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/active_record/migration.rb:252:in `sort_by'
  • .../lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/active_record/migration.rb:252:in `each'
  • .../lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/active_record/migration.rb:252:in `sort_by'
  • .../lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/active_record/migration.rb:252:in `migration_files'
  • .../lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/active_record/migration.rb:235:in `migration_classes'
  • .../lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/active_record/migration.rb:223:in `migrate'
  • .../lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/active_record/migration.rb:190:in `up'
  • .../lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/active_record/migration.rb:181:in `migrate'
  • .../lib/ruby/gems/1.8/gems/rails-1.0.0/lib/tasks/databases.rake:3
  • .../lib/ruby/gems/1.8/gems/rails-1.0.0/lib/tasks/databases.rake:2:in `call'
  • .../lib/ruby/gems/1.8/gems/rake-0.6.2/lib/rake.rb:202:in `execute'
  • .../lib/ruby/gems/1.8/gems/rake-0.6.2/lib/rake.rb:202:in `each'
  • .../lib/ruby/gems/1.8/gems/rake-0.6.2/lib/rake.rb:202:in `execute'
  • .../lib/ruby/gems/1.8/gems/rake-0.6.2/lib/rake.rb:180:in `invoke'
  • .../lib/ruby/gems/1.8/gems/rake-0.6.2/lib/rake.rb:1454:in `run'
  • .../lib/ruby/gems/1.8/gems/rake-0.6.2/lib/rake.rb:1454:in `each'
  • .../lib/ruby/gems/1.8/gems/rake-0.6.2/lib/rake.rb:1454:in `run'
  • .../lib/ruby/gems/1.8/gems/rake-0.6.2/bin/rake:7
  • .../bin/rake:18:in `load'
  • .../bin/rake:18

So that is very interesting. Note how there is nothing in the above points to anything of mine. In pondering this with one of my coworkers, I explained how one of the things that I tried during debugging it was renaming the name of the class and filename, and that struck a cord.

It turns out that the mess above was all caused by my filename. I had named my file revision_add_blah_to_myApp and named my class AddBlahToMyApp. The problem with this is that the Rails convention for class names requires me to either name my file revision_add_blah_to_my_app or name my class AddBlahToMyapp. This very subtle issue caused this most unusable error message to cause a few completely wasted hours.

I have blogged before about great and not so great exceptions and their handling. I think this also falls in that category of not-so-great. It would have been nice to know that this problem originated with an attempt to load a class that did not exist, for example. In that case, I would have looked at that issue more closer than looking for references to first (and indirect references thereof) where the object invoking that method could have been nil.

June 25, 2006

Clearing an empty java.util.HashMap...

Here is an interesting question; which of the following programs executes faster:

  1. public class ProgramA {
  2.   public static void main( String [] args ) {
  3.     java.util.Map<String, String> map = new java.util.HashMap<String, String>( 100000000 );
  4.     long start = System.currentTimeMillis();
  5.     for ( int i = 0; i < 100; ++ i ) {
  6.       map.clear();
  7.     }
  8.     System.err.println( ( System.currentTimeMillis() - start ) + "ms" );
  9.   }
  10. }
  1. public class ProgramB {
  2.   public static void main( String [] args ) {
  3.     java.util.Map<String, String=> map = new java.util.HashMap<String, String=>( 100000000 );
  4.     long start = System.currentTimeMillis();
  5.     for ( int i = 0; i < 100; ++ i ) {
  6.       if ( ! map.isEmpty() ) {
    // this line is not in the above.
  7.         map.clear();
  8.       }
  9.     }
  10.     System.err.println( ( System.currentTimeMillis() - start ) + "ms" );
  11.   }
  12. }

By simply looking at the code or reading through the documentation, you may think that they are similar since the map is empty, since presumably the clear() method would take this into consideration. But of course, if that were the answer, this would not be much of a blog post.

On my machine, Program_A runs in about 37625 milliseconds with one of my processors pegged whereas the Program_B runs in about 1 millisecond. Why? It would appear that the clear() method recreates its internal representation each time that you call it instead of looking at the current state of the object and realizing that it is indeed empty. Naturally if the initialCapacity is smaller, the program is less affected by this.

Obviously no one writes code exactly like the above snippet, but if you are calling clear() from within a loop, you may want to be aware of this.

June 10, 2006

.NET and loading assemblies for other platforms...

If you are working with .NET, and you see the following exception:

TypeLoadException: Method 'Dispose' in type 'eSQL.SqlConnection' from assembly 'eSQLNet, Version=2.9.2056.14469, Culture=neutral, PublicKeyToken=null' does not have an implementation

Make sure that the .NET assembly you are referencing (eSQL in this case) is for your current platform.

In the case depicted above, I had accidentally selected the ARM version of the eSQL DLL instead of the x86 version. Unfortunately, however, the exception message above does not exactly say that. It would have been nicer to have gotten a message that would have said something about the assembly not being compiled for the current platform...

Earlier Entries

<  1  2  3  4  5  6  7  >

Navigation

Recent Posts

eyt*