dimarts, 19 de juliol del 2011

BeanShell, scripting per a Java

BeanShell (bsh) és un interpret de Java estès amb característiques de llenguatge d'scripting. BeanShell està desenvolupat en Java i s'entrega i es pot executar com aplicació standalone des del jar, però també es pot afegir com a llibreria en una aplicació standalone Java que incorporaria d'aquesta forma un llenguatge d'scripting per a fer macros, per exemple.

A tall d'exemple, BeanShell és un dels llenguatges de macros que incorpora OpenOffice.org/LibreOffice (OOo/LO). També és el llenguatge de macros de l'editor JEdit, també es pot trobar com a llenguatge de macros a l'IDE de Java NetBeans.

Encastat a una aplicació Java, i pel fet d'executar-se en la mateixa JVM de l'aplicació hoste, és capaç d'interactuar amb els objectes de l'aplicació. Com que és interpretat i no cal compilar els scripts de bsh obre una porta a l'extensió de l'aplicació hoste. I amb tota la potència de Java, doncs BeanShell és capaç d'importar packages java.

BeanShell és un jar que ocupa menys de 300KB. És considerablement més petit, doncs, que alternatives més potents com Groovy (De fet, Groovy va molt més enllà del que seria un llenguatge d'scripting. Per exemple, el framework  Grails  està basat en Groovy).

Els punts forts de BeanShell són aquests: és java, ofereix facilitats per a l'scripting, és petit, és encastable i és fa servir a OOo/LO, entre d'altres.

Punts dèbils? malgrat que la llista de correu de desenvolupadors i d'usuaris de BeanShell a SourceForge mostra certa activitat enguany, la llista d'anuncis de noves versions resta aturada des de 2005 (justament quan el JCP aprovava la creació d'una JSR per al BeanShell). El punt dèbil és, doncs, que no ha evolucionat des de fa massa temps.

En tot cas, el fet de ser un interpret de Java fa que sigui immediatament utilitzable per programadors d'aquest llenguatge.

L'escenari d'us de BeanShell seria, doncs, aquell en el que cal incorporar un llenguatge de macros a una aplicació java que sigui lleuger i, a la vegada, d'aplicació immediata.

La sintaxi del llenguatge és la de Java. Res a descobrir, doncs. Tanmateix, hi han afegides característiques orientades a l'scripting, com el "tipatge relaxat", o comandes específiques  per a entrada sortida per consola

Les referències són, per una banda el manual d'usuari; i per l'altre, el javadoc de beanshell. També és recomanable veure la presentació amb diapositives (en format .pdf) Un punt d'entrada és l'anàlisi dels exemples que també es poden trobar a la web.

Si hem instal·lat bsh des del Centre de Programari de l'Ubuntu, o si tenim instal·lat l'OpenOffice.org/LibreOffice, és possible que trobem el bsh.jar a les ubicacions següents:

/usr/share/java/bsh-2.0b4.jar
/usr/share/java/bsh.jar
/usr/lib/openoffice/basis3.2/program/classes/bsh.jar

Per engegar l'interpret n'hi ha prou amb escriure bsh a la línia de comandes. Però també el podem engegar com la aplicació Java que és:

java -jar /usr/share/java/bsh.jar bsh.

en aquest punt ja podem introduir ordres. tanmateix és més pràctic fer un script:

Fem la prova canònica. Creo l'script hello.bsh

a = "hola ";
b = " món!";
print (a + b);

i l'executo

bsh ./hello.bsh

hola  món!

Com era d'esperar... L'script anterior en bsh és indistingible d'altres llenguatges d'script que disposen de la comanda print.

Haviem dit que bsh és java amb característiques d'script. Vet aquí la primera: no ha calgut definir el tipus de les variables.

Hauria pogut ser més rigurós. Amplio l'script amb línies que són Java inequívocament:

a = "hola ";
b = " món!";
print (a + b);

String s_Nom = "Serveix TIC i Programari Lliure";
String s_llocweb = " (http://apuntstecnologia.blogspot.com/)";

System.out.println(s_Nom + ", " + s_llocweb);

System.out.println("Variable no inicialitzada: " + sNom + ", " + s_LlocWeb);

i el resultat és:

albert@atenea:~$ bsh ./hello.bsh
hola  món!
Serveix TIC i Programari Lliure,  (http://apuntstecnologia.blogspot.com/)
Variable no inicialitzada: void, void


void, en comptes de null o "". A bsh es pot verificar si una variable està inicialitzada comparant-la amb "void".

Més coses: els "scripted objects", una forma de definir objectes a l'estil de com ho fan altres llenguatges d'scripts (recorda Javascript) que aprofiten les estructures de blocs.

ClasseBsh() {
   propietat1 = 1;
   propietat2 = "un text";  
   
   Metode1() {
      print("Aquest és el mètode 1");
   }

   Metode2() {
      print("Aquest és el mètode 2");
   }

   Metode3() {
       cbsh2 = ClasseBsh2();
       print("cbsh2.propietat1: " + cbsh2.propietat1);
       print("cbsh2.propietat2: " + cbsh2.propietat2);
       cbsh2.Metode1();
       cbsh2.Metode2();
   }

   
   ClasseBsh2() {
       propietat1 = 1;
       propietat2 = "un altre text";   
       
       Metode1() {
           print("Aquest és el mètode 1 intern");
       }

       Metode2() {
           print("Aquest és el mètode 2 intern");
       }

      return this;
    }

    return this;
}

cbsh1 = ClasseBsh();

print("cbsh1.propietat1: " + cbsh1.propietat1);
print("cbsh1.propietat2: " + cbsh1.propietat2);
cbsh1.Metode1();
cbsh1.Metode2();
cbsh1.Metode3();

El resultat és:

albert@atenea:~$ bsh ./classes.bsh
cbsh1.propietat1: 1
cbsh1.propietat2: un text
Aquest és el mètode 1
Aquest és el mètode 2
cbsh2.propietat1: 1
cbsh2.propietat2: un altre text
Aquest és el mètode 1 intern
Aquest és el mètode 2 intern

Remarcar la sintaxi simplificada, i també com es poden fer classes dins de classes sense ĺímit de nivell.

Com encastar BeanShell a una aplicació Java?

Res més senzill. Podem fer una petita classe java que carrega un interpret de BeanShell que executa un script:

package com.sticipl.proves;

public class ProvaBshRun {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        new ProvaBshRun();
    }

    public ProvaBshRun() {
        try {
            // carrega script i l'executa
            System.out.println("carrega script i l'executa");
            Interpreter beanshellInterpret = new Interpreter();
            beanshellInterpret.source("/home/albert/hello.bsh"); 
      
        } catch(Exception e) {
            System.out.println("Error: " + e.toString());
        }
    }
}

 

Per a fer l'execució més senzilla, empaqueto la classe anterior en un jar. Faig servir el següent build.xml per a l'ant















<project name="provabsh" default="build" basedir=".">
  <description>
  proves amb beanshell
  </description>

  <!-- estableix propietats globals -->
  <property name="src" location="src"/>
  <property name="bin" location="bin"/>
  <property name="build" location="build"/>
  <property name="lib"  location="lib"/>


  <target name="init">
    <!-- time stap -->
    <tstamp/>
    <!-- Crea el directory bin utilitzat en la compilació -->
    <mkdir dir="${bin}"/>
  </target>


  <target name="compile" depends="init" description="compila els fitxers font" >
    <!-- Compila les fonts java de ${src} en ${build} -->
    <javac srcdir="${src}" destdir="${bin}">
      <classpath>
       <pathelement location="${lib}/bsh-2.0b4.jar"/>
      </classpath>
   </javac>
  </target>

  <target name="build" depends="compile" description="genera el jar">
    <!-- Crea el directori de distribució -->
    <mkdir dir="${build}"/>

    <jar jarfile="${build}/provabsh.jar" basedir="${bin}">
      <include name="**/*.class"/>
      <manifest>
        <attribute name="Main-Class" value="com.sticipl.proves.ProvaBshRun"/>
          <attribute name="Class-Path" value="bsh-2.0b4.jar"/>
      </manifest>
    </jar>
     
      <copy file="${lib}/bsh-2.0b4.jar" todir="${build}" />
  </target>

</project>


Amb el build.xml anterior obtinc una carpeta build on he deixat el jar provabsj.jar que conté la classe ProvaBshRun. El manifest del jar indica que cal fer servir la llibreria bsh-2.0b4.jar.

Puc executar la classe amb:

albert@atenea:~/wk-java/ProvaBsh/build$ java -jar ./provabsh.jar

o bé amb:

albert@atenea:~/wk-java/ProvaBsh/build$ java -jar -cp ./bsh-2.0b4.jar ./provabsh.jar

i el resultat de l'execució és:

carrega script i l'executa
hola  món!
Serveis TIC i Programari Lliure,  (http://apuntstecnologia.blogspot.com/)
Variable no inicialitzada: void, void
data: Tue Jul 19 18:38:08 CEST 2011


Un cop carregat l'script també es poden manipular-ne els mètodes i propietats. Per exemple, carrego l'script classes.bsh i n'executo mètodes i en canvio propietats:

package com.sticipl.proves;

import bsh.Interpreter;
import java.util.Date;

public class ProvaBshRun {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        new ProvaBshRun();
    }

    public ProvaBshRun() {
        Interpreter beanshellInterpret = new Interpreter();

        try {
            // Avaluació d'instruccions i expressions
            // Carrega script i en modifica propietats
            System.out.println("Carrega script");
            beanshellInterpret.source("/home/albert/classes.bsh");    
          
            int  iPropietat1 = ((Integer) beanshellInterpret.get("cbsh1.propietat1")).intValue();
            String sPropietat2 = (String) beanshellInterpret.get("cbsh1.propietat2");
            System.out.println("iPropietat1: " + iPropietat1);
            System.out.println("sPropietat2: " + sPropietat2);
           
           
            iPropietat1 = 25;
            beanshellInterpret.set("cbsh1.propietat1", iPropietat1);
            sPropietat2 = "aquest és el text canviat des de l'aplicació";
            beanshellInterpret.set("cbsh1.propietat2", sPropietat2);
            System.out.println("des de l'aplicació");
            System.out.println("iPropietat1: " + iPropietat1);
            System.out.println("sPropietat2: " + sPropietat2);   
            System.out.println("des de beanshell");
            beanshellInterpret.eval("print(\"cbsh1.propietat1: \" + cbsh1.propietat1);");
            beanshellInterpret.eval("print(\"cbsh1.propietat2: \" + cbsh1.propietat2);");
           
            System.out.println("Executa mètode 1:" );
            beanshellInterpret.eval("cbsh1.Metode1();");
            System.out.println("Executa mètode 2:" );
            beanshellInterpret.eval("cbsh1.Metode2();");
            System.out.println("Executa mètode 3:" );
            beanshellInterpret.eval("cbsh1.Metode3();");
        } catch(Exception e) {
            System.out.println("Error: " + e.toString());
        }
    }
}







El resultat de l'anterior és

Carrega script
cbsh1.propietat1: 1
cbsh1.propietat2: un text
Aquest és el mètode 1
Aquest és el mètode 2
cbsh2.propietat1: 1
cbsh2.propietat2: un altre text
Aquest és el mètode 1 intern
Aquest és el mètode 2 intern
iPropietat1: 1
sPropietat2: un altre text
des de l'aplicació
iPropietat1: 25
sPropietat2: aquest és el text canviat des de l'aplicació
des de beanshell
cbsh1.propietat1: 25
cbsh1.propietat2: aquest és el text canviat des de l'aplicació
Executa mètode 1:
Aquest és el mètode 1
Executa mètode 2:
Aquest és el mètode 2
Executa mètode 3:
cbsh2.propietat1: 1
cbsh2.propietat2: un altre text
Aquest és el mètode 1 intern
Aquest és el mètode 2 intern



En el jar de BeanShell es poden trobar classes per a l'execució remota d'scripts, i també per l'execució en mode servlet. Deixo per a futurs posts la revisió d'aquestes opcions.

En resum s'ha presentat l'aplicació BeanShell i les seves característiques que més immediatament es poden utilitzar en aplicacions java.

Cap comentari:

Publica un comentari a l'entrada