Sunday, December 13, 2009

Hibernate LazyInitializationException + Flex / BLazeDS AMF Serializations

On one of my previous posts I shared how I pruned hibernate proxied entities | collections with an annotation directive to avoid lots of DTO handling as a practical approach for prototyping,
that is one indirect way of tackling the issue of running across the culprit of LazyInitializationException when reflecting over proxied objects outside of Session context. The concept of configuring Hibernate to perform eager fetching of relationships is really a bad idea specially when object graphs could go too deep with massive n+1 hierarchies, moreover the 'session-in-view' perhaps may be what you not want as well. .
So in this particular instance with BlazeDS/Flex AMF binary serializations and using Spring + Hibernate for persistence I'd like to share one personal approach.
The verdict: you could do pruned DTO by hand or if dealing with raw hibernate objects then: (may not apply to all cases) but try to stay with lazy (default) as much as possible, when there's a need to walk through an object graph do it programaticaly or use 'fetch join' in hql queries as an alternative, if BlazeDS barks here and there with LazyInitException then below is a reference implementation approach that worked ok (when prototyping), so it'll instruct serializers via a custom bean proxy which registers Serializable type (or register more granular types as needed) to filter hibernate proxied beans | persistent collections when the marshaling happens in detach mode outside session.transactional context. You could take it a bit further to utilize Hibernate.initialize(obj) within current session context as it is explained here (look for Initializing collections and proxies).
Cheers!

package com.martin.orm.io;

import java.io.Serializable;
import flex.messaging.io.BeanProxy;
import flex.messaging.io.PropertyProxyRegistry;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.collection.AbstractPersistentCollection;

public class HbmLazyAmfFilterProxy extends BeanProxy {
private static final long serialVersionUID = 7538263979358789547L;
public HbmLazyAmfFilterProxy() {
super();
PropertyProxyRegistry.getRegistry().register(Serializable.class,this);
}

@Override
public Object getValue(Object o, String p) {
Object val = super.getValue( o , p );
if ( ( val instanceof HibernateProxy) ||
( val instanceof AbstractPersistentCollection ) ) {
val = null;
}
return val;
}
}

Or, a more generic bean utility dto reflective populator could be something like this:



//////////////////////////////////////////////////////////////////////////
public void filter( final T orig , final T dest ) throws Exception {
final PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();
final PropertyDescriptor[] origDescriptors =
propertyUtilsBean.getPropertyDescriptors(orig);
for (int i = 0; i < origDescriptors.length; i++) {
final String name = origDescriptors[i].getName();
if ("class".equals(name)) continue;
if ( propertyUtilsBean.isReadable ( orig , name ) &&
propertyUtilsBean.isWriteable( dest , name ) ) {
try {
final Object value =
propertyUtilsBean.getSimpleProperty( orig , name );
if ( ( value instanceof HibernateProxy) ||
( value instanceof AbstractPersistentCollection ) ) {
continue; // filter
}
BeanUtils.copyProperty( dest , name , value );
} catch (final NoSuchMethodException e) {
// deal with it
}
}
}
}

Monday, December 7, 2009

Thread-safe MapCache with Reaper

Sometimes when writing middle-ware applications there's a need for a simple thread safe parameterized mem map cache with eviction reaper, generally non locking on retrieval and with internal wrapper class. By reading the latest java.util.concurrent api doc there is a rich list of collections built with concurrency in mind, I could not find one with an eviction reaper built-in so today I assembled one and here's a reference implementation I'd like to share.

package com.martin.cache;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Externalizable;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;

/**
* Thread safe simple parameterized map cache with eviction reaper,
* generally non locking on retrieval + internal <V> wrapper class.
* @author MartinZ 12/07/2009
* @param K key type
* @param V value type
*/
public class MapCache< K , V > {

private class Reaper implements Runnable {
public void run() {
evict();
}
}

public interface EvictionListener< K > {
void evicted( K key );
}

/////////////////////////////////////////////////////////////////////
public static final long DEFAULT_EVICT_INTERVAL_MS = (30 * 1000);
public static final long DEFULT_15MINUTES_CACHE_MS = (15 * 60 * 1000);
/////////////////////////////////////////////////////////////////////

private final ConcurrentHashMap< K , Value<V> > map;
private final ScheduledThreadPoolExecutor timer;
private final HashSet<EvictionListener<K>> listeners;
private final long interval;

public MapCache() {
this(DEFAULT_EVICT_INTERVAL_MS);
}

public MapCache( Long tm ) {
interval = (tm==null)?DEFAULT_EVICT_INTERVAL_MS:tm;
map = new ConcurrentHashMap<K,Value<V>>();
timer = new ScheduledThreadPoolExecutor(1);
listeners = new HashSet<EvictionListener<K>>();
}

public void addEvictionListener(EvictionListener<K> l) {
listeners.add(l);
}
public void removeEvictionListener(EvictionListener<K> l) {
listeners.remove(l);
}

public int getSize() {
return map.size();
}

public void clear() {
map.clear();
}

public void start() {
timer.scheduleWithFixedDelay(
new Reaper(),0,interval,TimeUnit.MILLISECONDS
);
}

public void stop() {
timer.shutdown();
}

/**
* @param key != null
* @param val != null
* uses default 15 minute time to live since last get
*/
public V put( K key , V val ) {
return put( key , val , DEFULT_15MINUTES_CACHE_MS );
}

/**
* @param key != null
* @param val != null
* @param time ms to keep an entry alive. 0 = never evict.
*/
public V put( K key , V val , long tm ) {
if(key == null || val == null ) return null;
Value<V> value = new Value<V>( val , tm );
Value<V> retval = map.put( key , value );
return (retval != null ? retval.value : null);
}

public V get(K key) {
if(key == null) return null;
Value<V> val = map.get(key);
if(val == null) return null;
val.refreshTimestamp();
return val.value;
}

public Value<V> getEntry( K key ) {
if(key == null) return null;
Value<V> val = map.get(key);
if(val == null) return null;
val.refreshTimestamp();
return val;
}

public V remove(K key) {
Value<V> val = map.remove(key);
return (val != null ? val.value : null);
}

public Set<Map.Entry<K,Value<V>>> entrySet() {
return map.entrySet();
}

private void evict() {
for( Iterator<Map.Entry<K,Value<V>>> it =
map.entrySet().iterator(); it.hasNext();) {
Map.Entry<K,Value<V>> entry = it.next();
Value<V> val = entry.getValue();
if(val != null) {
if( (val.timeout > 0) &&
(System.currentTimeMillis() >
(val.insertion + val.timeout))) {
it.remove(); // rm from iterator
notifyListeners( entry.getKey() );
}
}
}
}

private void notifyListeners( K key ) {
for(EvictionListener<K> l: listeners) {
try { l.evicted(key);
} catch(Throwable t) { }
}
}

public String toString() {
StringBuilder sb=new StringBuilder();
for(Map.Entry<K,Value<V>> entry: map.entrySet()) {
Value<V> val=entry.getValue();
sb.append(entry.getKey()).append(": ");
sb.append(entry.getValue().getValue());
sb.append(" (expiration tm.ms: ");
sb.append(val.getTimeout()).append(")\n");
}
return sb.toString();
}

///////////////////// internal wrapper ////////////////////
public static class Value< V > implements Externalizable {
private V value;
private long timeout;
private transient long insertion=System.currentTimeMillis();
private static final long serialVersionUID = 1100000000001L;

public Value( V value , long timeout ) {
this.value = value;
this.timeout = timeout;
}

public V getValue() { return value; }
public long getTimeout() { return timeout; }
public long getInsertionTime() { return insertion; }

public void refreshTimestamp() {
if( timeout > 0 )
insertion=System.currentTimeMillis();
}

public void writeExternal(ObjectOutput out)
throws IOException {
out.writeLong(timeout);
out.writeObject(value);
}

@SuppressWarnings("unchecked")
public void readExternal(ObjectInput in)
throws IOException,ClassNotFoundException {
timeout = in.readLong();
value = (V) in.readObject();
insertion = System.currentTimeMillis();
}
}
}


Sunday, November 15, 2009

Distributed computing concepts

Whether load-balancing, fail-over, replication, distributed FS, cloud capabilities, etc, for clustering application servers nowadays, the ultimate goal is to improve quality of highly available + scalable service with less latency and fault tolerance features.
By looking under the hood on how J2EE servers do it, I came across JGroups library which is the toolkit JBoss uses per instance.
JGroups is a library for reliable multicast communication, it provides a flexible protocol stack which allows developers to adapt to specific application requirements and specific network topology characteristics, its main features:
  • Transport protocols: UDP (IP Multicast), TCP, JMS
  • Fragmentation of large messages
  • Reliable unicast and multicast message transmission. Lost messages are retransmitted
  • Failure detection: crashed members are excluded from the membership
  • Ordering protocols: Atomic (all-or-none delivery), Fifo, Causal, Total Order
  • Membership
  • Encryption
With this lower level reliable communication library, developers could then implement sophisticated middle-ware applications with distributed computing based features, just to mention couple ones that come to my mind: memcache ( distributed map ), map-reduce / fork-join model for task execution in a private cluster/cloud, and many more capabilities, here's a partial reference source implementation that I experimented with:

// create
org.jgroups.JChannelFactory factory = new JChannelFactory("udp.xml"); //tcp.xml etc
org.jgroups.JChannel channel = (JChannel) factory.createChannel();
channel.setOpt( Channel.AUTO_RECONNECT , true );
channel.setOpt( Channel.AUTO_GETSTATE , true );
...
// start
channel.connect( "CLUSTER-NAME" );
org.jgroups.blocks.PullPushAdapter adapter = new PullPushAdapter(channel,this,this);
channel.getState(null,10000); // 10 secs conn tolerance
...
// stop
channel.disconnect();
channel.close();

// simple serialized user defined type msg send
ClusterMsg m = new ClusterMsg( action );
byte[] buffer = org.jgroups.util.Util.objectToByteBuffer( m );
Message msg = new Message( dest , src , buffer , 0 , buffer.length );
channel.send( msg );

// from Interface org.jgroups.MessageListener
public byte[] getState() { ... }
public void receive( Message msg ) {
Serializable s = (Serializable)
org.jgroups.util.Util.objectFromByteBuffer(msg.getBuffer());
if( s instanceof ClusterMsg ) {
ClusterMsg cmsg = (ClusterMsg) s;
switch( cmsg.getAction() ) {
// do something
}
}
}
public void setState( byte[] newState ) { ... }

// from Interface org.jgroups.MembershipListener
public void viewAccepted( org.jgroups.View currentView ) { ... }


parallelism pseudo-code concept [divide-conquer | map-reduce | fork-join]

Result solve(Problem problem) {
if (problem is small enough)
result = problem.solve()
else {
split problem into independent parts
fork new subtasks to solve each part
join all subtasks
compose result from sub-results
}
}

In general, split tasks could then be allocated | distributed to available nodes for execution, the middle-ware could be able to perform a network node pre-inspection to determine its hardware capabilities of each node corresponds for computing or data storage based on their processor, memory, HD power, etc. Thus depending on what a certain task does, allocation should be in function of what a certain node can provide at its best.

Here's a static img for illustration of my simple PoC experiment, I'll spawn 3 instances (same host) with one being the master node, and two slave ones, there's a simple map being serialized and acting as a simple memcache implementation as well as a simple distributed task execution capability, I created a simple web UI which shows all nodes in cluster along with the protocol stack configured, and one can run tasks, send sessages to all nodes and shut-down a targeted node or all cluster in view:

Hard to see it all working from img, but the main concept is there and functional, now the cool thing is that if either node goes down (including master one) the whole cluster is aware of it and the state is being serialized and distributed upon node bootstrap via the org.jgroups.MessageListener.
No need to reinvent the wheel here, as we know most containers have clustering built-in and there are already middle-ware open source projects: GridGain , Hadoop and other products in the arena such as: Coherence , Terracota , etc.

Finally, with the new explosion of cloud services such as bottom up: IaaS, PaaS, SaaS with flexible 'pay as you need' on demand features, we can see that there's a new trend to provide cloud services in which under the covers it is dynamic provisioning of distributed computing platform at its core.

Friday, September 18, 2009

Windows installer for a java swing app

Early this year I contracted a very small project where the desktop java swing app needed to be installed and run on MS-OS(s), since I used a cross-platform java executable wrapper: launch4j to generate an .exe I could then automate an installer with features like: un-installer, detect core dependencies such as JRE and install it not present as well as Adobe reader, registry settings, readme file, logo splash, etc, etc, and more.

Simple cmd compiler invokation as:
makensis.exe /V4 InstallScriptSample.nsi

Resolution:
NSIS did the job, scriptable installer with rich set of options. Below is an example configuration I used, take it as a guideline and modify accordingly for your needs.
Cheers!

;----------------------------------------------------
; File: InstallScriptSample.nsi
; Installer using -> http://nsis.sourceforge.net
;----------------------------------------------------
; Note replace "MyApp" with desired app name
;----------------------------------------------------

SetCompressor /SOLID lzma

!include "MUI.nsh"
!insertmacro MUI_PAGE_WELCOME
#!insertmacro MUI_PAGE_DIRECTORY
#!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!define MUI_FINISHPAGE_NOAUTOCLOSE
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_CHECKED
!define MUI_FINISHPAGE_RUN_TEXT "Start MyApp"
!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLink"
!define MUI_FINISHPAGE_SHOWREADME_CHECKED
!define MUI_FINISHPAGE_SHOWREADME $INSTDIR\README.txt
!insertmacro MUI_PAGE_FINISH

;----------------------------------
!insertmacro MUI_LANGUAGE "English"
;----------------------------------

#Page directory
#Page components
#Page instfiles

;--------------------------------
; Main Install settings
;--------------------------------

Name "MyApp"
Caption "MyApp Installer | Please wait!"
Icon "icon.ico"
#InstallDir "$PROGRAMFILES\MyApp"
InstallDir "c:\MyApp"
OutFile "Setup.exe"
ShowInstDetails show
ShowUninstDetails show
AutoCloseWindow true
RequestExecutionLevel admin
BrandingText "MyApp v1.0"
AllowRootDirInstall false
InstallColors 00FF00 000000
InstallDirRegKey HKCU "Software\MyApp" ""
InstallDirRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MyApp" "UninstallString"

#---------------- installer section start ----------------
section

Call Includes
Call CheckJRE
Call InstallJre
Call InstallAdobe

Success:
Call CreateShortCutsAndUninstaller
Goto Done

Done:

# messageBox MB_OK "Installation was successful @ $INSTDIR $\r$\n \
# Please use desktop shortcut [MyApp] to start-up application."

sectionEnd

#---------------- installer section end --------------------


#---------------- uninstaller section start ----------------

section "uninstall"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MyApp" "DisplayName" ""
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MyApp" "UninstallString" ""
DeleteRegKey /ifempty HKCU "Software\MyApp"
delete "$DESKTOP\MyApp.lnk"
delete "$STARTMENU\MyApp.lnk"
delete "$SMPROGRAMS\MyApp.lnk"
rmdir /r $INSTDIR

sectionEnd

#---------------- uninstaller section end ------------------

Function LaunchLink
ExecShell "" "$INSTDIR\MyApp.exe"
FunctionEnd


;--------------------------------
Function Includes

WriteRegStr HKCU "Software\MyApp" "" $INSTDIR

SetOverwrite on

SetOutPath $INSTDIR

File run.cmd
File MyApp.exe
File README.txt
File QA.txt

SetOutPath $INSTDIR\config
File .\config\config.xml
File .\config\hbm.cfg.xml
File .\config\log4j.xml

SetOutPath $INSTDIR\db
File .\db\HSQLDB.app.log
File .\db\HSQLDB.backup
File .\db\HSQLDB.data
File .\db\HSQLDB.properties
File .\db\HSQLDB.script

SetOutPath $INSTDIR\img
File /r ".\img\*.*"

SetOutPath $INSTDIR\lib
File /r ".\lib\*.*"

SetOutPath $INSTDIR\rpt
File /r ".\rpt\*.*"

SetOutPath $INSTDIR\inc
File .\inc\jre-6u11-windows-i586-p.exe
File .\inc\AdbeRdr90_en_US.exe

SetOverwrite off

FunctionEnd

;--------------------------------

Function InstallJre

ExecWait '$INSTDIR\inc\jre-6u11-windows-i586-p.exe /quiet' $0

FunctionEnd

;--------------------------------

Function InstallAdobe

ExecWait '$INSTDIR\inc\AdbeRdr90_en_US.exe /sAll /rs' $0

FunctionEnd

;--------------------------------

Function CreateShortCutsAndUninstaller

# push $0
# FileOpen $0 $INSTDIR\MyApp.cmd w
# FileWrite $0 "cd $INSTDIR\bin$\r$\n"
# FileWrite $0 "run.cmd$\r$\n"
# FileClose $0
# pop $0

CreateShortCut "$DESKTOP\MyApp.lnk" "$INSTDIR\MyApp.exe" \

"" "$INSTDIR\img\icon.ico" 0 SW_SHOWNORMAL CONTROL|SHIFT|F5 "MyApp"



CreateShortCut "$SMPROGRAMS\MyApp.lnk" "$INSTDIR\MyApp.exe" \

"" "$INSTDIR\img\icon.ico" 0 SW_SHOWNORMAL CONTROL|SHIFT|F5 "MyApp"



CreateShortCut "$STARTMENU\MyApp.lnk" "$INSTDIR\MyApp.exe" \

"" "$INSTDIR\img\icon.ico" 0 SW_SHOWNORMAL CONTROL|SHIFT|F5 "MyApp"



WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MyApp" "DisplayName" "MyApp (Uninstall)"

WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MyApp" "UninstallString" "$INSTDIR\Uninstall.exe"



writeUninstaller "$INSTDIR\Uninstall.exe"



FunctionEnd



;--------------------------------

Function CheckJRE

push $0

push $1


ClearErrors

ReadRegStr $0 HKLM "SOFTWARE\JavaSoft\Java Runtime Environment" "CurrentVersion"

ReadRegStr $1 HKLM "SOFTWARE\JavaSoft\Java Runtime Environment\$0" "JavaHome"

StrCpy $1 "$1\bin\java.exe"

pop $0

exch $1

FunctionEnd

;--------------------------------
; eof
;--------------------------------

Cross-platform java executable wrapper

Early this year I needed to wrap a small project java swing app into a native MS-OS executable, after deliberating on few products out there I chose launch4j
Resolution:
It did the job, it has easy to use streamlined UI which generates an xml configuration file which then you can reuse to rebuild exe. Below is an example configuration I used, take it as a guideline and modify accordingly for your needs.
Cheers!





true
gui
lib\org.myapp_1_0_0.jar
C:\tmp\MyApp.exe
Java Runtime Error
config/log4j.xml config/config.xml
.
normal
http://java.com/download
http://java.sun.com
false
false

icon.ico

MyApp
MyApp


org.myns.myproj.MyAppMain
lib/org.myapp_1_0_0.jar
lib/commons-collections-3.1.jar
lib/commons-io-1.4.jar
lib/commons-lang.jar
lib/commons-logging-1.1.jar



C:\Program Files\Java\jre6
1.6.0_11

preferJre
128
256


img\logo.bmp
true
60
true


An error occurred while starting the application.
This application was configured to use a bundled Java Runtime Environment but the runtime is missing or corrupted.
This application requires a Java Runtime Environment
The registry refers to a nonexistent Java Runtime Environment installation or the runtime is corrupted.
An application instance is already running.


Monday, August 31, 2009

JVM Agent - Class Spy Utility

I've been experimenting recently on a PoC java agent utility which dumps high & low level details for any class loaded by JVM at any point of time.
It does not perform any bytecode instrumentation manipulation / alteration but rather loads all classes available / loaded by the JVM in context into a UI in which it organizes classes by package namespace into a hierarchical tree structure.
This utility comes handy when you need to introspect class bytecode blueprint signatures that may be in question at runtime, similar to the javap utility that ships with jdk.

According to the specs the agent jar must contain:

MANIFEST.MF
...
Agent-Class: org.martin.agent.jvm.JvmAgent
Premain-Class: org.martin.agent.jvm.JvmAgent
Boot-Class-Path: jvmagent_1_0_0.jar
Can-Redefine-Classes: false
Can-Retransform-Classes: false
(note that redefinitions + transformations are not set since neither is needed here)

and method premain (similar to a 'main') signature hook is as follows:
org.martin.agent.jvm.JvmAgent
public static void premain(String agentArgs, Instrumentation inst) { ... }

once we have instrumentation access we can then:
[1] register a custom class transformer

instrumentation.addTransformer( new MyClassLoadTransformer() );
...
class MyClassLoadTransformer implements ClassFileTransformer {
public byte[] transform(
ClassLoader loader,
String fullyQualifiedClassName,
Class classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classofileBuffer) throws IllegalClassFormatException {
// illustration here!
}
}

[2] and invoke the instrumentation method:
final Class[] clsArr = instrumentation.getAllLoadedClasses();
which feeds the UI by representing pkgs hierarchicaly in a tree:

Once a desired class node is selected, it prompts for high / low level detail?, either selection will dump its details on to System.out, below is a console sample of output:

Instructions:
[1] download jar (only 22+k) and place it either in /lib or /bin of your app
[2] Then you just need to add -javaagent parameter such as:
java -javaagent:jvmagent_1_0_0.jar ...(more options here)
(depending on where it is : relative path -javaagent:../lib/jvmagent_1_0_0.jar , so configure accordingly!)

Agent will launch and load a UI with an initial draw, if your bootstrap process takes a bit longer then at any point you can press refresh button to re-load jvm state, the jvm + your application will be dynamically loading on demand classes based on the dependencies needed, so refresh accordingly.

Bytecode class dump disassembler credits.

Conclusion
Now imagine taking this concept further, profilers per say, or by using bytecode manipulation libraries such as asm or javassist an example is javarebel which allows reloading classes dynamically at runtime (even if making significant changes) without needing to restart container. I do have a working version agent where does hotswap (as long as new bytecode respects same signature and only changes within method implementation), however JVM can become very stale if you violate that limitation (at least up until now in jdk 6+).

Till next time, cheers!

Tuesday, June 23, 2009

Java shell

I've been having fun writting a utility java shell, I use cygwin on windows but I needed something more integrated with java such has building class paths, invoking java/ant/groovy /os scripts, scanning in context class loader certain classes | @Annotations, etc, etc. And also when I switch to Linux there it is for me as well. Some basic commands are os file system handling such as ls / rm / mk / grep / etc.
Internals use java.io.Console and a custom annotation:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Command {
String name() default "";
String description() default "";
String abbrev() default "";
String def() default "";
}
So anytime I need a new command I can annotate it like below and instantly it becomes available via method reflection invocation:

@Command(name="cmd", description="sample cmd", abbrev=".", def="?")
public void cmd( String str ) throws Exception {
...
}

Here's an example below for few common file system cmds: ls , lsr (just like ls but with recursive capabilities building a tree)

Note that commands can be chained 'piped' taking res output as input like in *nix such as:
lsr src | grep '06-11'
And also io redirect '>' result:
ls . | lsr src | grep '06-11' > result.log
Or io redirect appended '>>'
ls . | lsr src | grep '06-11' >> result.log
etc
I'll have to admit that grep command which takes regular expressions to search is my favorite command of them all (specially in windows cuz u know y), it's so powerfull when combined with others.
Here's a list of the help for all cmds implemented so far (keeps growing by the day)

Today I included a disassembler via 'javap' which scans in context class loader via a package prefix and shows public class signatures, it comes handy when you need to introspect bytecode loaded and being currently invoked. It also can open a script file and sequentially execute cmds but for that there's a groovy cmd which I can invoke and run groovy scripts with dyn-language richness, one example is I run [def ant = new AntBuilder()] via groovy ;-).

Now one thing I hate to do is walking by hand on file system typing long paths, so I added a simple 'fs' cmd which launches a swing UI which facilitates file system operations, I hooked most common cmds in a menu and results are directed to console:

I know it looks like a toy but it surely comes handy on day by day con$ole operations, heck I don't know what I'll add next but it sure saves the day, I love being productive with tools that do things my way ;-) and I can easily throw a new feature at it in a matter of minutes.

One final trick (for windows) is that you can invoke it directly from windows file explorer anytime you right-click on a folder (shows on menu):
file: reg.reg

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Directory\shell\jell]
@="jell"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Directory\shell\jell\command]
@="cmd.exe /k \"cd %L\"&jell"

I'll probably pack it once it becomes stable so I can share as GA, that is if anybody finds this useful,
Well, till next time, cheers...