martes, 2 de febrero de 2010

replacing text on files recursively over directories

In very special ocations, when I'm programming, I need to recursively replace text of all files in directories. For example, you want to recursively replace the string "import org.foo.bar.Class1" with "org2.foo.bar.Class1" in all .java files, in a java source folder. I know this can be done with a relatively simple shell (bash) script using sed, grep, etc unix commands, but what about systems that doesn't have bash? Also, there is a program called rpl (http://www.laffeycomputer.com/rpl.html) which purpose is this but, unfortunately, didn't worked for me (I compiled it on my machine, but the command fails with an error message (couldn't allocate memory or simply doesn't finnish its execution). So I implemented this functionality in java. I tested it on large source folders and works quick and nice. I hope this can be helpful for somebody:



package org.sgx.utils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
* replace strings with new strings in multiple text files recursively over
* directories and supports limiting the search to specific file names.
*
* @author sgurin
*
*/
public class RPL {
public static void copyDirAndReplaceRecursively(File src, File dest,
String filter, String str, String replacement)
throws FileNotFoundException, IOException {
if (src.isDirectory()) {
dest.mkdirs();
File[] childs = src.listFiles();
for (int i = 0; i < childs.length; i++) {
File child = childs[i];
File destChild = new File(dest.getAbsolutePath()
+ File.separator + child.getName());
copyDirAndReplaceRecursively(child, destChild, filter, str,
replacement);
}
} else {
if (src.getName().contains(filter)) {
copyFileAnReplacetext(src, dest, str, replacement);
} else {
copyFileAnReplacetext(src, dest, null, null);
}
}

}

public static void copyFileAnReplacetext(File srcFile, File destFile,
String str, String replacement) throws FileNotFoundException,
IOException {
StringBuffer sb = new StringBuffer();
InputStream is = new FileInputStream(srcFile);
readFile(is, sb);
is.close();
String s = sb.toString();
if (str != null && replacement != null)
s = s.replace(str, replacement);
FileOutputStream os = new FileOutputStream(destFile);
os.write(s.getBytes());
os.close();
}

public static void readFile(InputStream in, StringBuffer sb)
throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line = null;
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();
}

// test
public static void main(String[] args) {
try {
File srcDir = new File(
"/home/sebastian/desarrollo/portal4/blogsJavas/src2"), destDir = new File(
"/home/sebastian/desarrollo/portal4/blogsJavas/src");
String str = "com.liferay.portlet.blogs", repl = "org.sgx.portlet.circulares";
FSUtils.copyDirAndReplaceRecursively(srcDir, destDir, ".java", str,
repl);

} catch (Exception e) {
e.printStackTrace();
}
}
}

lunes, 18 de enero de 2010

Problem with keyboard event handling in java, both with swing and swt

I will try to explain, with my bad english, a recent problem that I'm having with keyboard event handling in java using both swing and swt GUI libraries. Basically the problem is that there is not a way, both in swing and swt, of knowing the status of the keyboard keys. Basically the problem is that, while the user is still pressing the key (and never release it), keyrelease events are being throwed (!) and for that it is impossible to get the real keyrelease event. This problem only manifest in programs that need to use advance keyboard controlling; think for example in a piano simulator in which the user uses the keyboard keys as if it where a piano's keys. When the user presses a key a note turn on, and it is turned off only when the user releases it. The user can hold the key an arbitrary amount of time without release it and the note must be turned on in all that time.

I will present the problem with two simple GUI snippets, one for swing and other for swt, and I will compile and execute them both in windows and linux, using sun jdk version 1.6_13. The snippets are a window that listens to keypressed and keyreleased events and prints a message when the event notification is received. In all the tests, the user will press a key and will keep it pressed for some time (more than a second) and then release it. So the correct output for this "use case" would be only two messages "keyPressed" and
"keyReleased" in the output console. The only toolkit and OS combination that worked as spected was swt+linux. In the rest of the combinations, several "keyPressed" and
"keyReleased" are repeated while the user is keeping the key presssed.











SWT snippetSWING snippet

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class KeyPressTest extends org.eclipse.swt.widgets.Dialog {
    
    private Shell dialogShell;
    
    public KeyPressTest(Shell parent, int style) {
        super(parent, style);
    }
    
    public void open() {
        try {
            Shell parent = getParent();
            dialogShell = new Shell(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL);
            dialogShell.addKeyListener(new KeyListener() {
                @Override
                public void keyReleased(KeyEvent arg0) {
                    System.out.println("keyReleased");
                }
                @Override
                public void keyPressed(KeyEvent arg0) {
                    System.out.println("keyPressed");
                }
            });
            dialogShell.setLayout(new FormLayout());
            dialogShell.layout();
            dialogShell.pack();
            dialogShell.setLocation(getParent().toDisplay(100, 100));
            dialogShell.open();
            Display display = dialogShell.getDisplay();
            while (!dialogShell.isDisposed()) {
                if (!display.readAndDispatch())
                display.sleep();
            }
            } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        try {
            Display display = Display.getDefault();
            Shell shell = new Shell(display);
            KeyPressTest inst = new KeyPressTest(shell, SWT.NULL);
            inst.open();
            } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

public class KeyPressTest extends javax.swing.JFrame {
    public KeyPressTest() {
        super();
        initGUI();
    }
    private void initGUI() {
        try {
            setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
            this.addKeyListener(new KeyListener() {
                @Override
                public void keyTyped(KeyEvent e) {
                }
                @Override
                public void keyReleased(KeyEvent e) {
                    System.out.println("keyReleased");
                }
                @Override
                public void keyPressed(KeyEvent e) {
                    System.out.println("keyPressed");
                }
            });
            pack();
            setSize(400, 300);
            } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                KeyPressTest inst = new KeyPressTest();
                inst.setLocationRelativeTo(null);
                inst.setVisible(true);
            }
        });
    }
}


And the results, for swing and swt in combination with windows and linux are:















windowslinux
swing

keyPressed
keyReleased
keyPressed
keyReleased
keyPressed
keyReleased
....
keyPressed
keyReleased

keyPressed
keyReleased
keyPressed
keyReleased
keyPressed
keyReleased
....
keyPressed
keyReleased
SWT

keyPressed
keyReleased
keyPressed
keyReleased
keyPressed
keyReleased
....
keyPressed
keyReleased

keyPressed
keyPressed
keyPressed
keyPressed
keyPressed
...
keyPressed
keyPressed
keyReleased


As I understand, THIS IS A BUG, because the behaviour does not apply the documentation. For example, this is the more relevant paragraph about keypressed and keyreleased events of swing (java.awt.event.KeyEvent javadoc):

"Key pressed" and "key released" events are lower-level and depend on the platform and keyboard layout. They are generated whenever a key is pressed or released, and are the only way to find out about keys that don't generate character input (e.g., action keys, modifier keys, etc.).

and this is not true because keyreleased events are generated without a key being released

Note that the only different situation is swt in linux. Imho it is still incorrect (because several keypressed events are fired but only one actually happens) but in this case we can we can know when the key is really releaed. In the other scenarios it is impossible.

IMHO, a cause of this problem coud be that swt and swing designers have modeled the keyboard event system thinking on text and not in other keyboard usage. In the case of swt that heavily relies on underlying plafform (gtk in linux, winapi in windows), I think the event stuff is a responsability of the uderlying platform and it is there where we note the defferencies.

So my question for java developers reading this is if there exists a way of being correctly notified when a keyrelease event occurs using swing or swt. Other question could be if there exists other library for java which handles this correctly because now, I havent figure out how to do this simple task in java ;(.

If you are a java programmer and want a workaround, I think the only one is the following. Since, while the key is pressed the keypressed event is fired regularly, the way we have to know that a key is released is when keypressed event is not firing anymore for that key. Note that this will be incorrect (because it is not really a keyrelease event) and can be expensive to implement because it because it require, for each key, a listener thread that triggers the fake keyrelease event when it detects no more keypressed. My proposal is the following keylistener (KeyListener2 inner class):


package org.sgx.javatests.keypress.swingAll;

import java.awt.Component;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.HashMap;
import java.util.Map;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

public class SwingSollutionAllClasses {

/**
* this class is responsible for maintaining a key status map
* @author sebastian
*
*/
static class InputManager implements KeyListener {
//sgurin : þe last keypressed and keyreleased events registered for each key
Map lastKeyPressedEvents = new HashMap();
Map lastKeyReleasedEvents = new HashMap();
//one for each ascii character.
public boolean[] key_state_up = new boolean[256]; //true if pressed
public boolean[] key_state_down = new boolean[256]; //true if not pressed
//a string used as a buffer by widgets or other text input controls
private String keyCache = "";
public InputManager(){
for (int i = 0; i < key_state_up.length; i++) {
key_state_up[i]=true;
key_state_down[i]=false;
}
}
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
if( code >= 0 && code < 256 ) {
key_state_down[code] = true;
key_state_up[code] = false;
lastKeyPressedEvents.put(code, e);
}
}
public void keyReleased(KeyEvent e) {
int code = e.getKeyCode();
if( code >= 0 && code < 256 ) {
key_state_up[code] = true;
key_state_down[code] = false;
lastKeyReleasedEvents.put(code, e);
}
}
public void keyTyped(KeyEvent e) {
keyCache += e.getKeyChar();
}
public boolean isKeyDown( int key ) {
return key_state_down[key];
}
public boolean isKeyUp( int key ) {
return key_state_up[key];
}
}

/**
* this class is a new KeyListener adapter that corrects the default keyreleased
* notification policy (repeatedly notifies keypressed and keyreleased events
* when a key is pressed and not released for a while).
*
* note that this is a heavy listener, use only at special cases (when you need different behaviour for keyreleased event handling)
* it uses an InputManager that mantains the key status and starts a thread that checks each key status and notify changes
*
* @author sgurin
*
*/
static abstract class KeyListener2 implements KeyListener {

KeyChecker checkerThread ;
public KeyListener2(Component target) {
super();
checkerThread=new KeyChecker(this, target);
checkerThread.start();
}
public void destroy() {
checkerThread.stopChecking();
}

static class KeyChecker extends Thread implements KeyListener {
private static final long SLEEP_AMOUNT =50;
InputManager iman;
Component target;
volatile private boolean stoped;
public boolean[] last_key_state_up=null, last_key_state_down=null;
KeyListener2 listener;

public KeyChecker(KeyListener2 listener, Component target){
iman = new InputManager();
this.target = target;
this.listener = listener;
target.addKeyListener(iman);
target.addKeyListener(this);
}

@Override
public void run() {
last_key_state_down=cloneArray(iman.key_state_down);
last_key_state_up = cloneArray(iman.key_state_up);
while(!stoped) {
try {
Thread.sleep(SLEEP_AMOUNT);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < last_key_state_up.length; i++) {
if(last_key_state_up[i] && iman.key_state_down[i])
listener.keyPressed(iman.lastKeyPressedEvents.get(i));
if(last_key_state_down[i] && iman.key_state_up[i])
listener.keyReleased(iman.lastKeyReleasedEvents.get(i));
}
last_key_state_down=cloneArray(iman.key_state_down);
last_key_state_up = cloneArray(iman.key_state_up);
}
}
private boolean[] cloneArray(boolean[] a) {
if(a==null)
return null;
boolean [] r = new boolean[a.length];
for (int i = 0; i < r.length; i++)
r[i]=a[i];
return r;
}
public void stopChecking() {
stoped=true;
}
@Override
public void keyPressed(KeyEvent e) {}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
listener.keyTyped(e);
}
}
}

/**
* swing test
*/
static class KeyPressTest extends javax.swing.JFrame {
public KeyPressTest() {
super();
initGUI();
}
private void initGUI() {
try {
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
new KeyListener2(this) {
@Override
public void keyTyped(KeyEvent e) {}
@Override
public void keyReleased(KeyEvent e) {
System.out.println("keyReleased");
}
@Override
public void keyPressed(KeyEvent e) {
System.out.println("keyPressed");
}
};
pack();
setSize(400, 300);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
KeyPressTest inst = new KeyPressTest();
inst.setLocationRelativeTo(null);
inst.setVisible(true);
}
});
}
}
public static void main(String[] args) {
KeyPressTest.main(args);
}
}



Related information I found:
http://stackoverflow.com/questions/1736828/how-to-stop-repeated-keypressed-keyreleased-events-in-swing
http://groups.google.com/group/comp.lang.java.gui/browse_thread/thread/e215f9729154511e
http://gpsnippets.blogspot.com/2008/03/keyboard-input-polling-system-in-java.html

sábado, 20 de junio de 2009

java2script SWT portlets

author: Sebastián Gurin
Añadir vídeo
In this document I will try to explain how to make a simple jsp portlet with a java2script swt application as its contents. We will use liferay portal for testing it. You can download the portlet WAR file and project sources from java2script group files . the file is called MyJ2sPortlet.war

This is not a tutorial neither of swt gui applications, portlets, or client-server application. Here I will only show what is the best way, IMHO, of showing a swt shell as a portlet..

If you want to test this portlet you only need to download liferay portal. If you want to import this as an eclipse project and make your own swt portlet you will have to be familiarized with portlet development and java2script.

In the first place I developed a simple swt application using my favorite visual editor in eclipse. In gnome it looks like the following:


We want to render it as a portlet. In this solution, the resulting portlet (with red halo) will be:

In the following picture we use liferay freeform layout. Observe that the shell will expand accordingly to the portlet size:



deploying the portlet to liferay for testing

The .war file is a web application portlet archive ready for deploy in a portal application. For testing the .war portlet in liferay you must:

  1. download liferay-bundled-with-tomcat, for example liferay 4.4.1 bundled with tomcat 5.5 http://sourceforge.net/project/downloading.php?group_id=49260&filename=liferay-portal-tomcat-5.5-4.4.1.zip&a=34438561

  2. run bin/startup,

  3. login as test@liferay.com password “test”.

  4. copy myJ2sPortlet.war to /home/yourusername/liferay/deploy (or in the case of windows something like c:\Documents and settings\Users\yourusername\liferay\deploy).

  5. Wait until liferay console prints “22:43:54,308 INFO [PortletHotDeployListener:425] Portlets for myJ2sPortlet registered successfully”

  6. Click on “welcome test!” → add application → java2script → myj2sportlet and the portlet should be added to the page

In the 4 step is where we actually make the portlet hot deploy in liferay. Alternatively, you can install de portlet from inside the portal. Just open Admin->admin liferay portlet, click "plugins" tab, click "install more plugins", click "upload file" tab, and choose MyJ2sPortlet.war. Once this step is finnished, liferay console should print somethings like the following telling us the portlet was successfully deployed:

13:06:38,595 INFO [BaseDeployer:493] Deploying myJ2sPortlet.war
Expanding: /home/sebastian/beeblos4/deploy/myJ2sPortlet.war into /tmp/20090621130638598
Copying 1 file to /tmp/20090621130638598/WEB-INF
13:06:39,230 INFO [BaseDeployer:1132] Modifying Servlet 2.5 /tmp/20090621130638598/WEB-INF/web.xml
Copying 735 files to /home/sebastian/desarrollo/portal4/tomcat/webapps/myJ2sPortlet
Copied 76 empty directories to 3 empty directories under /home/sebastian/desarrollo/portal4/tomcat/webapps/myJ2sPortlet
Copying 1 file to /home/sebastian/desarrollo/portal4/tomcat/webapps/myJ2sPortlet
Deleting directory /tmp/20090621130638598
13:06:40,151 INFO [PortletAutoDeployListener:87] Portlets for /home/sebastian/beeblos4/deploy/myJ2sPortlet.war copied successfully
13:06:43,934 INFO [PluginPackageHotDeployListener:74] Reading plugin package for myJ2sPortlet
13:06:43,935 WARN [PluginPackageHotDeployListener:123] Plugin package on context myJ2sPortlet cannot be tracked because this WAR does not contain a liferay-plugin-package.xml file
13:06:44,506 INFO [PluginPackageHotDeployListener:187] Plugin package myJ2sPortlet/myJ2sPortlet/unknown/war registered successfully
13:06:44,511 INFO [PortletHotDeployListener:133] Registering portlets for myJ2sPortlet
13:06:44,600 INFO [PortletHotDeployListener:425] Portlets for myJ2sPortlet registered successfully

Note that the portlet is not deployed until liferay prints " Portlets for myJ2sPortlet registered successfully"

Steps I followed for creating the project

The .war file is a zip and contains both binary (.class and .js) and source code (java). in src/ directory you can find portlet and servlet code and in /html/myJ2s_j2sproject/src-j2s it is the swt shell java sources.

I recommend you to decompress it and import the project to eclipse. This is a “portlet project” and you can easily build, package and deploy on liferay via a simple ant script (configure build.properties according to your system).

In summary this web application serve the java2script content statically and once deployed it still can be accessed from outside the portal application , for example from http://localhost:8080/myJ2sPortlet/html/myJ2s_j2sproject/org.sgx.portlet.myJ2sPortlet.gui.Dialog1.html

For one side, we have an html and javascript files generated by java2script that will render a maximized swt shell:

dialogShell = new Shell(parent, SWT.NONE);

//.... initialize shell content......

dialogShell.layout();

dialogShell.pack();

dialogShell.open();

dialogShell.setMaximized(true);

Display display = dialogShell.getDisplay();

while (!dialogShell.isDisposed()) {

if (!display.readAndDispatch())

display.sleep();

}

(see Dialog1.java)

in the other side, out portlet contents will be a maximized iframe which points to the static content. Portlet's View.jsp:

<%@page contentType="text/html"%>

<%@page pageEncoding="UTF-8"%>

<iframe height="100%" width="100%" src="/myJ2sPortlet/html/myJ2s_j2sproject/org.sgx.portlet.myJ2sPortlet.gui.Dialog1.html"/>

Having java2script application in a separate iframe (and thus in a separate dom) will prevent to have nasty javascript libraries incompatibilities. Also we took advantage of swt shell maximization for the portlet content size adjustment.

Hope this can be useful for people who wish to experiment with java2script applications inside portals.




miércoles, 20 de mayo de 2009

Cómo hacer que eclipse se vea mejor en linux


Plase translate this article to your favorite language:



Una de las principales incomodidades de eclipse para linux es el desperdicio de espacio (principalmente vertical). Comparemos a continuación la versión de windows y la versión de linux. Observemos en particular, como la versión de windows es capaz de mostrar muchísima más información en la vista package explorer que la versión de linux:

eclispe para linux (sin ajustes)


eclipse para windows:
Para proyectos grandes, con muchos recursos, este desperdicio de espacio es bastante molesto ya que cuanto más se tenga que hacer scroll para encontrar un recurso, más se distrae uno. Además de las vistas en forma de árbol como la package explorer, navigator, ant, outline, etc, en mi opinión eclipse para linux también desperdicia mucho espacio horizontal en los botones de la barra de herramientas, menu, etc.

Lamentablemente, las preferencias de eclipse no permiten cambiar la fuente, ni el espaciado de los elementos de este tipo de componentes. Sin embargo, dado de que en linux, SWT (el toolkit gráfico utilizado por eclipse) se basa en los widgets de gtk+ lo que sí podemos hacer es personalizar la theme gtk de forma de solucionar esto.

La forma más fácil de hacer esto es editando el archivo ~/.gtkrc-2.0 con el siguiente contenido:

style "gtkcompact" {
font_name="Sans 8"
GtkButton::default_border={0,0,0,0}
GtkButton::default_outside_border={0,0,0,0}
GtkButtonBox::child_min_width=0
GtkButtonBox::child_min_heigth=0
GtkButtonBox::child_internal_pad_x=0
GtkButtonBox::child_internal_pad_y=0
GtkMenu::vertical-padding=1
GtkMenuBar::internal_padding=0
GtkMenuItem::horizontal_padding=4
GtkToolbar::internal-padding=0
GtkToolbar::space-size=0
GtkOptionMenu::indicator_size=0
GtkOptionMenu::indicator_spacing=0
GtkPaned::handle_size=4
GtkRange::trough_border=0
GtkRange::stepper_spacing=0
GtkScale::value_spacing=0
GtkScrolledWindow::scrollbar_spacing=0
GtkExpander::expander_size=10
GtkExpander::expander_spacing=0
GtkTreeView::vertical-separator=0
GtkTreeView::horizontal-separator=0
GtkTreeView::expander-size=8
GtkTreeView::fixed-height-mode=TRUE
GtkWidget::focus_padding=0
}
class "GtkWidget" style "gtkcompact"

style "gtkcompactextra" {
xthickness=0
ythickness=0
}
class "GtkButton" style "gtkcompactextra"
class "GtkToolbar" style "gtkcompactextra"
class "GtkPaned" style "gtkcompactextra"

Básicamente, las expresiones que comienzan con "style" son reglas de estilo, mientras que las expresiones que comienzan con "class" aplican una regla de estilo a algún tipo de widget gtk. Luego de este cambio, al iniciar eclipse se ve prácticamente con el mismo espaciado que la versión de windows:



Notar que particularmente el problema es que todas las themes gtk que comunmente vienen instaladas dejan demasiado espacio (en el contexto de un programador) entre items del widget Tree de gtk (GtkTreeView).

Este pequeño cambio hace, en mi opinión, 100% más utilizable a eclipse en linux.

.... Y los años transcurrieron y esta información sigue siendo relevante para eclipse 4.3 en linux arch, y especialmente más útil ahora que eclipse 4.X desperdicia muchísimo más espacio, veamos el antes y después:
Antes: 

Después:


Notar la gran diferencia de ahorro de espacio en la tool bar superior y en el árbol de trabajo de la izquierda.BTW - at least in arch linux -  you can modify the file /home/sg/.gtkrc-2.0.mine instead original and often auto generated /home/sg/.gtkrc-2.0

lunes, 4 de mayo de 2009

tomcat 5.5 behind apache 2.2

Recently I needed to add a web application hosted in tomcat 5.5 to an apache 2.2 web server that already serves static content, svn, web dav, etc. I tried lots of ways of making this, and in this document I will describe the best sollution I've found.

In your apache's httpd.conf file add the following lines, for example at the end of the file:


LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
<Location /tomcatSite/ >
ProxyPass ajp://localhost:8009/
</Location>


This tells apache that all http requests pointing to http://yourhost/tomcatSite/ will be redirected to a running apache instance. So save the file, restart apache, start tomcat and try to navigate to http http://yourhost/tomcatSite/. In a regular tomcat installation you should see the tomcat's main page.

The idea is that apache to listen on port 80 and tomcat to listen on another port, for example 8080. In my case as I said, this allow me to have my j2ee application in http://yourhost/tomcatSite/ but taking advantage of apache's flexibility, efficiency, supported protocols/servers like svn, webdav, etc.

I hope this can be usefull for somebody as it was for me...

miércoles, 29 de abril de 2009

Generating a java bean from a properties file

little introduction to .properties files


Well, this is my situation. In my work, often I'm in need of making configurable stuff so non-programmer users can configure certain aspect of a application only editing text files. Xml is great for that, specialy if you want to support complex data and formally define de format of the document with a DTD section. But, sometimes you need to define only few configurable string values and in these circunstance xml can be like killing a fly with a gun. A much more confortable way in these cases is to use .properties resources. These are plain text files with for example the following code:

username=fulano
password=xyz
serverUrl=http://192.168.1.111


In the most common situation you left these files inside your classpath and some java class will read them.

In the last example, a web service client will read the account information from a .properties file. For reading the properties, we can, for example, name the file account1.properties and store it in the same package as some WebServiceClientManager.class. In side this class we simply do


InputStream stream = this.getClass().getResourceAsStream("account1.properties");
java.util.Properties props = new java.util.Properties();
props.load(stream);
String username = props.get("username");
String password= props.get("password");
etc.


Note that java.util.Properties objects are java.utils.HashMap representing the key=value in the .properties file. Note that because of the last, the order of properties is not respected.

Generating java bean class code from .properties files



Well, in my case, I feel more confortable dealing with java objects than with maps (in which you have to work with string ids). So I've created the following class that is able to create a java class code (a java bean) with the properties expressed in a .properties file:

public class Properties2JavaBean {
private Properties props;
public String propertiesToBean(String className,
String packageName) throws IOException {
String attrDef = "\tString ", methodsDef="",
constructorSignature="\tpublic "+className+"(", constructorBody="{";
Iterator<object> it = props.keySet().iterator();
while (it.hasNext()) {
String k = (String) it.next();
attrDef+=k;
constructorSignature+="String "+k;
constructorBody+="\n\t\tthis."+k+"="+k+";";
if(it.hasNext()){
attrDef+=", ";
constructorSignature+=", ";
}
methodsDef+="\n\n"+buildGetter(k)+"\n"+buildSetter(k);
}
constructorSignature+=")";
constructorBody+="\n\t}";

return "package "+packageName+";\n" +
"/** this class was autogenerated from a .properties file"+
" by Properties2JavaBean */\n"+

"public class "+className+"{\n" +

""+attrDef+";\n"+
constructorSignature+constructorBody+"\n"+
methodsDef+"\n" +
"\n\n\tpublic static "+className+ " newInstante(){ " +
"\n\t\t"+propertiesToBeanConstructor(className, "inst")+
";\n\t\treturn inst;" +
"\n\t}\n" +
"}";
}
public String propertiesToBeanConstructor(String className,
String instanceName) throws IOException{
String s = className+" "+instanceName+" = new "+className+"(";
Iterator<object> it = props.keySet().iterator();
while (it.hasNext()) {
s+="\""+props.get(it.next())+"\"";
if(it.hasNext())
s+=", ";
}
return s+");";
}
public void load(String s) throws IOException {
load(stringToStream(s));
}
public void load(InputStream s) throws IOException {
props = new Properties();
props.load(s);
}
String buildGetter(String propName) {
return "\tpublic String get"+changeFirstLetterMayus(propName, true)+
"(){\n\t\treturn this."+propName+";\n\t}";
}
String buildSetter(String propName) {
return "\tpublic void set"+changeFirstLetterMayus(propName, true)+
"(String attr){\n\t\tthis."+propName+" = attr;\n\t}";
}
public static String changeFirstLetterMayus(String strInput, boolean mayus) {
if(mayus)
return strInput.substring(0,1).toUpperCase()+
strInput.substring(1,strInput.length());
else
return strInput.substring(0,1).toLowerCase()+
strInput.substring(1,strInput.length());
}
private static InputStream stringToStream(String s) {
return new ByteArrayInputStream(s.getBytes());
}
}



So, for example, if I execute this conversor for the .properties example listed above, the following java bean class code is created:

package org.sgx.claroscuro;
/** this class was autogenerated from a .properties file by Properties2JavaBean */
public class ConfigBean{
String serverUrl, password, username;
public ConfigBean(String serverUrl, String password, String username){
this.serverUrl=serverUrl;
this.password=password;
this.username=username;
}
public String getServerUrl(){
return this.serverUrl;
}
public void setServerUrl(String attr){
this.serverUrl = attr;
}
public String getPassword(){
return this.password;
}
public void setPassword(String attr){
this.password = attr;
}
public String getUsername(){
return this.username;
}
public void setUsername(String attr){
this.username = attr;
}
public static ConfigBean newInstante(){
ConfigBean inst = new ConfigBean(
"http://192.168.1.111", "xyz", "fulano");

return inst;
}
}


Nice, eh? As you can see, in the static newInstance() generated method this javabean is instantiated with the exacly the same information stored in the source .properties file.

So, if you are dealing with .properties files for your configuration stuff, and you are making javabeans manually, this class can be usefull... Use it as you wish.

lunes, 20 de abril de 2009

json parser in visual basic 6

json parser in visual basic 6

Recently, in work, I encountered with the following situation. A visual basic 6 desktop program need to comunicate with our main web application via web services. This webservices are json based. Until recently, there were no json librery / parser for visual basic 6. Today I found the only (seems) json library for visual basic.

My knowledge of vb6 is short and I don't like it (or eny other ms devel tools). But it seems that you can embed a javascript aprser in your vb6 programs using "MSScriptControl.ScriptControl.1" object. So the json parser is there! With this little class:

'a very little json library in vb using MSScriptControl.ScriptControl.1
'@author: sgurin.

Dim ScriptControl
Dim initialized As Boolean

Public Sub init_class()
Set ScriptControl = CreateObject("MSScriptControl.ScriptControl.1")
ScriptControl.Language = "JavaScript"
initialized = True
End Sub

Private Function evalCode(json As String) As String
json = Replace(json, """", "\""")
evalCode = "eval("" " & json & " "") "
End Function

Public Function getProperty(jsonObject As String, prop As String) As String
Dim ret As String
Dim jsCode As String
If Not initialized Then
init_class
End If
jsCode = "var obj = " & jsonObject 'evalCode(jsonObject)
ScriptControl.ExecuteStatement (jsCode)
getProperty = ScriptControl.Eval("obj[""" & prop & """]")
End Function

'for example getSimpleArrayItem("[{foo:1}, {foo:2}]", 0, "foo") == 1
Public Function getSimpleArrayItem(jsonArray As String, index As Integer, prop As String) As String
Dim jsCode As String
If Not initialized Then
init_class
End If
jsCode = "var arr = " & jsonArray
ScriptControl.ExecuteStatement (jsCode)
getSimpleArrayItem = ScriptControl.Eval("arr[" & index & "][""" & prop & """]")
End Function

Public Function getArrayLenght(jsonArray As String) As Integer
Dim jsCode As String
If Not initialized Then
init_class
End If
jsCode = "var arr = " & jsonArray
ScriptControl.ExecuteStatement (jsCode)
getArrayLenght = ScriptControl.Eval("function js_arrayLength(a){if(a==null||a.length==null||a.length==undefined){return 0}else return a.length;}js_arrayLength(arr)")
End Function




using this class one can parse a json string with for example:

Dim parser As New jsonParser
Dim s as String
s="{age:18}"
msgbox "age: " & parser.getProperty(s, "age")


of another example:

Dim parser As New jsonParser
Dim size As Integer, i As Integer, s As String, json as String
json="[{age:18},{age:22}]"
s=" ages: "
size = parser.getArrayLenght(resp)
For i = 0 To size - 1
s = s & parser.getSimpleArrayItem(resp, i, "age") & ", "
Next i

Well, I hope somebody found this useful. Please feel free to use this code as a fundation for a real json parser in visual basic 6. We need more !