10.30.2008

Is JSF Fixable?

Subbu says that JSF is Not Fixable. His he right or his he wrong? That is the question!

I've been wanting to write about JSF for a while now. I was an early adopter and big proponent of JSF when it first appear at the surface of Java land. Since then, I've worked on many projects that used this framework and slowly but surely, my opinion changed about this framework.

In Subbu opinion, JSF is not fixable because it was not designed with web architecture in mind. The framework is not RESTful and requires POST for everything. JSP proponent argue that RESTful is coming to JSF and it is even already supported right now when you use JBoss Seam.

It's true, that JSF is getting better. Facelets will become part of JSF (it should have been there from day though - why make a brand new framework and use broken things like JSPs). JSF 2.0 will make some space for GETs and JBoss Seam fixes a lot of other issues with JSF.

However, if JSF is not Fixable, it is not solely because it is not RESTful. JSF is broken at many places.

The JSF request processing life-cycle is one of the worst thing about JSF. There is so many steps in the life-cycle, different ways to exit the normal processing and this gives something many entry points for custom components (one for each steps in the life-cycle). Things can go wrong at many places without even touching your application code.

Closely related to the request processing life-cycle, is the component model. In JSF, components are a kind of black box. But when you think that black box should be mostly independent on the framework (beside the connecting part with the framework), there is so many implementation leak in JSF, that your component must be ported to every JSF implementation. I say ported and not tested... Because the order in which the component tree is processed can make you component fail. You have to make so many assumption based on the JSF implementation (things that are not present in the specification) that you actually need to choose your JSF implementation carefully and will never be guaranteed that your component will survive even minor updates in the JSF implementation.

Want to add to functionality to an existing component? There is no easy way to do that. The best way, is to take the source code (if available) for the component and refactor it with some new name and then hack you way through it. And when I say hack, I mean it in a bad way! I've never saw a component source code that was easily readable and understandable. Large component sets like ADF Faces are notably ugly. Even the Swing component model (which is quite complex) is super simple compared to JSF component model.

It looks like in JSF, the worst thing about leaking abstractions made it into the framework and the good thing about encapsulation where left out.

And before I get flamed that you will be able, with JSF 2.0 - which is still in draft, to create composite components soon... I have to respond that soon is not now.

One of a good indication that JSF is broken beyond fixable, is that you cannot do simple thing simply. Want to create a new component, then you have something like 3 files to edit. Want to create a new converter, again, three files needs to be edited. I know that good thing came in sets of three, but this is not the kind of good I want.

Another good indication that JSF is broken beyond fixable, is when thing go wrong. Good luck finding you problem... Facelets help you a bit by giving you a more precise location of the issue. But it does not always work. When you look at stack trace, you have to scroll hundreds of line before you see the line from you application. But what the hell do you do when you don't even have any of you application code in the stacktrace? Why on earth, you are have a stacktrace that is 30-40 level deep before even reaching an action method? Just to process a fringing POST.

In JSF, the framework is everywhere except where you need it.

Yes, there is some good things about JSF, I like the expression language (even is it is a bit inconsistent and not powerful enough to my taste). But the bad things are so deeply rooted. If you are just a user of the framework and that you are writing really simple applications, then it does look like JSF is great, but whenever you start digging a bit and adding a bit of complexity (Ajax anyone? Without another component set that takes a few MB?), the framework start eating up your nerves.

So would I ever recommend JSF again? Maybe maybe not, in the Java world, there is so many framework to choose from, that your cannot recommend one with the certainty that it will still be around one or two years from now. The only sure thing, is that JSF will be there because it is the standard... And this is very sad!

10.20.2008

Making Mr Bones work with jRuby

UPDATE : The fixes outlined bellow where incorporated by Tim in the main Mr Bones distribution. If you update to the latest gem, it will work out of the box with jRuby.

Want to make some GEM, Mr Bones (github) is going to help you setup your directory structure and comes with Rake tasks to package your application into a nice little Gem. The great thing about Mr Bones is that it does not add any dependencies to your Gem (like hoe does).


To create a new project, you just type bones [project_name] and you can start developping your application or library. You are done? type bones gem:install and your code is packaged and installed.


Want to do the same using jRuby instead of plain old Ruby (MRI)... You do just the same... But as of version 2.1.0, all you would just get errors when using Bones. The reason is that Mr Bones does things slightly differently dependings on the RUBY_PLATFORM value.

For example, if Bones detected that the platform was win32, it does it's redirection to nul: instead of /dev/null.

The problem with running Bones under jRuby, is that there is no win32 string in the value of RUBY_PLATFORM. Instead, you have java. So if you run Bones with jRuby under Windows, it does not beleive it's running under Windows and it does thing like it would if running under Unixes.

I really wanted Bones to work under jRuby, so I started working on a patch... Here is what I've done...

In lib/bones.rb, the existing code set a WIN32 constant to true if RUBY_PLATFORM contains win32. It then uses this constant to set DEV_NULL constant to the proper null device for redirection. I believe a more appropriate way of doing this is to check for the existence of /dev/null and then set DEV_NULL depending on the result. So on all Unix based OSes, the value would be /dev/null because it exists, else it would be nul: (these days, only Windows is not Unix based).

I also set a HOME constant that will point to the user home directory. To do this, I assign the value of the HOME environment variable and if its not set, I use the value of the USERPROFILE environment variable. Again, no need to check the platform, just check for things that should be there one way or another.

Finally, Bones will run things like rake or gem. It does so by using the system(...) method or Rake sh(...) method. This is fine if you have only one installation of Ruby on your system. But if you have many installation like I does, it will use the one it find in the PATH. So I use the same trick as Rake to find the ruby intepreter we are running on and set it to the RUBY constant. We will use this later on to make sure we use rake or gem for the same interpreter.

Here is the patch for lib/bones.rb :
diff --git a/lib/bones.rb b/lib/bones.rb
index 50a63b6..35dcd75 100644
--- a/lib/bones.rb
+++ b/lib/bones.rb
@@ -4,8 +4,9 @@ module Bones
   # :stopdoc:
   VERSION = '2.1.1'
   PATH = File.expand_path(File.join(File.dirname(__FILE__), '..'))
-  WIN32 = %r/win32/ =~ RUBY_PLATFORM
-  DEV_NULL = WIN32 ? 'NUL:' : '/dev/null'
+  HOME = File.expand_path(ENV['HOME'] || ENV['USERPROFILE'])
+  DEV_NULL = File.exist?("/dev/null") ? "/dev/null" : "nul:"
+  RUBY = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']). sub(/.*\s.*/m, '"\&"') # Ruby Interp
reter location - taken from Rake source code
   # :startdoc:

   # Returns the path for Mr Bones. If any arguments are given,
@@ -16,6 +17,7 @@ module Bones
     args.empty? ? PATH : File.join(PATH, args.flatten)
   end

+
   # call-seq:
   #    Bones.require_all_libs_relative_to( filename, directory = nil )
   #
@@ -46,7 +48,7 @@ module Bones
     load bones_setup

     rakefiles = Dir.glob(File.join(Dir.pwd, %w[tasks *.rake])).sort
-    import(*rakefiles)
+    import(*rakefiles) unless rakefiles.empty?
   end

   # TODO: fix file lists for Test::Unit and RSpec


Now onto lib/bones/main.rb. In main.rb, there are calls to rake. This is one place I use the RUBY constant mentionned earlier to execute rake from the same interpreter location and not from the system PATH. To do that, I just do
system("#{::Bones::RUBY} -S rake ...") instead of just system("rake ...")

Ther was also a place in this file that the old WIN32 constant was used to find the user home directory. So I replace this with the HOME constant we talked a bit earlier.

Here is the patch for lib/bones/main.rb :
diff --git a/lib/bones/main.rb b/lib/bones/main.rb
index b7ec28b..1cc074a 100644
--- a/lib/bones/main.rb
+++ b/lib/bones/main.rb
@@ -181,9 +181,9 @@ class Main
       if test(?f, File.join(output_dir, 'Rakefile'))
         begin
           FileUtils.cd output_dir
-          system "rake manifest:create 2>&1 > #{::Bones::DEV_NULL}"
+          system "#{::Bones::RUBY} -S rake manifest:create 2>&1 > #{::Bones::DEV_NULL}"
           @io.puts "now you need to fix these files"
-          system "rake notes"
+          system "#{::Bones::RUBY} -S rake notes"
         ensure
           FileUtils.cd pwd
         end
@@ -363,7 +363,7 @@ class Main
   def mrbones_dir
     return @mrbones_dir if defined? @mrbones_dir

-    path = (::Bones::WIN32 ? ENV['HOMEPATH'].tr("\\", "/") : ENV['HOME'])
+    path = ::Bones::HOME
     path = File.join(path, '.mrbones')
     @mrbones_dir = File.expand_path(path)
   end

It was now lib/bones/tasks/setup.rb b/lib/bones/tasks/setup.rb turn to get dissected. There, it was again seting the value of a WIN32 constant with a slightly different logic than in bones.rb. It used this constant to set DEV_NULL again and then to use diff.exe instead of just diff when running under Windows. I removed the WIN32 constant. Got rid of the if used to add the ".exe" extension as they are not necessary. If ther is a diff.exe on Windows, system("diff ...") will work just nice.  When checking for an appropriate diff utility, the existing code does some system commands inside a quiet context. the quiet(...) method just changed the stderr and stdout to the null device before running the block. There is a problem when calling the reopen() method under jRuby that made the quite method effect stay there until the end of the program. So I removed that method and used redirection in the system(...) call to acheive the same result.

I then set the value of the RCOV, RDOC and GEM constant to "#{RUBY} -S rcov", ... so that these commands execute under the same interpreter we are currently running.

Here is the lib/bones/tasks/setup.rb b/lib/bones/tasks/setup.rb patch :
diff --git a/lib/bones/tasks/setup.rb b/lib/bones/tasks/setup.rb
index 27d659b..a722c13 100644
--- a/lib/bones/tasks/setup.rb
+++ b/lib/bones/tasks/setup.rb
@@ -5,7 +5,7 @@ require 'rake/clean'
 require 'fileutils'
 require 'ostruct'

-class OpenStruct; undef :gem; end
+class OpenStruct; undef :gem if defined? gem; end

 # TODO: make my own openstruct type object that includes descriptions
 # TODO: use the descriptions to output help on the available bones options
@@ -123,36 +123,17 @@ import(*rakefiles)
 %w(lib ext).each {|dir| PROJ.libs << dir if test ?d, dir}

 # Setup some constants
-WIN32 = %r/djgpp|(cyg|ms|bcc)win|mingw/ =~ RUBY_PLATFORM unless defined? WIN32
-
-DEV_NULL = WIN32 ? 'NUL:' : '/dev/null'
-
-def quiet( &block )
-  io = [STDOUT.dup, STDERR.dup]
-  STDOUT.reopen DEV_NULL
-  STDERR.reopen DEV_NULL
-  block.call
-ensure
-  STDOUT.reopen io.first
-  STDERR.reopen io.last
-  $stdout, $stderr = STDOUT, STDERR
-end
+DEV_NULL = File.exist?("/dev/null") ? "/dev/null" : "nul:"
+
+DIFF = if system("gdiff '#{__FILE__}' '#{__FILE__}' > #{DEV_NULL} 2>&1") then 'gdiff'
+       else 'diff' end unless defined? DIFF
+
+SUDO = if system("which sudo > #{DEV_NULL} 2>&1") then 'sudo'
+       else '' end

-DIFF = if WIN32 then 'diff.exe'
-       else
-         if quiet {system "gdiff", __FILE__, __FILE__} then 'gdiff'
-         else 'diff' end
-       end unless defined? DIFF
-
-SUDO = if WIN32 then ''
-       else
-         if quiet {system 'which sudo'} then 'sudo'
-         else '' end
-       end
-
-RCOV = WIN32 ? 'rcov.bat' : 'rcov'
-RDOC = WIN32 ? 'rdoc.bat' : 'rdoc'
-GEM  = WIN32 ? 'gem.bat'  : 'gem'
+RCOV = "#{RUBY} -S rcov"
+RDOC = "#{RUBY} -S rdoc"
+GEM  = "#{RUBY} -S gem"

 %w(rcov spec/rake/spectask rubyforge bones facets/ansicode).each do |lib|
   begin

There is one last change that should be done to be able to run the specifications, we need to require 'rubygems' so the trick we took from Rake to get the current ruby interpreter work :
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 5a60dc1..1965e1c 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -3,6 +3,7 @@
 unless defined? BONES_SPEC_HELPER
 BONES_SPEC_HELPER = true

+require 'rubygems'
 require File.expand_path(
     File.join(File.dirname(__FILE__), %w[.. lib bones]))

--


We can nw enjoy Bones with jRuby on Windows just like we would enjoy it under MRI.

I've submitted this patch to Tim, the auther of Mr Bones. While he review it, you can download it from here and apply it to your source tree to check it out.


9.09.2008

Google Chrome... One week after.

I've now been using Google Chrome for one week. And here is an update on my first impression.

Chrome is still my default browser, this is good :-) The browser did not crash once in that week. And it is almost always open. I use a laptop and shutdown/log off rarely (standby/hibernate most of the time). So I guess I can say that Google Chrome is very stable (better that Firefox). I did not even got one crash in a tab. I really like the speed and responsiveness of Chrome.

Other things I like :
  • I assigned a shortcut for GMail. It now lives in its own window. What I like, is that when I'm in the GMail window and press Ctrl-T to open a new tab, instead of opening it in the GMail window, the tab is opened in my browser windows. This is how it should work. Great!
  • In the rare cases when I close the browser and reopen it, all my tabs reload quickly without bogging down my system like Firefox did.
  • Google Apps works better in Chrome that in Firefox... especially when the browser reload the tab when it's being opened.

All is not nice and dandy in Chrome though, here are some gripes I have with the browser :
  • Lack of extensions. They say they will address the issue in a later version. In the meantime, bookmarklets are alleviating a bit the lack of extensions,
  • Full page zoom please! The zoom feature of Chrome only increase font size. This is lame. Give me full page zoom like in Firefox 3,
  • Directly open PDF and some other file types. When I download some file and the server set the content-disposition to attachment, Chrome absolutely want to save the file. Please give me the option of just opening the file with the associated application (saving it to a temporary location),
  • They really should encrypt and password protect saved password. They cannot claim better privacy without this. I feel that should have done it from the start,
  • Going to http://askville.amazon.com/ give me a page telling me that I'm not using a supported version of Safari,

  • I encountered some layout issues. Most of them are minor. But when I access my internal time-sheet application (Oracle Applications), Some buttons used to submit the time-sheet are invisible.
  • First week, first security update... Maybe this should go in part in the pros, but there is no notification (if you leave your browser open all the time) unless you go to the Tools | About Google Chrome menu.

None of the cons are really showstopper for me. So I guess I will continue to use Chrome for a while.


9.03.2008

Google Chrome First Impression

Yesterday, Google released their own browser. This release was like a little surprise. While it has been rumoured to be in work for a while, it was announced only the day before.

I don't know how many people downloaded it yesterday, but I'm one of them. I saw the little comic book  explaining how the browser work and wanted to put my hand on the product. After a few hour playing with it, here are y tought on the browser :
  • It's fast! Right when you start it, it really scream... It feels faster that Firefox 3. When you click, it looks like your actions are instantanious. For some reason, I'm even able to watch some higher definition movien in Flash that I were constantly pausing when using Firefox 3 (I don't beleive the browser makes Flash run better, but thats the feeling I get). Switching between tabs is also instantanious.
  • Damn my screen look larger. The minimalist user interface make your browsing surface a bit larger. Granted, you could do that with IE and Firefox in Kiosk mode. But here, you have no choice... Just a big browsing area.
  • Where are my mouse gestures? I was using a mouse gesture extension in Firefox. There seem to be no extensions and no way to write them for Chrome (I'm probably wrong here... I guess the APIs are not realsed yet but they'll be coming in a futher release).
  • Better Privacy. Not so fast. They claim better privacy... But their password manager is not encrypted nor password protected! I don't realsy care about the other privacy features they offer. I don't believe they are much better that the ones in Firefox. I don't feel that the Internet is a dangerous place anyway.
  • Gears bundled in. The Gears API are natively supported in the browser. They even automatically grant www.google.com the necessary rights. They shouldn't do that. However, they don't grant docs.google.com the Gears rights.
  • I already miss extensions.

Overall, I feel this is a solid beta realease. I will keep using Chrome as my default browser for at least a few days. Maybe I'll get back to Firefox to get my extensions back.

8.01.2008

Don't say you've not be warned

I took this grainy video (sorry, my phone is not really good at taking videos) at my local Best Buy. It looks like Best Buy want you to know exactly what will happen to your Xbox 360. Funny... Best Buy has never been known for their truthful advertising!


7.08.2008

iPhone 3G in Canada - Do complain to Fido and/or Rogers



It looks like complaining to Fido (Fido is a subsidiary of Rogers) or Rogers about the iPhone 3G rate plan may lead to some changes...

I'm a current Fido subscriber, after a two week vacation, away from Canadian news, I saw the announced rate plan for the soon to be release iPhone 3G in Canada. While I was not expecting prices lower that AT&T's in the US, I was in complete disbelief when I saw the proposed rates. So as any good customer would do, yesterday I went to their web sites and wrote a complain (I complained about their highly priced rate plans and said that I would not buy the iPhone and consider switching to one of their competitor. I closed my complain telling them that I would write to the CRTC about how they abuse their GSM monopoly in Canada to offer non-competitive pricing).

Just a few minutes ago, I received a call from someone named S. Martin from Fido who wanted me to listen to what he had to say about my complain. The guy is certainly not a classic PR representative with only corporate mambo gumbo going out of his mouth. He seemed to trully care about me not leaving Fido for Bell. He did repeat some of the PR stuff I saw on some news site. The interesting part was that he told me that the proposed rate plans were not definitive, that they may change. He told me not to do anything before the iPhone 3G is launched, that nothing was final until July 11th. They will listen to the market and adjust.

He also told me that the CRTC (the equivalent of the FCC here in Canada) did not rule wireless pricing so it was useless for me to write them a letter. That I would be wasting my time. I guess this really mean that they don't want get the wrath of the CRTC if too many people write about this issue.

The conversation with M. Martin was always cordial and on a gentle tone.

So I guess the only thing to do right now, is to be patient and wait... Do not crumble under the iPhone 3G hype (too soon) and wait a little bit for them to see that we are not interested in their overpriced rate plans. They will have to adjust their pricing model and offer something a lot better than what they are offering right now.

2.27.2008

JNA love NXT

I started working on something to bring some Ruby love to my Lego Mindstorm NXT. I know, there is already one or two projects that aims to do that, but the first one is dormant and the other one is at a fairly early stage.

The first thing that needs to be done before accomplishing anything useful on the NXT, is to connect to it via USB or (preferably) Bluetooth. This can be done in two ways : use the Lego Fantom API or use direct communication via a serial link.

Communicating via a serial link from Ruby involves using the ruby-serialport gem which seems to be no longer maintained. The other options to work with the serial port (besides using platform specific stuff) would be to use JRuby and the Java Communication API. But Java Comm only support Solaris and Linux. There is also RXTX but it is not that simple to get started with. If serial port communication was the only option, I would probably choose ruby-serialport to get things done (which ruby-nxt is using).

However, there is the option of using the Lego Fantom API. This API comes as a shared library that gets installed whenever you install the Lego Mindstorm software. It is available for Windows and Mac OS. This API is used by the Lego Mindstorm software so I assume it is well tested and will be maintained as long as there is a market for the NXT. It has the added benefit to completely abstract the actual communication link between the PC and the NXT. This mean that you no longer have to choose to work with USB or Bluetooth, the same code will work with both. While I would have likes to see the Fantom API available for Linux, this is not a big showstopper for me as I guess that most Mindstorm users use either Windows or Mac OS.

To use the Fantom API from Ruby, I need a way to map shared library functions to Ruby. This can be done using Ruby DLX, Ruby/DL or ruby-dl2. Again, none of these projects seems to be active at the moment. On the other hand, in Java land, there is JNA. Which is currently gathering some buzz and is being actively maintained. JNA allows to map shared library functions to Java interfaces is a really easy and concise way. Even if I want to ultimately use Ruby to talk to my robot, I don't care if my Ruby code must run under JRuby. So JNA is a good fit for me to get quickly started and not care to much about the low level communication stuff needed to connect to the NXT.

It is now time to start playing around and send some commands to my Lego robot.

So the first thing that need to be done, is to create the Java interface that will be used to map the Fantom API. Here is a minimalist version of it (it just contains the API function needed for this example):

Fantom.java

package treelaws.fantom;

import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.ByteByReference;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;
import java.nio.Buffer;

/**
 * Provides access to the Lego Mindstorm Fantom Library
 * @author Emmanuel Pirsch
 */
public interface Fantom extends StdCallLibrary {
    Fantom INSTANCE = (Fantom) Native.loadLibrary("fantom", Fantom.class);
    
    /**
     * 
     * @param searchBluetooth indicate if we also search bluetooth devices.
     * @param bluetoothSearchTimeout the timeout in second when searching bluetooth devices.
     * @param status OUT
     * @return
     */
    Pointer nFANTOM100_createNXTIterator(boolean searchBluetooth, int bluetoothSearchTimeout, Status status);
    /**
     * 
     * @param iNXTIterator
     * @param resourceName length must be at least 256.
     * @param status
     */
    void nFANTOM100_iNXTIterator_getName(Pointer iNXTIterator, byte[] resourceName, Status status);
    void nFANTOM100_iNXTIterator_advance(Pointer iNXTIterator, Status status);
    Pointer nFANTOM100_iNXTIterator_getNXT(Pointer iNXTIterator, Status status);
    void nFANTOM100_destroyNXTIterator(Pointer iNXTIterator, Status status);
    int nFANTOM100_iNXT_sendDirectCommand(Pointer iNXT, boolean requireResponse, Buffer commandBuffer, int commandBufferLength, byte[] responseBuffer, int responseBufferLength, Status status);
}

 

So now, I can easily connect to the NXT using something like :

String name= "T-NXT";

Status status= new Status(); Pointer iNXTIterator= fantom.nFANTOM100_createNXTIterator(true, 30, status); try { while(!Status.Statuses.NO_MORE_ITEMS_FOUND.equals(status.getStatus())) { byte[] resourceName= newResourceName(); fantom.nFANTOM100_iNXTIterator_getName(iNXTIterator, resourceName, status); System.out.println(asString(resourceName)); if (asString(resourceName).contains(name)) { Pointer iNXT= fantom.nFANTOM100_iNXTIterator_getNXT(iNXTIterator, status); if (Status.Statuses.SUCCESS.equals(status.getStatus())) { return iNXT; } else { throw new UnableToCreateNXTException(); } } fantom.nFANTOM100_iNXTIterator_advance(iNXTIterator, status); } } finally { if (iNXTIterator != null) { fantom.nFANTOM100_destroyNXTIterator(iNXTIterator, status); } }

 

And then start a robot program (already uploaded to the NXT) with :

String filename= "scorpion.rxe"
Status status= new Status();
byte[] filenameBytes= filename.getBytes();
ByteBuffer command= ByteBuffer.allocate(filenameBytes.length+1+1);
command.put((byte)0x00);
command.put(filenameBytes);
command.put((byte)0x00);
fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, false, command, command.capacity(), null, 0, status);

 

And this is just the beginning... Over the next few weeks (as time permit), I will add support to the rest of the Fantom API and add some nice Java classes to wrap this functionality and completely hide the Fantom API. Once this is done, I will release this code and start working on the JRuby side of things.

For completion, here are the NXT, Status and FantomUtils classes (The NXT class is the starting point of the Fantom API wrapper):

NXT.java

package treelaws.mindstorm;

import com.sun.jna.Pointer;
import java.nio.ByteBuffer;
import treelaws.fantom.Fantom;
import treelaws.fantom.Status;

import static treelaws.fantom.FantomUtils.*;

/**
 * 
 * @author Emmanuel Pirsch
 */
public class NXT {
    private static Fantom fantom= Fantom.INSTANCE;
    
    private String name;
    private Pointer nxtPointer;

    public NXT(String name) {
        this.name= name;
        this.nxtPointer= connect(name);
    }
    
    public void startProgram(String filename) {
        Status status= new Status();
        byte[] filenameBytes= filename.getBytes();
        ByteBuffer command= ByteBuffer.allocate(filenameBytes.length+1+1);
        command.put((byte)0x00);
        command.put(filenameBytes);
        command.put((byte)0x00);
        fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, false, command, command.capacity(), null, 0, status);
        System.out.println(status.getStatus().toString());
    }
    
    public void stopProgram() {
        ByteBuffer command= ByteBuffer.allocate(1);
        command.put((byte)0x01);
        fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, false, command, 1, null, 0, new Status());
    }

    private Pointer connect(String name) {
        Status status= new Status();
        Pointer iNXTIterator= fantom.nFANTOM100_createNXTIterator(true, 30, status);
        try {
            while(!Status.Statuses.NO_MORE_ITEMS_FOUND.equals(status.getStatus())) {
                byte[] resourceName= newResourceName();
                fantom.nFANTOM100_iNXTIterator_getName(iNXTIterator, resourceName, status);
                System.out.println(asString(resourceName));
                if (asString(resourceName).contains(name)) {
                    Pointer iNXT= fantom.nFANTOM100_iNXTIterator_getNXT(iNXTIterator, status);
                    if (Status.Statuses.SUCCESS.equals(status.getStatus())) {
                        return iNXT;
                    } else {
                        throw new UnableToCreateNXTException();
                    }
                }
                fantom.nFANTOM100_iNXTIterator_advance(iNXTIterator, status);
            }
        } finally {
            if (iNXTIterator != null) {
                fantom.nFANTOM100_destroyNXTIterator(iNXTIterator, status);
            }
        }
        throw new NXTNotFoundException();
    }
}

Status.java

package treelaws.fantom;

import com.sun.jna.Structure;
import java.util.EnumSet;
import java.util.HashMap;

/**
 *
 * @author Emmanuel Pirsch
 */
public class Status extends Structure {
    public int code;
    public byte[] filename= new byte[MAX_FILENAME_LENGTH];
    public int lineNumber;
    
    public Statuses getStatus() {
        return Statuses.fromCode(code);
    }
    
    static int MAX_FILENAME_LENGTH= 101;
    
    public enum Statuses {
        SUCCESS,
        FIRST(0),
        PAIRING_FAILED(5),
        BLUETOOTH_SEARCH_FAILED(6),
        SYSTEM_LIBRARY_NOT_FOUND(7),
        UNPAIRING_FAILED(8),
        INVALID_FILENAME(9),
        INVALID_ITERATOR_DEREFERENCE(10),
        LOCK_OPERATION_FAILED(11),
        SIZE_UNKNOWN(12),
        DUPLICATE_OPEN(13),
        EMPTY_FILE(14),
        FIRMWARE_DOWNLOAD_FAILED(15),
        PORT_NOT_FOUND(16),
        NO_MORE_ITEMS_FOUND(17),
        TOO_MANY_UNCONFIGURED_DEVICES(18),
        COMMAND_MISMATCH(19),
        ILLEGAL_OPERATION(20),
        BLUETOOTH_CACHE_UPDATE_FAILED(21),
        NON_NXT_DEVICE_SELECTED(22),
        RETRY_CONNECTION(23),
        POWER_CYCLE_NXT(24),
        FEATURE_NOT_IMPLEMENTED(99),
        FW_ILLEGAL_HANDLE(189),
        FW_ILLEGAL_FILENAME(190),
        FW_OUT_OF_BOUNT(191),
        FW_MODULE_NOT_FOUND(192),
        FW_FILE_EXISTS(193),
        FW_FILE_IS_FULL(194),
        FW_APPEND_NOT_POSSIBLE(195),
        FW_NO_WRITE_BUFFERS(196),
        FW_FILE_IS_BUSY(197),
        FW_UNDEFINED_ERROR(198),
        NO_LINEAR_SPACE(199),
        FW_HANDLE_ALREADY_CLOSED(200),
        FW_FILE_NOT_FOUND(201),
        FW_NOT_LINEAR_FILE(202),
        FW_END_OF_FILE(203),
        FW_END_OF_FILE_EXPECTED(204),
        FW_NO_MORE_FILES(205),
        FW_NO_SPACE(206),
        FW_NO_MORE_HANDLES(207),
        FW_UNKNOWN_ERROR_CODE(208),
        LAST(999);
        
        private int code;
        private static HashMap<Integer, Statuses> code_map= new HashMap<Integer, Statuses>();
        static {
            for(Statuses s : EnumSet.allOf(Statuses.class)) {
               code_map.put(s.code, s);
            }
        }
        Statuses() {
            this.code = 0;
        }
        Statuses(int code_offset) {
            this.code= -142000 - code_offset;
        }
        
        static Statuses fromCode(int code) {
            return code_map.get(code);
        }
    }
}

 

FantomUtils.java

package treelaws.fantom;

import com.sun.jna.Native;

/**
 *
 * @author Emmanuel Pirsch
 */
public class FantomUtils {
    static int DEFAULT_RESOURCE_NAME_ALLOCATION_SIZE = 256;
    
    public static final byte[] newResourceName() {
        return new byte[DEFAULT_RESOURCE_NAME_ALLOCATION_SIZE];
    }
    
    public static final String asString(byte[] s) {
        return Native.toString(s);
    }
}

2.12.2008

Making IRB work properly under Windows when using an international keyboard

Until today, I've been looking for a way to solve a problem I have running irb under Windows. The problem is that I'm using an international keyboard layout and keys like [, ], { and } (which are really useful in Ruby) could not be inputted in irb when readline is enabled.

I did some research on the web, but the solution did not worked for me (and it looks like I'm not the only one). A few weeks ago, I stopped looking for a solution as I decided to do my RoR work on a Linux Virtual machine. For some reason, I had to start working on Windows again. So today, I decided to dig a bit deeper. As the proposed solution used an .inputrc file, I investigated the format of the file and came across a post that described a problem using the Meta key shortcut (M-) in a .inputrc file. The response to the problem suggested to use "\e" instead of "M-". So I did that and guess what? it worked!

So putting everything together :

  1. Add the HOME environment variable => HOME=%USERPROFILE%
  2. Create a .inputrc file in your home folder (cd %USERPROFILE%) with the following content :
  3. "\e[": "["
    "\e]": "]"
    "\e{": "{"
    "\e}": "}"
    "\e\": "\"
    "\e|": "|"
    "\e@": "@"
    "\e~": "~" 
  4. For added completion/history management, create a .irbrc file in your home folder with the following content:
  5. require 'irb/completion'
    ARGV.concat([ "--readline", "--prompt-mode", "simple" ])
    
    module Readline
      module History
        LOG = "#{ENV['HOME']}/.irb-history"
    
        def self.write_log(line)
          File.open(LOG, 'ab') {|f| f << "#{line}\n"}
        end
    
        def self.start_session_log
          write_log("\n# session start: #{Time.now}\n\n")
          at_exit { write_log("\n# session stop: #{Time.now}\n") }
        end
      end
    
      alias :old_readline :readline
      def readline(*args)
        ln = old_readline(*args)
        begin
          History.write_log(ln)
        rescue
        end
        ln
      end
    end
    
    #Readline::History.start_session_log
    
    require 'irb/ext/save-history'
    IRB.conf[:SAVE_HISTORY] = 100
    IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb-save-history"
And you're done!

2.04.2008

Chased by the statically typed ghosts

It looks like statically typed ghosts are out there again. They like to chase the Poor little dynAmiCally typed Man. Most of the time, the ghosts run faster than the little Pac-Man but their bloat makes them crawl when they turn corners or go in tunnels. Little Pac-Man goal is to clear the level in the most efficient way. Little Pac-Man has some special features to help him mind his business. For each level that he has to go through, he can use specials pills. These pills allows him to open up the ghosts and mixes in some different behaviour into the ghosts. De-routed by this ability, the ghosts turns blue. Little Pac-Man understand that this power comes at a certain price, this is why he uses it sparingly and do not abuse it to eat the ghosts (unless he knows he can eat them all in one shot as this will make him score big-time!) as they will quickly return to their static behaviour. As we can see in the picture, it takes four (4) ghosts to take on Pac-Man.

1.29.2008

Another take on a jQuery.equalizeCols plugin

Using CSS, it is very difficult to create multiple columns with the same height. One must use Javascript to modify <div> height so multiple div share the same height.

This blog post offer a jQuery plugin to do that. However, this particular script cannot be applied many time to the same elements. I had to adjust the height of multiple column that have their content updated with some Ajax requests. So I needed to be able to readjust the height of the columns after the content has been updated. Using the script from the post, my column would grow after each update.

I did like how it was adjusting the height of the column. It add a div with the missing height to all the columns that needs to be adjusted.

So I came up with the following script. This script can be applied many times to the same elements without any adverse effects.

   1: jQuery.fn.equalizeCols = function() {
   2:     jQuery(this).filter(function() {return jQuery(this).find(".auto_fill").length == 0;}).each(function() {
   3:         jQuery(this).append(jQuery("<div class='auto_fill'></div>").height(0));
   4:     }).css("height", "auto");
   5:     
   6:     var max_height= 0;
   7:     jQuery(this).each(function() {
   8:         var self= jQuery(this);
   9:         var height= self.height() - self.find(".auto_fill").height();
  10:         max_height=height > max_height ? height : max_height;
  11:     });
  12:     
  13:     jQuery(this).each(function() {
  14:         var self= jQuery(this);
  15:         var height= self.height() - self.find(".auto_fill").height();
  16:         self.find(".auto_fill").height(max_height - height);
  17:     });
  18: }
 

On line 2-3, an empty <div> is added to each element that may have their height adjusted. The <div> has an initial height of 0 pixels. Note that then <div> will be added only if the containing <div> does not already have this empty <div>.

On line 6-11, the maximum height is calculated.

On line 13-17, the empty div height is adjusted so the total height of the containing div is equal to the previously calculated maximum height.

So to adjust the height of some elements, you just have to do this :

   1: $(".columns").equalizeCols();

1.18.2008

Fixing attachment_fu on Windows

Like many others, I've encountered issue when developing Rails applications using attachment_fu on Windows. After doing some research, I've come up with the following solution to the problem.

The problem has two parts :
  1. Size is not included in the list error message,
  2. Timeout error when uploading to S3.

Fixing "Size is not included in the list" error message

Some people have reported that there is a timing issue, when trying to get the file size, with Tempfile on Windows. It seems that the size of the file is not properly reported by Windows after writing data to it. Proposed solutions for this problem include :
  1. Sleeping in a loop as long as the file size is 0,
  2. Reading back the entire file in memory.

I think I found a better and less patchy solution for this issue: forcing the OS to flush the file to disk before reading it's size.
Here is the code to do it :
require 'tempfile'
class Tempfile
def size
if @tmpfile
@tmpfile.fsync # added this line
@tmpfile.flush
@tmpfile.stat.size
else
0
end
end
end

Doing a flush is not enough... flush will flush the Ruby buffer but the file may not be immediately written to the disk by the OS. Doing the fsync ensure that the file is written to disk by the OS before continuing. After that, Windows will properly report the actual file size.

Fixing the Timeout error when uploading to S3

This issue is related to opening files for reading on Windows. On Windows, you have to open the file in binary mode. So patching attachment_fu is simple :
require 'technoweenie/attachment_fu/backends/s3_backend'
module Technoweenie
module AttachmentFu
module Backends
module S3Backend
protected
def save_to_storage
if save_attachment?
S3Object.store(
full_filename,
(temp_path ? File.open(temp_path, "rb") : temp_data), # added , "rb"
bucket_name,
:content_type => content_type,
:access => attachment_options[:s3_access]
)
end

@old_filename = nil
true
end
end
end
end
end

I've also included a fix from someone else (which was not enough in itself to solve my S3 upload problem):
module Technoweenie
module AttachmentFu
# Gets the data from the latest temp file. This will read the file into memory.
def temp_data
if save_attachment?
f = File.new( temp_path )
f.binmode
return f.read
else
return nil
end
end
end
end

Wrapping it up

So I put all this code in lib/attachment_fu_patch.rb and required it in environment.rb.

Problem fixed!

Note, I did not test it on other OSes, but these fixes should not have any adverse effects.

AdSense Links