IMPORTANT: Per accedir als fitxer de subversion: http://acacha.org/svn (sense password). Poc a poc s'aniran migrant els enllaços. Encara però funciona el subversion de la farga però no se sap fins quan... (usuari: prova i la paraula de pas 123456)

Projecte Java ME GestioAules

De SergiTurWiki
Share/Save/Bookmark
Dreceres ràpides: navegació, cerca

Contingut

Vegeu també

1r Exercici. Creació de la interfície gràfica d'usuari d'alt nivell.LCDUI. Versió 0.1

Del repositori SVN del curs, baixeu-vos l'exercici:

/tags/GestioAules_v0.1

Consulteu, si encara no heu consultat, l'apartat Organització del SVN del curs i seguiu l'exemple checkout amb Netbeans de l'exemple Hola Mon! canviant el tag a /tags/GestioAules_v0.1.

Aquest exercici conté l'esquelet d'una aplicació de gestió per a professors. L'esquelet només conté una interfície gràfica d'usuari bàsica representada pel següent flux de treball:

GestioAules.png

Utilitzant les eines que proporciona Netbeans, heu de crear una interfície gràfica una mica més elaborada. El resultat està representat en el següent flux de treball:

GestioAules1.png

Us mostrem com han de quedar les diferents pantalles:

GestioAulesScreen1.png GestioAulesScreen2.png GestioAulesScreen3.png
GestioAulesScreen4.png GestioAulesScreen5.png GestioAulesScreen6.png
GestioAulesScreen7.png GestioAulesScreen8.png

L'únic codi que heu d'implementar és el de la obtenció de la data actual, al formulari frmAlumnesGrup i a la finestra d'alerta infoDataGrupActual. Per obtenir la data podeu utilitzar:

Al mètode getInfoDataGrupActual afegiu després de:

// write post-init user code here

El següent:

Date current = new Date();
infoDataGrupActual.setString("La data actual és:"+ current.toString());

Per tal que el formulari, mostri la data actual, feu doble clic al camp dateField i afegiu:

GestioAulesScreen9.png

Canviar les dades del MIDlet

El nostre MIDlet encara és una còpia del HelloMIDlet que proporciona Netbeans al crear un projecte nou. En aquest apartat se us demana que canvieu les dades del MIDlet. Per fer-ho aneu a:

  • Properties del projecte (botó dret sobre nom del projecte) i aneu l'apartat Application Descriptor. Aquí es pot canviar el nom del fabricant i el venedor (dades del fitxer JAD) (pestanya Attributes) i el nom del MIDlet (a la pestanya MIDlets)
  • Encara dins de Properties aneu a l'apartat Creating JAR i canvieu el nom dels fitxers jar i jad.

2n Exercici. Bases de dades RMS. Guardar les preferències d'usuari. Versió 0.2

Del repositori SVN del curs, baixeu-vos la versió 0.2 del projecte Gestió d'aules:

/tags/GestioAules_v0.2

Consulteu, si encara no heu consultat, l'apartat Organització del SVN del curs i seguiu l'exemple checkout amb Netbeans de l'exemple Hola Mon! canviant el tag a /tags/GestioAules_v0.2.

Noms d'objectes de la GUI

Fins ara, no hem canviat els noms per defecte que proporciona Netbeans per als objectes de la interfície gràfica d'usuari. Això provoca que anem "acumulant" objectes del tipus:

backCommand
backCommand1
backCommand2
cancelCommand
cancelCommand1

Per exemple dins del formulari preferències (que per cert segurament té el nom form o similar) tenim 2 objectes gràfics:

choiceGroup
textField1

A aquestes alçades del projecte, encara és fàcil distingir els objectes que hi ha, però a mesura que creix el nombre d'objectes sinó els hi posem noms més identificadors, pot ser un autèntic maldecap intentar saber quin és l'objecte que ens interessa.

En aquest exercici us proposem que canvieu els noms dels objectes que aneu utilitzant per noms més descriptius. Per exemple:

choiceGroup --> choiceGroupProfessor
textField1  --> textFieldURLServidor

Una pràctica habitual, es començar el nom de l'objecte amb quelcom que identifica el tipus d'objecte que és (choiceGroup, textField, etc...) i després el nom de l'objecte. El nom de l'objecte ha de ser un nom que tingui relació amb l'objecte.

Per canviar el nom d'un objecte no és recomanable que ho feu a la brava! Utilitzeu les opcions del menú Refactor. Per exemple localitzeu el textField de la URL del servidor

private TextField textField;

Seleccioneu la variable textField (doble clic) i aneu al menú Refactor > Rename. Us apareixerà la següent finestra:

GestioAulesPreferencies1.png

Si accepteu, Netbeans s'encarregarà de canviar tot el codi que faci falta per tal que el canvi de nom no tingui cap implicació.

Bases de dades amb RMS

En aquest apartat utilitzarem les bases de dades Record Managment System (RMS) per tal de guardar les preferències de l'usuari. L'objectiu es fer permanents els canvis del menú Preferències de l'aplicació gestió d'aules.

GestioAulesPreferencies.png

Com veiem, a les preferències guardem:

  • URL del servidor: Més endavant utilitzarem aquest valor per connectar el nostre mòbil a un servidor.
  • Professor: L'usuari (professor) de l'aplicació ha d'indicar quin és el seu usuari
NOTA: Ara de moment, la llista de professors serà una llista estàtica. Podeu posar el que vulgueu a la llista, per exemple: profe1, profe2, profe3. 

Més endavant quan sapiguem com connectar amb el servidor, crearem aquesta llista de forma dinàmica, fent una consulta dels professors que té el sistema servidor.

El primer que hem de fer és inicialitzar l'estructura de dades (RecordStore) on guardarem les preferències. En la primera execució del MIDlet, el RecordStore estarà buit. Podem utilitzar el mètode initialize per fer aquesta inicialització. El mètode initialize és cridat durant la iniciació del MIDlet (mètode startApp), just abans de la crida a startMIDlet():

public void startApp() {
       if (midletPaused) {
           resumeMIDlet ();
       } else {
           initialize ();
           startMIDlet ();
       }
       midletPaused = false;
   }

NOTA: Podeu anar saltant entre funcions, seleccionant amb 2 clics una funció i fent clic a Botó dret > Navigate > Go To Source (Ctrl+Shift+B)

El codi del mètode initialize és el següent

private void initialize () {
        // write pre-initialize user code here
         RecordStore rs;
         try {
            rs = RecordStore.openRecordStore(GestioAules.PreferencesRecordStore, true);
            if (rs.getNumRecords()==0) {
                this.initializePreferences();
            }
         }
         catch (RecordStoreException ree) {
             ree.printStackTrace();
         }
      // write post-initialize user code here
}


La inicialització del recordStore, la fa el mètode initializePreferences(). Però només cridarem aquest mètode si el RecordStore està buit (rs.getNumRecords()==0)

El codi del mètode initializePreferences, pot ser el següent:

protected void initializePreferences() {
   RecordStore rs;
   ByteArrayOutputStream baos = new ByteArrayOutputStream();
   DataOutputStream dos = new DataOutputStream(baos);
   try {
       dos.writeUTF(GestioAules.DEFAULT_URL);
   } catch (IOException e) {
       e.printStackTrace();
   }
   byte [] barrURL = baos.toByteArray();
   
   baos.reset();
   try {
       dos.writeUTF(GestioAules.DEFAULT_Professor);
   } catch (IOException e) {
       e.printStackTrace();
   }
   byte [] professor = baos.toByteArray();

   try {
           rs = RecordStore.openRecordStore(this.PreferencesRecordStore, true);
           rs.addRecord(barrURL, 0, barrURL.length);
           rs.addRecord(professor, 0,professor.length);
           rs.closeRecordStore();
           baos.close();
           dos.close();
   } catch (Exception e) {
       e.printStackTrace();
   }
}

On:

  • PreferencesRecordStore: és el nom del RecordStore.
  • this.DEFAULT_URL: és el valor per defecte que volem que aparegui a la URL del servidor
  • this.DEFAULT_Professor: és el valor per defecte que volem que aparegui al choiceGroup professor.

Són variables membres estàtiques del MIDlet:

public class GestioAules extends MIDlet implements CommandListener {

   private boolean midletPaused = false;
    
   public static String PreferencesRecordStore = "RSpreferences";
   
   public static int URL_ID_PreferencesRecordStore = 1;
   
   public static int Professor_ID_PreferencesRecordStore = 2;
   
   public static String DEFAULT_URL = "http://192.168.1.2";
   
   public static String DEFAULT_Professor="Sergi Tur";

...

Com podeu veure, la URL del servidor la guardem a la primera posició del RecordStore, i el valor professor el guardem a la segona posició.

Ara es tracta de llegir aquests valor quan vulguem mostrar el formulari preferències. Afegiu codi als mètodes getTextFieldURLServidor:

public TextField getTextFieldURLServidor () {
if (textFieldURLServidor == null) {
    
    // write pre-init user code here
    //Obtenir del recordStore Preferencies les dades de la URL
    RecordStore rs;
    byte[] url=null;
    DataInputStream dis=null;
    String URL=null;
    try {
            rs = RecordStore.openRecordStore(GestioAules.PreferencesRecordStore, true);
            url = rs.getRecord(GestioAules.URL_ID_PreferencesRecordStore);
            rs.closeRecordStore();
    } catch (RecordStoreException e) {
        e.printStackTrace();
    }
    
    if (url != null) {
        ByteArrayInputStream bais =  new ByteArrayInputStream(url);
        dis = new DataInputStream(bais);
    }
    
    try {
        if (dis!=null)
            URL = dis.readUTF();
        dis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    
textFieldURLServidor = new TextField ("URL del servidor", "", 32, TextField.URL);
// write post-init user code here
this.getTextFieldURLServidor().setString(URL);
 

}
return textFieldURLServidor;
}

i getChoiceGroupProfessor():

public ChoiceGroup getChoiceGroupProfessor () {
if (choiceGroupProfessor == null) {
// write pre-init user code here
   // write pre-init user code here
   //Obtenir del recordStore Preferencies les dades de la URL
   RecordStore rs;
   byte[] professor=null;
   DataInputStream dis=null;
   String strProfessor=null;
   try {
           rs = RecordStore.openRecordStore(PreferencesRecordStore, true);
           professor = rs.getRecord(this.Professor_ID_PreferencesRecordStore);
           rs.closeRecordStore();
   } catch (RecordStoreException e) {
       e.printStackTrace();
   }
   
   if (professor != null) {
       ByteArrayInputStream bais =  new ByteArrayInputStream(professor);
       dis = new DataInputStream(bais);
   }
   
   try {
       if (dis!=null)
           strProfessor = dis.readUTF();
       dis.close();
   } catch (IOException e) {
       e.printStackTrace();
   }
    
choiceGroupProfessor = new ChoiceGroup ("Professor:", Choice.POPUP);
choiceGroupProfessor.setSelectedFlags (new boolean[] {  });
// write post-init user code here

choiceGroupProfessor.append(strProfessor,null);
choiceGroupProfessor.append ("Professor2", null);
choiceGroupProfessor.append ("Professor3", null);
choiceGroupProfessor.append ("Professor4", null);
choiceGroupProfessor.setSelectedFlags (new boolean[] { true , false, false, false});
choiceGroupProfessor.setFont (0, null);
choiceGroupProfessor.setFont (1, null);
choiceGroupProfessor.setFont (2, null);
choiceGroupProfessor.setFont (3, null);
}

Observeu que Netbeans no us deixa canviar codi que hagi estat generat pel dissenyador gràfic d'interfícies (està amb un fons blanc). Això és així per tal de mantindré una coherència entre el codi font i el que apareix a les finestres Screen i Flow. Si voleu modificar codi que estigui en gris ho heu de fer des de aquestes 2 últimes finestres.

En canvi si que us permet afegir codi abans i després,tal i com indiquen els comentaris:

// write pre-init user code here
// write post-init user code here

Amb això tenim resolt la lectura dels valors de la base de dades RecordStore. Ara cal implementar la comanda Guardar per tal que guardi les possibles modificacions que fem en el formulari preferències. Afegiu des de la finestra Flow, la comanda Guardar, que serà de tipus OkCommand (observeu la captura de pantalla de la finestra preferències que hi ha al principi d'aquest apartat).

GestioAulesPreferencies2.png

Ara em de modificar el que fa realment aquesta comanda. Totes les comandes es gestionen al mètode commandAction:

public void commandAction (Command command, Displayable displayable) {
if (displayable == Seleccionaungrup) {
if (command == List.SELECT_COMMAND) {
 // write pre-action user code here
 SeleccionaungrupAction ();
 // write post-action user code here
} else if (command == backCommand1) {
 // write pre-action user code here
 switchDisplayable (null, getLstMenu ());
...

Busqueu la comanda que us pertoqui (en aquest cas okCommand3), i afegim un mètode just abans de tornar al menú principal

 else if (command == okCommand3) {
// write pre-action user code here
this.savePreferences();
switchDisplayable (null, getLstMenu ());
// write post-action user code here
}

Observeu com es canvia de pantalla amb switchDisplayable.

NOTA: Observeu la importància que té posar noms que identifiquin clarament cada objecte per tal de facilitar la llegibilitat del codi?

El mètode savePreferences pot contenir un codi similar a (seleccioneu el text savePreferences i utilitzeu Go To Source --> Ctrl+ Shift+ B):

protected void savePreferences() {
    RecordStore rs;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(baos);
    try {
        dos.writeUTF(this.getTextFieldURLServidor().getString());
    } catch (IOException e) {
        e.printStackTrace();
    }
    byte [] barrURL = baos.toByteArray();
    
    baos.reset();
    try {
        dos.writeUTF(this.getChoiceGroupProfessor().getString(
                this.getChoiceGroupProfessor().getSelectedIndex()));
    } catch (IOException e) {
        e.printStackTrace();
    }
    
    byte [] professor = baos.toByteArray();

    try {
            rs = RecordStore.openRecordStore(GestioAules.PreferencesRecordStore, true);
            rs.setRecord(GestioAules.URL_ID_PreferencesRecordStore, barrURL, 0, 
                    barrURL.length);
            rs.setRecord(GestioAules.Professor_ID_PreferencesRecordStore, professor, 0 
                    , professor.length);
            rs.closeRecordStore();
            baos.close();
            dos.close();
    } catch (Exception e) {
        e.printStackTrace();
    }

3r Exercici. Generic Connection Framework i Fils d'execució. Versió 0.3

Per treballar amb l'API GFC (Generic Connection Framework) i els fils d'execució partirem de la versió 0.3.

Del repositori SVN del curs, baixeu-vos l'exercici:

/tags/GestioAules_v0.3

Consulteu, si encara no heu consultat, l'apartat Organització del SVN del curs i seguiu l'exemple checkout amb Netbeans de l'exemple Hola Mon! canviant el tag a /tags/GestioAules_v0.3.


Configuracions prèvies

Instal·lació d'un servidor web (Apache) de proves

Per dur a terme els exercicis de connectivitat d'aquest projecte necessitarem un servidor web de proves al qual es hi connectarem des del mòbil (real o emulador). Utilitzarem Apache. Podeu instal·lar Apache amb:

$ sudo apt-get install apache2

Segurament heu accedit algun cop a una pàgina web tipus:

http://acacha.dyndns.org/~sergi

El símbol ~ s'utilitza típicament en servidor Apache per indicar una carpeta que conté fitxers web d'un usuari del servidor. En l'exemple l'usuari és sergi. Aquest fitxers normalment es troben a la $HOME del usuari en una carpeta sovint anomenada public_html. Per tant:

http://acacha.dyndns.org/~sergi

Mostra els fitxers que hi ha a la carpeta:

/home/sergi/public_html

Per activar aquest servei amb Apache només cal fer el següent:

$ sudo a2enmod userdir 
Enabling module userdir.
Run '/etc/init.d/apache2 restart' to activate new configuration!

$ sudo /etc/init.d/apache2 restart

Un cop instal·lat el servei podeu accedir via web als fitxers de la carpeta:

/home/usuari/public_html

Posant la següent URL al navegador:

http://localhost/~usuari/

Si teniu problemes per accedir als fitxers, cal recordar que en sistemes Unix-like cal tenir en compte els permisos dels fitxers.

Podeu trobar més informació sobre el servidor web a l'article sobre Apache d'aquesta wiki.

Creació d'un fitxer amb la llista de professors

Anem a crear el fitxer professors al servidor web. Creeu una carpeta on guardarem els fitxers de servidor de la nostra aplicació de gestió d'aules:

$ mkdir /home/sergi/public_html/gestioaules

Creeu un fitxer de text, amb una llista de professors ficticis. Ho podeu fet per l'entorn gràfic:

$ nautilus /home/sergi/public_html/gestioaules

I aquí amb el botó dret creeu un nou fitxer buit. També podeu utilitzar un editor de text de línia de comandes com per exemple [[[Editors de text#joe|joe]]:

$ joe /home/sergi/public_html/gestioaules

Per salvar el fitxer amb joe cal utilitzar la combinació de tecles Ctrl+K+X.

Si us falta imaginació (;-)) us proporcionem un exemple de fitxer:

Tim Berners-Lee 
Linus Torvalds 
Richard_Stallman
Dennis Ritchie
Ian Murdock
Andrew Tanenbaum
Mark Shuttleworth 
Eric Raymond
Andrew Tridgell 
Paul Vixie

Obtenir la llista de professors. Connexió HTTP a un fitxer de text

A la pantalla frmPreferencies guardem el valor de la URL del servidor al qual ens volem connectar. En aquesta URL esperem trobar l'aplicació servidor del nostre client Java ME. De moment, el servidor consistirà en una simple base de dades de fitxers de text. Un dels fitxers, conté una llista de professors, llista que mostrarem al camp de tipus choiceGroup que mostra els professors.

Per tant, és necessari realitzar una connexió HTTP al servidor utilitzant l' API GCF.

Seguint les recomanacions de l'article Networking, User Experience, and Threads de la web de Sun, introduirem una nova pantalla que mostri l'estat de la nova connexió. Al Flow de la nostra aplicació ara tenim:

FlowAbansConnexio.png

Hem d'afegir una pantalla formulari entre el menú i el formulari de preferències:

FlowAbansConnexio2.png

Afegiu una comanda de tipus cancel a aquest formulari i anomeneu al formulari frmConnectant. Això ens permetrà cancel·lar la connexió.

NOTA: Vigileu amb possibles problemes de refresc de la pantalla flow. A vegades, tot i que no ho mostri (tal 
i com passa en les imatges), existeix una connexió entre la comanda Cancel del formulari preferències i el menu 
principal.

Per comprovar que existeixen les connexions, podeu utilitzar la pantalla que apareix a la part inferior esquerre de Netbeans quan seleccioneu el frmPreferències per tal d'accedir al codi font:

NetbeansFlowNavigator.png

Veure que s'executa switchDisplayable i per tant hi ha el canvi de finestra:

...
else if (displayable == frmPreferencies) {
       if (command == backCommand2) {                                          
// write pre-action user code here
           switchDisplayable(null, getLstMenu());                                            
// write post-action user code here
       } else if (command == okCommand3) {                                            
// write pre-action user code here
this.savePreferences();
...
Pantalla amb el formulari connectant...

A la pantalla Screen del formulari frmConnectant afegirem un Gauge. La pantalla ha de quedar com a la imatge de la dreta.

Podeu controlar els valors màxim i inicial del gauge accedint a les propietats de l'element gauge. Els valors que hem posat són:

  • Gauge value: 1
  • Maximum Value: 10
  • Instance name: gaugeConnectant

Arribat aquest punt, executeu l'aplicació per provar que funciona bé el flux de la interfície gràfica. No podreu accedir al formulari frmPreferències, per què la connexió encara no la hem implementada.

Implementació de la connexió. Fil d'execució:

Tal i com hem comentat a la teoria, cal que la connexió la executem en un thread especific. Per crear la nova classe Thread, seleccioneu el paquet edu.upc.ice.gestioAules a la pestanya projectes i executen crtl+N (o executeu New al menú File). Creeu una nova classe Java que anomenarem ConnexionsHTTP:

NovaClasse.png
NovaClasse2.png

Ara feu que la nova classe hereti de Thread, canviant la capçalera de la classe:

...
public class ConnexionsHTTP extends Thread {
...

Premeu Alt+Insert dins la classe i seleccioneu la opció:

Override Methods

Seleccioneu el mètode run. Ara feu que aquest mètode cridi el mètode connect(). Ha de quedar així:

public void run() {
       connect();
   }

I afegiu el mètode connect:

private void connect() {

   InputStream in = null;
   String strURLServidor = null;
   String strURLFitxerProfes = null;
   try {

     int rc;
     strURLServidor=
             this.getGestioAulesMIDlet().getTextFieldURLServidor().getString();
     strURLFitxerProfes = new String(strURLServidor+"/profes");
     mHttpConnection = (HttpConnection) Connector.open(strURLFitxerProfes);

     //Obtenir el codi de resposta
     rc = mHttpConnection.getResponseCode();
     if (rc != HttpConnection.HTTP_OK) {
       this.getGestioAulesMIDlet().urlNotFound("No s'ha trobat la URL: " +
               strURLFitxerProfes);
       return;
     }
     //System.out.println(con.getType());
     if (!mHttpConnection.getType().startsWith("text/plain")) {
       this.getGestioAulesMIDlet().fitxerProfesNoText("El fitxer " +
               strURLFitxerProfes + " no és un fitxer de text");
       return;
     }

     in = mHttpConnection.openInputStream();

     this.getGestioAulesMIDlet().getGaugeConnectant().setValue(3);
     this.getGestioAulesMIDlet().getGaugeConnectant().
             setLabel("Rebent dades...");

     //Llegir el fitxer de profes
     StringBuffer buf = new StringBuffer();
     Vector lines = new Vector();
     int c ;
     try {
       while ((c = in.read()) != -1) {
           char ch = (char) c;
           if (ch == '\n') {
               lines.addElement(buf.toString());
               buf.delete(0, buf.length());
           } else {
               buf.append(ch);
           }
       }
       in.close();
     } catch (IOException ioe) {
            ioe.printStackTrace();
            this.getGestioAulesMIDlet().networkException(ioe);
     }
     this.getGestioAulesMIDlet().setProfes(lines);    

     // Tancar la connexió
     in.close();
     mHttpConnection.close();

     //Mostrar el formulari preferències
     this.getGestioAulesMIDlet().mostrarFormulariPreferencies();
   }
   catch (ConnectionNotFoundException cne) {
     if (mCancel == false) {
       this.getGestioAulesMIDlet().urlNotFound(
               "ConnectionNotFoundException. No s'ha trobat la URL: " +
               strURLFitxerProfes);
       cne.printStackTrace();

       return;
     }
     mCancel = false;
   }
   catch (Exception e) {
     if (mCancel == false) {
       try {
         if (in != null) in.close();
         if (mHttpConnection != null) mHttpConnection.close();
       }
       catch (IOException ioe) {
           ioe.printStackTrace();
       }
       e.printStackTrace();
       this.getGestioAulesMIDlet().networkException(e);
     }
     mCancel = false;
   }
 }
Nota: Fixeu-vos en la importància de tenir un if (mCancel == false) { en les escepcions. 
Sinó posem aquest codi no podem cancel·lar en els cassos d'error que controlen les exepcions.

I el mètode cancel():

 public void cancel() {
   try {
     mCancel = true;
     if (mHttpConnection != null)
       mHttpConnection.close();
   }
   catch (IOException ignored) {}
 }

Amb la combinació de tecles Alt+Enter podeu afegir els imports necessaris (opció Add import...). També ho podeu fer amb el menú contextual (botó dret) i la opció Fix imports.

Amb la mateixa combinació de tecles (Alt+Enter) podeu afegir els camps (opció create Field). Al final heu de tenir els següents camps:

També podeu crear els Setters & Getters de tots els camps amb Alt+Insert escollint la opció Getter & Setter.

Finalment cal crear el constructor:

public ConnexionsHTTP(GestioAules gestioAulesMIDlet) {
       this.setGestioAulesMIDlet(gestioAulesMIDlet);
       mCancel = false;
}

Fixeu-vos que al crear el fil li passem una instància del MIDLet actual. Això és així per tal de poder utilitzar mètodes del MIDlet des del fil d'execució.

Per executar la connexió anirem al mètode:

public void lstMenuAction()

i a la opció de menú Preferències:

} else if (__selectedString.equals("Prefer\u00E8ncies")) {
// write pre-action user code here
           switchDisplayable(null, getFrmConnectant());                                     
           // write post-action user code here
           con.start();

L'objecte con és de tipus ConnexionsHTTP (el thread del qual acabem de crear el codi). Com que aquest mateix thread el voldrem utilitzar més tard en el mètode cancel·lar, con hauria de ser un camp de la classe GestioAules. Utilitzeu la opció Create Field amb la combinació de tecles Alt+Return.

Podem inicialitzar aquest objecte al mètode initialize afegint la línia:

con = new ConnexionsHTTP(this);

Recordeu que podeu accedir-hi directament amb la opció Go to Source del menú contextual(botó dret) de la opció de menú que és mostra a la pantalla Flow.

Fixeu-vos que primer es mostra la pantalla de connexió i aleshores s'inicia el Thread ConnexionsHTTP.

Ara anem a implementar la opció cancel·lar. Seleccioneu la comanda cancel·lar de l'objecte (des de la finestra Flow) i utilitzeu la opció Go To Source del menú contextual i afegiu a post-action:

} else if (displayable == frmConnectant) {
       if (command == cancelCommand2) {
           // write pre-action user code here
           switchDisplayable(null, getLstMenu());
           // write post-action user code here
           con.cancel();
       }

Afegiu al MIDlet GestioAules un mètode per mostrar una pantalla d'alerta en cas de diferents tipus d'errors i per forçar canvis de pantalla amb alert:

public void mostrarFormulariPreferencies(){
       switchDisplayable(null, getFrmPreferencies());
   }
   public void networkException(Exception ioe) {
       Alert a = new Alert("Ha ocorregut una excepció",
               ioe.toString(), null, null);
       a.setTimeout(Alert.FOREVER);
       switchDisplayable(a, getLstMenu());
   }
   public void urlNotFound (String missatgeError) {
       Alert a = new Alert("No s'ha trobat la URL",missatgeError,null, null);
       a.setTimeout(Alert.FOREVER);
       switchDisplayable(a, getFrmPreferencies());
   }
   public void fitxerProfesNoText (String missatgeError) {
       Alert a = new Alert("El fitxer de profes no és de text ",missatgeError,null, null);
       a.setTimeout(Alert.FOREVER);
       switchDisplayable(a, getFrmPreferencies());
   }

Observeu com s'utilitzen les pantalles d'alerta com a transició cap a altres pantalles.

També heu de crear al MIDlet, un camp de tipus vector, per guardar la llista dels professors:

   public Vector profes;

   public Vector getProfes() {
       return profes;
   }

   public void setProfes(Vector profes) {
       this.profes = profes;
   }

Finalment tingueu en compte aquesta llista de qüestions a tenir en compte:

  • Inicialització de variables, pantalles i camps de formulari: Si us heu fixat, Netbeans inicialitza els valors dels objectes el primer cop que s'utilitzen, mitjançant mètodes get (getFrmPreferències, getTextFieldURLServidor, etc). Aquesta política és força recomanable ja que estalvia temps de processament i memòria, fet que cal que recordeu que es força important al programar dispositius mòbils. L'inconvenient és que heu de tenir en compte que com a mínim el primer cop haureu d'accedir a aquest objectes a través del mètode get, sinó podeu torbar-vos amb NullPointerExceptions, degut a que els objectes no estan inicialitzats.
  • Camp de Text (textField) URL Servidor: Potser us caldrà augmentar la mida màxima de camp per tal poder posar la URL completa. Ho podeu fer a les propietats del camp, a les quals podeu accedir seleccionant la opció Propietats del menú contextual (botó dret) des de la finestra Flow.
  • Correcció d'alguns warnings: Pot ser que en algun cas trobeu un avís de compilació conforme utilitzeu la variable this per accedir a camps estàtics del MIDlet GestioAules. Per eliminar aquests avisos, canvieu:
this.variableEstàtica

per

GestioAules.variableEstàtica
  • És recomanable que modifiqueu el noms per defecte dels objectes que crea Netbeans. Per exemple, la comanda okCommandx del formulari frmPreferencies és molt més comprensible si té com a nom quelcom com okCommandGuardar.

Implementació d'una millora dels fils d'execució. Ús de semàfors (versió 0.5)

A la classe ConnexionsHTTP cal afegir els mètodes:

public synchronized void stop() {
   mTrucking = false;
   notify();
 }
public synchronized void go() {
   notify();
 }

El camp mTrucking és de tipus boolean i podeu afegir el camp amb les tecles Alt+Enter utilitzant la opció Create Field.

Canvieu el codi del mètode run() de:

public void run() {
       connect();
   }

a

public synchronized void run() {
   while (mTrucking) {
     try { wait(); }
     catch (InterruptedException ie) {}
     if (mTrucking) connect();
   }
}

I inicialitzeu els camps al constructor:

public ConnexionsHTTP(GestioAules gestioAulesMIDlet) {
       this.setGestioAulesMIDlet(gestioAulesMIDlet);
       mTrucking = true;
       mCancel = false;
}

Ara les modificacions que cal introduir al MIDlet. Primer, iniciem (amb un start()) el fil al mètode initialize. Afegiu la línia con.start():

con = new ConnexionsHTTP(this);
con.start();

La vida del fil serà la mateixa que la del MIDlet. Per tant, al mètode que destrueix em MIDlet afegim:

public void destroyApp(boolean unconditional) {
       con.stop();
}

Ara en comptes de cridar a start quan seleccionem la opció de menú Preferències, cridem al mètode go() dins del mètode lstMenuAction:

} else if (__selectedString.equals("Prefer\u00E8ncies")) {
           // write pre-action user code here
           switchDisplayable(null, getFrmConnectant());
           // write post-action user code here
           con.go();
       }

Per cancel·lar el fil d'execució no cal canviar res.

Amb aquesta millora, no gastem tants recursos creant un thread cada cop que volem iniciar una connexió HTTP.

Exercici opcional. Obtenir i mostrar una imatge via HTTP

Us proposem que al menú preferències mostreu una imatge amb una foto del professor (foto tipus carnet) seleccionat. La imatge s'obtindrà mitjançant una connexió al servidor web. Per exemple podeu guardar les fotos a:

/home/sergi/public_html/gestioaules/fotosprofes

Que equivaldria a la URL:

http://localhost/~sergi/gestioaules/fotosprofes

Les fotos hauran de tenir el mateix nom (sense comptar la extensió) que el nom del fitxer profes.

Podeu consultar un exemple de com connectar-se a una imatge i mostrar-la a:

http://www.java-tips.org/java-me-tips/midp/displaying-image-from-servlet-on-j2me-device.html

4t Exercici. Servlets, serialització, persistència i personalització del procés de construcció amb Ant

Per realitzar aquest exercici, podeu partir de la versió 0.4.

Del repositori SVN del curs, baixeu-vos l'exercici:

/tags/GestioAules_v0.4

Consulteu, si encara no heu consultat, l'apartat Organització del SVN del curs i seguiu l'exemple checkout amb Netbeans de l'exemple Hola Mon! canviant el tag a /tags/GestioAules_v0.4.

Personalització del procés de construcció del projecte

Per defecte, els projectes de Netbeans es guarden a la carpeta NetbeansProjects de la vostre HOME. Per exemple:

/home/sergi/NetbeansProjects

Podeu consultar el contingut exacte d'aquesta carpeta sense sortir de Netbeans, anant a la pestanya Files (al costat de Projects) a la sub-finestra esquerre de Netbeans:

ProcesConstruccio1.png

El projecte està organitzat en una sèrie de subdirectoris:

  • build: codis compilat (.class)
  • dist: els fitxers de l'aplicació empaquetada
  • nbproject: fitxers específics del projecte a Netbeans
  • src: codi font
NOTA: La carpeta dist pot no estar entre els subdirectoris. Si és així, segurament heu 
modificat la carpeta on es desplega (deploy) el projecte a les propietats del projecte (botó dret al nom del projecte --> Propierties --> Opció Deploying).

El fitxer més important és build.xml. Aquest fitxer és un fitxer de configuració de tasques d'Ant. Ant és una eina similar al make de C, que permet automatitzar les tasques de construcció del projecte.

Netbeans utilitza ant per a compilar, debugar, ofuscar, desplegar, i en definitiva, construir el projecte. Podeu consultar les tasques que realitza Netbeans consultant el fitxer build.xml del vostre projecte. Veureu una línia com la següent:

<import file="nbproject/build-impl.xml"/> 

El fitxer nbproject/build-impl.xml és el que realment utilitza Netbeans per a construir el projecte. Normalment, a no ser que sapigueu exactament el que esteu fent, no haureu de modificar mai aquest fitxer directament. El fitxer es modifica a través de la interfície gràfica de Netbeans.

El que si us proporciona Netbeans, és la possibilitat de personalitzar el procés de construcció. Per aquesta raó, disposeu de una sèrie de tasques predefinides (que per defecte no fan res) que podeu implementar. Les tasques són:

pre-init:                 called before initialization of project properties
post-init:                called after initialization of project properties
pre-preprocess:           called before text preprocessing of sources
post-preprocess:          called after text preprocessing of sources
pre-compile:              called before source compilation
post-compile:             called after source compilation
pre-obfuscate:            called before obfuscation 
post-obfuscate:           called after obfuscation
pre-preverify:            called before preverification
post-preverify:           called after preverification
pre-jar:                  called before jar building
post-jar:                 called after jar building
pre-build:                called before final distribution building
post-build:               called after final distribution building
pre-clean:                called before cleaning build products
post-clean:               called after cleaning build products

Per exemple, si afegiu el següent al fitxer build.xml (just després de la línia amb l'import)

<target name="post-build">
      <echo message="Creant el fitxer MD5 per al fitxer ${dist.dir}/${dist.jar}"/>
      <checksum file="${dist.dir}/${dist.jar}" forceOverwrite="yes"/>
      <zip destfile="GestioAules.zip" basedir="${dist.dir}"/>
      <zip destfile="GestioAules.zip" basedir="${dist.dir}"/>
      <tar destfile="GestioAules.tar" basedir="${dist.dir}"/>
      <gzip src="GestioAules.tar" destfile="GestioAules.tar.gz"/>
      <delete file="GestioAules.tar"/>
</target> 

Podreu automatitzar la creació d'un fitxer de zip i un tar.gz amb el vostre projecte i el càlcul de les sumes MD5.

Ara quan executeu el vostre projecte s'executarà la tasca post-build (fixeu-vos en la finestra output). També podeu executar directament la tasca, des de el menú contextual del fitxer build.xml:

ProcesConstruccio2.png

Tingueu en compte també, que ant es pot executar des de la línia de comandes.

MIDlets i Servlets. Creació d'un Servlet per a obtenir la llista de professors

Primer de tot anem a veure com es pot crear un Servlet senzill amb Netbeans (HolaMon! Servlet). Posteriorment el modificarem (amb les eines de refactoring de Netbeans) per tal d'adaptar-lo al nostre projecte.

Executeu File > New Project (Ctrl+Alt+n) i escolliu crear un projecte de tipus Java Web > Web Application:

NouHolaMonServlet.png

Anomeneu al projecte HolaMonServlet:

NouHolaMonServlet1.png

I escolliu Tomcat com a servidor d'aplicacions Java (recordeu que durant la instal·lació vam escollir instal·lar Tomcat):

NouHolaMonServlet2.png

Feu clic a Finish.

NOTA: També podríeu utilitzar qualsevol altre servidor d'aplicacions com GlassFish.

Ara cal crear el Servlet. Aneu a la pestanya Projects i seleccioneu Source Packages:

NouHolaMonServlet3.png

Des d'aquí creeu un nou Servlet amb Ctrl+n:

NouHolaMonServlet4.png

Anomeneu al Servlet HolaMonServlet i el creeu al paquet:

edu.ice.upc.gestioAulesServlet
NouHolaMonServlet5.png

I deixeu les opcions per defecte de la següent finestra:

NouHolaMonServlet6.png

Aquesta finestra configura la URL que cridarà al servlet. En el nostre cas:

http://localhost:8084/HolaMonServlet/

Ara modifiqueu el codi del Servlet que acabeu de crear. Concretament modifiqueu el mètode processRequest a:

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
       response.setContentType("text/plain;charset=UTF-8");
       PrintWriter out = response.getWriter();
       try {
           out.println("Hola Mon!");
       } finally { 
           out.close();
       }
   } 

Fixeu-vos que hem canviat el Mime-Type del fitxer a text/plain;charset=UTF-8.

Ara ja podeu executar el Servlet. Netbeans iniciarà Tomcat i us mostrarà el resultat de cridar el Servlet amb el navegador per defecte (en l'exemple Konqueror):

NouHolaMonServlet7.png

Podeu modificar el navegador per defecte al menú Tools, opció Options, pestanya General.

Ara podem adaptar aquest Servlet per tal que mostri la llista de professors. Canvieu la línia:

out.println("Hola Mon!");

Per:

out.println("Tim Berners-Lee");
out.println("Linus Torvalds");
out.println("Richard Stallman");
out.println("Dennis Ritchie");
out.println("Ian Murdock");
out.println("Andrew Tanenbaum");
out.println("Mark Shuttleworth");
out.println("Eric Raymond");
out.println("Andrew Tridgell");
out.println("Paul Vixie");

I utilitzeu les opcions de Refactorin per tal de donar un nom més adequat al Servlet:

  • Canvieu el nom del servlet a ProfesServlet: Utilitzeu el menú contextual (boto dret) del servlet. Utilitzeu la opció Refactor>Rename.
  • Canvieu el nom del projecte a GestioAulesServlet: Boto dret al nom del projecte > Rename. Canvieu també el nom de la carpeta.
  • Canvieu la URL del Servlet a ProfesServlet: Aneu a la carpeta Web Pages/WEB-INF/web.xml del projecte i mostreu la pestanya Servlets. Modifiqueu aquí el Servlet Name i la URL Pattern a ProfesServlet. Guardeu amb Ctrl+S.

Al codi font de web.xml (pestanya XML), veureu que modifica les línies:

<servlet-mapping>
       <servlet-name>ProfesServlet</servlet-name>
       <url-pattern>/ProfesServlet</url-pattern>
</servlet-mapping>

Podeu modificar la URL que executa el Servlet al menú contextual del fitxer ProfesServlet, a la opció Tools>Set Servlet Execution URI.


NOTA: Si teniu problemes de refresc (modifiqueu coses del projecte i no es reflexteixen al navegador), ca 
que teniu en compte que a vegades cal tornar a instal·lar l'aplicació web al contenidor d'aplicacions (Tomcat). A 
la pestanya Services (germana de Projects), podeu accedir al servidor Tomcat i reiniciar-lo (botó dret 
restart).

Ara ja podeu modificar la URL del servidor al MIDlet, per tal que apunti a:

http://localhost:8084/GestioAulesServlet/ProfesServlet

I comprovar que tot funciona correctament executant el MIDlet (enrecordeu-vos de modificar el Projecte principal al menú Run abans d'executar el MIDlet.)

NOTA: Recordeu de canviar el valor de la variable estàtica "/profes". Ara com podeu observar la variables 
estàtica ha de tenir la URL del Servlet que voleu cridar.

Serialització. Objectes Profe i Grup

Ara que ja tenim el formulari preferències implementat i ara que utilitzem un Servlet en la part servidor de la nostre aplicació client-servidor, podem passar a implementar la pantalla:

SeleccionaUnGrup.png
.

A la qual si accedeix seleccionant la opció "Passar llista" del menú principal. Aquesta pantalla ha de mostrar una primera opció anomenada "Segons la hora actual", seguida de la llista de grups als quals el professor té docència assignada.

La opció "Segons la hora actual" calcularà a quin grup és el que pertoca segons la hora en que és selecciona aquest menú. Aquesta opció la implementarem més endavant.

En aquest apartat, el que hem de fer és aconseguir la llista de grups als quals un professor té docència assignada. Per aconseguir aquesta llista, la aplicació mòbil és connectarà a un Servlet que li proporcionara un objecte amb la informació del professor. Aquesta comunicació entre servidor i client es dura a terme en binari i per aquesta raó, ens caldrà serialitzar l'objecte professor.

Primer de tot hem de modificar la interfície gràfica. Ara la lista de grups ja no és estàtica, sinó que l'haurem d'omplir de forma dinàmica des del codi font del MIDlet. Elimineu els elements de la llista i deixeu només la opció "Segons la hora actual"

SeleccionaUnGrup1.png

Un cop tenim la interfície gràfica preparada, heu de crear dues classes al paquet ice.edu.upc.gestioaules (utilitzeu l'assistent de creació amb Ctrl+n):

  • Professor: Objecte que inclou tota la informació relativa al professor
  • Grup: Objecte que inclou tota la informació relativa al professor

L'objecte professor tindrà dos variables membres amb els seus corresponents mètodes getter/setter:

  • Nom: Objecte de tipus String amb el nom del professor.
  • Grups: Objecte de tipus Vector amb el conjunt de grups als quals el professor imparteix docència.

Per la seva banda, l'objecte grup tindrà de moment només una variable membre:

  • Nom: Objecte de tipus String amb el nom del grup.

Totes dues classes implementaran la interfície Persistent:

 package edu.upc.ice.GestioAules;

import java.io.IOException;

/**
 *
 * @author Sergi Tur Badenas
 */
public interface Persistent {

     /**
     * Implementació de la persistència de l'objecte
     */
    byte[] serialize() throws IOException;
    /**
     * Recuperació d'un objecte persistent
     */
    void deserialize( byte[] data ) throws IOException;

}

Que com podeu veure els obliga a implementar els mètodes:

  • serialize: Converteix l'objecte en un array de bytes (byte [])
  • deserialize: Omple les variables membre d'un objecte a partir d'un array de bytes que emmagatzemi en binari la informació de l'objecte.

Per implementar aquest mètodes heu de tenir en compte que:

  • La classe Professor és un objecte complex que conté un vector dinàmic. Per serialitzar el vector, heu d'incloure en la serialització el nombre d'elements del vector
  • També haureu de guardar la longitud de cada objecte Grup del vector, de tal manera que sigui possible llegir tot el grup d'un cop al deserialitzar.
  • El vector inclou objectes del tipus Grup. La implementació de la serialització de l'objecte Grup és senzilla.

Tot seguit us mostrem la solució:

Classe Professor:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package edu.upc.ice.GestioAules;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Vector;

/**
 *
 * @author sergi
 */
public class Professor implements Persistent {

    public String nom;
    public Vector grups;

    public Professor() {
        this.setNom("");
        this.setGrups(new Vector());
    } 

    public Professor(String nom, Vector grups) {
        this.nom = nom;
        this.grups = grups;
    }

    public Vector getGrups() {
        return grups;
    }

    public void setGrups(Vector grups) {
        this.grups = grups;
    }

    public String getNom() {
        return nom;
    } 

    public void setNom(String nom) {
        this.nom = nom;
    }  

    public String toString() {
        StringBuffer b = new StringBuffer();
        b.append('{');
        b.append(nom);
        b.append(',');
        b.append("grups:"); 

        Enumeration enumGrups = this.getGrups().elements(); 

        int count = 0;
        for (; enumGrups.hasMoreElements();) {
            Grup grup = (Grup) enumGrups.nextElement();
            b.append(grup.toString());
            b.append(',');
            count++;
        }
        b.append('}');
        return b.toString(); 

   }

    public byte[] serialize() throws IOException {
        //Si no implementessim el mètode podriem utilitzar:
        //throw new UnsupportedOperationException("Not supported yet.");
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        DataOutputStream dout = new DataOutputStream(bout);

        dout.writeUTF(this.getNom());  

        //Persistència del vector grups:
        int n = this.getGrups().size();
        dout.writeInt(n);
        for (int i = 0; i < n; ++i) {
            Grup o = null;
            try {
                o = (Grup) this.getGrups().elementAt(i);
            } catch (ClassCastException cce) {
                throw new IOException("No es pot persistir l'objecte de tipus" + o.getClass().getName());
            }
            dout.writeUTF("edu.upc.ice.GestioAules.Grup");
            byte[] data = o.serialize();
            dout.writeInt(data.length);
            if (data.length > 0) {
                dout.write(data);
            }
        }
        dout.flush();
        return bout.toByteArray();
    } 

public void deserialize(byte[] data) throws IOException {
        //Si no implementessim el mètode podriem utilitzar:
        //throw new UnsupportedOperationException("Not supported yet.");
        ByteArrayInputStream bin = new ByteArrayInputStream(data);
        DataInputStream din = new DataInputStream(bin);

        this.setNom(din.readUTF());
       
       //Recuperar el vector grups
       int n = din.readInt();
       for (int i = 0; i < n; ++i) {
           String type = din.readUTF();
           if (!type.equals("edu.upc.ice.GestioAules.Grup")) {
               //Error
           }
           Grup grup = new Grup();
           int len = din.readInt();
           byte[] tmp = new byte[ len ];
           din.readFully(tmp);
           grup.deserialize(tmp);
           if (this.getGrups()!=null)
               this.getGrups().addElement(grup);
           else {
               this.setGrups(new Vector());
               this.getGrups().addElement(grup);
           }
       }
       bin.close();
       din.close();
   }
}

Classe Grup

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package edu.upc.ice.GestioAules;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

/**
 *
 * @author sergi
 */
public class Grup implements Persistent { 
 
   public String nom;

   public String getNom() {
       return nom;
   }

   public void setNom(String nom) {
       this.nom = nom;
   }

   public Grup() {
   }

   public Grup(String nom) {
       this.nom = nom;
   }

   public String toString() {
       return super.toString();
   }

   public void deserialize(byte[] data) throws IOException {
       ByteArrayInputStream bin = new ByteArrayInputStream(data);
       DataInputStream din = new DataInputStream(bin);

       this.setNom(din.readUTF());
       din.close();
       bin.close();
   }

   public byte[] serialize() throws IOException {
       ByteArrayOutputStream bout = new ByteArrayOutputStream();
       DataOutputStream dout = new DataOutputStream(bout);

       dout.writeUTF(this.getNom());
       dout.flush();
       return bout.toByteArray();
   }
}

Servlet ProfeServlet. Obtenir un objecte de tipus professor

En aquest cas us oferim una implementació molt simple del Servlet que obté les dades del professor actual. El servlet el podeu afegir a la aplicació web GestioAulesServlet. Col·loqueu-vos al paquet edu.ice.upc.gestioAulesServlet i creeu un nou Servlet amb Ctrl+n. La URL del Servlet serà:

http://localhost:8084/GestioAulesServlet/ProfeServlet

Aquest Servlet esperarà que li passem un paràmetre per la Query String. El paràmetre s'anomena name i conté el nom del professor del qual volem obtenir informació. Un exemple:

http://localhost:8084/GestioAulesServlet/ProfeServlet?name=Tim%20Berners-Lee

Al paquet del servlet, cal incloure les classes Professor, Grup i Persistent que hem implementat en l'apartat anterior.

El més destacable del Servlet és el control d'errors, en cas d'error es retorna un fitxer de text:

if (name == null) {
           //No s'ha especificat el paràmetre. Enviar text amb el missatge
           //d'error
           response.setContentType("text/plain;charset=UTF-8");
           response.setStatus(response.SC_OK);
           PrintWriter out = response.getWriter();
           try {
               out.println("Error. No s'ha especificat el paràmetre name");
           } finally {
               out.close();
           }
           return;
       } 

I la forma en que es retorna un objecte binari:

       ...
       //Serialitzar l'objecte Professor
       byte[] data = profe.serialize();

       //Enviar el resultat amb les capçaleres adequades
       response.setContentType("application/octet-stream");
       response.setContentLength(data.length);
       response.setStatus(response.SC_OK);
       OutputStream out = null;
       try {
           out = response.getOutputStream();
           out.write(data);
           out.close();
       } finally {
           out.close();
       }

El codi del Servlet és:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package edu.ice.upc.gestioAulesServlet; 

import com.sun.org.apache.xalan.internal.xsltc.runtime.Hashtable;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Vector;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author sergi
 */
public class ProfeServlet extends HttpServlet { 

    public static final String PARAMETER_PROFE = "name";

    /** 
     * Processes requests for both HTTP GET and POST methods.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException { 

        //Obtenir del request el nom del professor que es vol obtenir
        String name = new String(""); 

        name = request.getParameter(ProfeServlet.PARAMETER_PROFE);

       if (name == null) {
           //No s'ha especificat el paràmetre. Enviar text amb el missatge
           //d'error
           response.setContentType("text/plain;charset=UTF-8");
           response.setStatus(response.SC_OK);
           PrintWriter out = response.getWriter();
           try {
               out.println("Error. No s'ha especificat el paràmetre name");
           } finally {
               out.close();
           }
           return;
       }

       //Obtenir l'objecte professor segons el nom
       Professor profe = this.obtenirProfessor(name);

       //No podem utilitzar ObjectOutputStream.writeObject() perquè
       //Java ME no disposa de ObjectInputstream.readObject()
       //Hem d'utilitzar la serializació manual
       if (profe == null) {
           //No s'ha trobat cap professor. Enviar text amb el missatge d'error
           response.setContentType("text/plain;charset=UTF-8");
           response.setStatus(response.SC_OK);
           PrintWriter out = response.getWriter();
           try {
               out.println("Error. No s'ha trobat cap professor amb el nom "
                       + name );
           } finally {
               out.close();
           }
           return;
       }

       //Serialitzar l'objecte Professor
       byte[] data = profe.serialize();

       //Enviar el resultat amb les capçaleres adequades
       response.setContentType("application/octet-stream");
       response.setContentLength(data.length);
       response.setStatus(response.SC_OK);
       OutputStream out = null;
       try {
           out = response.getOutputStream();
           out.write(data);
           out.close();
       } finally {
           out.close();
       }
   }

   /** 
    * Handles the HTTP GET method.
    * @param request servlet request
    * @param response servlet response
    * @throws ServletException if a servlet-specific error occurs
    * @throws IOException if an I/O error occurs
    */
   @Override
   protected void doGet(HttpServletRequest request, HttpServletResponse response)
           throws ServletException, IOException {
       processRequest(request, response);
   }

   /** 
    * Handles the HTTP POST method.
    * @param request servlet request
    * @param response servlet response
    * @throws ServletException if a servlet-specific error occurs
    * @throws IOException if an I/O error occurs
    */
   @Override
   protected void doPost(HttpServletRequest request, HttpServletResponse response)
           throws ServletException, IOException {
       processRequest(request, response);
   }

   /** 
    * Returns a short description of the servlet.
    * @return a String containing servlet description
    */
   @Override
   public String getServletInfo() {
       return "Short description";
   }// </editor-fold>

   private Professor obtenirProfessor(String name) {
       //throw new UnsupportedOperationException("Not yet implemented");
       Professor profe = new Professor();
       //Codi d'exemple de com obtenir el professor.
       //Un codi real implementaria segurament una connexió a la base de dades
       //de professors, per tal d'obtenir el professor.
       //Crear uns quants grups d'exemple
       Grup grup1 = new Grup("1r SMX A Matí");
       Grup grup2 = new Grup("1r SMX A Tarda");
       Grup grup3 = new Grup("2n ESI A Matí");
       Grup grup4 = new Grup("2n ESI A Tarda");
       Grup grup5 = new Grup("1r SMX B Matí");
       Grup grup6 = new Grup("1r SMX B Tarda");
       Grup grup7 = new Grup("2n ESI B Matí");
       Grup grup8 = new Grup("2n ESI B Tarda");

       Vector grups1 = new Vector();
       grups1.add(grup1);
       grups1.add(grup3);
       grups1.add(grup5);
       grups1.add(grup7);
       Vector grups2 = new Vector();
       grups2.add(grup2);
       grups2.add(grup4);
       grups2.add(grup6);
       grups2.add(grup8);

       if (name.equalsIgnoreCase("Tim Berners-Lee")) {
           profe.setNom("Tim Berners-Lee");
           profe.setGrups(grups1);
       } else if (name.equalsIgnoreCase("Linus Torvalds")) {
           profe.setNom("Linus Torvalds");
           profe.setGrups(grups1);
       } else if (name.equalsIgnoreCase("Richard Stallman")) {
           profe.setNom("Richard Stallman");
           profe.setGrups(grups2);
       } else if (name.equalsIgnoreCase("Dennis Ritchie")) {
           profe.setNom("Dennis Ritchie");
           profe.setGrups(grups2);
       } else if (name.equalsIgnoreCase("Ian Murdock")) {
           profe.setNom("Ian Murdock");
           profe.setGrups(grups2);
       } else {
           profe = null;
       }
       return profe;
   }
} 

Podeu comprovar que funciona accedint a la URL:

http://localhost:8084/GestioAulesServlet/ProfeServlet

Si no indiqueu el nom del professor (o succeeixi qualsevol altre error com que el nom del professor no existeixi) us mostrarà un error en un fitxer de text.

Si tot és correcte, el navegador us proposarà descarregar un fitxer binari amb un array de bytes que conté la informació serialitzada de l'objecte Professor.

Creació d'un fil d'execució per a la connexió a un objecte binari

En un apartat anterior vam veure com connectar-se a un URL amb Java ME.

Ara que ja tenim el servlet ProfeServlet, volem que el nostre MIDlet si connecti per obtenir les dades del professor. Per a realitzar aquesta connexió, hem de tornar a utilitzar un fil d'execució i el codi serà força similar al de la classe ConnexionsHTTP.

Per tal de reaprofitar el codi hem convertit la Classe ConnexionsHTTP en una classe abstracta:

package edu.upc.ice.GestioAules;

import java.io.IOException;
import javax.microedition.io.HttpConnection;

/**
 *
 * @author sergi
 */
public abstract class ConnexionsHTTP extends Thread {

    protected HttpConnection mHttpConnection;
    protected GestioAules gestioAulesMIDlet;
    protected boolean mCancel;
    protected boolean mTrucking; 
 
    public GestioAules getGestioAulesMIDlet() {
        return gestioAulesMIDlet;
    } 

   public void setGestioAulesMIDlet(GestioAules gestioAulesMIDlet) {
       this.gestioAulesMIDlet = gestioAulesMIDlet;
   }

   public HttpConnection getMHttpConnection() {
       return mHttpConnection;
   }

   public void setMHttpConnection(HttpConnection mHttpConnection) {
       this.mHttpConnection = mHttpConnection;
   }

   public ConnexionsHTTP(GestioAules gestioAulesMIDlet) {
       this.setGestioAulesMIDlet(gestioAulesMIDlet);
       mTrucking = true;
   }

   public synchronized void run() {
       while (mTrucking) {
           try {
               wait();
           } catch (InterruptedException ie) {
           }
           if (mTrucking) {
               connect();
           }
       }
   }

   protected abstract void connect();

   public void cancel() {
       try {
           mCancel = true;
           if (mHttpConnection != null) {
               mHttpConnection.close();
           }
       } catch (IOException ignored) {
       }
   }

   public synchronized void stop() {
       mTrucking = false;
       notify();
   }

   public synchronized void go() {
       notify();
   }
}

I hem creat dos classes que són filles d'aquesta classe abstracta:

  • ConnexioHTTPProfes: Obté la llista de profes per al formulari frmPreferencies.
  • ConnexioHTTPProfe: Obté l'objecte professor.

Aquestes classe implementen el mètode abstracte connect. El codi del mètode connect en el cas de la classe ConnexioHTTPProfes, és el que ja havíem vist en apartat anteriors.

El codi de ConnexioHTTPProfe és:

package edu.upc.ice.GestioAules;

import java.io.IOException;
import java.io.InputStream;
import javax.microedition.io.ConnectionNotFoundException;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;

/**
 *
 * @author sergi
 */
public class ConnexioHTTPProfe extends ConnexionsHTTP { 

   public static final String SERVLET_PROFE = "/ProfeServlet";

   public ConnexioHTTPProfe(GestioAules gestioAulesMIDlet) {
       super(gestioAulesMIDlet);
   }

   protected void connect() {
       InputStream in = null;
       StringBuffer strURLServidor = null;
       String strURLFitxerProfeMesParametre = null;
       String profeActual = null;
       StringBuffer buf = new StringBuffer();

       try {

           int rc;
           strURLServidor = new StringBuffer(this.getGestioAulesMIDlet().
                   getTextFieldURLServidor().getString());
           strURLServidor.append(ConnexioHTTPProfe.SERVLET_PROFE);
           profeActual= new String (
                   this.getGestioAulesMIDlet().
                   getChoiceGroupProfessor().getString(
                   this.getGestioAulesMIDlet().getChoiceGroupProfessor().
                   getSelectedIndex()));

           strURLServidor.append("?name=").append(profeActual.trim());
           strURLFitxerProfeMesParametre= Utilities.urlEncode(
                   strURLServidor.toString());
           
           mHttpConnection = (HttpConnection) Connector.open(
                   strURLFitxerProfeMesParametre);

           //Obtenir el codi de resposta
           rc = mHttpConnection.getResponseCode();
           if (rc != HttpConnection.HTTP_OK) {
               this.getGestioAulesMIDlet().urlNotFound(
                    "No s'ha trobat la URL: " + strURLFitxerProfeMesParametre);
               return;
           }

           String type = mHttpConnection.getType();
           //Si ens retorna un fitxer de text és un error
           if (type.startsWith("text/plain")) {
               in = mHttpConnection.openDataInputStream();
               int ch;
               while ((ch = in.read()) != -1) {
                   buf.append((char) ch);
                   System.out.println((char) ch);
               }
               this.getGestioAulesMIDlet().errorObtenirProfe(buf.toString());
               return;
           }

           //Si ens retorna alguna cosa diferent a binari és un error
           if (!type.startsWith(
                   "application/octet-stream")) {
               this.getGestioAulesMIDlet().fitxerProfesNoText("La URL " +
                    strURLFitxerProfeMesParametre + " no és un fitxer binari");
               return;
           }

           this.getGestioAulesMIDlet().getGaugeConnectant().setValue(3);
           this.getGestioAulesMIDlet().getGaugeConnectant().
                   setLabel("Rebent dades...");

           //Llegir l'objecte profe
           byte[] persistedProfe = null; // Codi per obtenir l'objecte
           Professor profe = new Professor();

           int len = (int) mHttpConnection.getLength();

           if (len > 0) {
               in = mHttpConnection.openDataInputStream();
               persistedProfe = new byte[len];
               in.read(persistedProfe);
           } else {
               this.getGestioAulesMIDlet().longitudDadesNula(
                       "La URL: " + strURLFitxerProfeMesParametre +
                       "retorna un binari de longitud nula");
               return;
           }
           // Tancar la connexió
           in.close();
           mHttpConnection.close();

           try {
               if (persistedProfe!=null)
                   profe.deserialize(persistedProfe);
           } catch (IOException ioe) {
               // Control d'errors
               this.getGestioAulesMIDlet().networkException(ioe);
           }


           this.getGestioAulesMIDlet().setProfeActual(profe);
           //Mostrar la pantalla SeleccionUnGrup
           this.getGestioAulesMIDlet().mostrarSeleccionaUnGrup();
       } catch (ConnectionNotFoundException cne) {
           if (mCancel == false) {
               try {
                   if (in != null) {
                       in.close();
                   }
                   if (mHttpConnection != null) {
                       mHttpConnection.close();
                   }
               } catch (IOException ioe) {
                   ioe.printStackTrace();
               }
               cne.printStackTrace();

               this.getGestioAulesMIDlet().urlNotFound(
                       "ConnectionNotFoundException. No s'ha trobat la URL: " +
                       strURLFitxerProfeMesParametre);
               cne.printStackTrace();
           }
           mCancel = false;
           return;
       } catch (Exception e) {
           if (mCancel == false) {
               try {
                   if (in != null) {
                       in.close();
                   }
                   if (mHttpConnection != null) {
                       mHttpConnection.close();
                   }
               } catch (IOException ioe) {
                   ioe.printStackTrace();
               }
               e.printStackTrace();
               this.getGestioAulesMIDlet().networkException(e);
           }
           mCancel = false;
       }

   }
}

El codi es força similar al de ConnexioHTTPProfes. Destaquem però el control d'errors:

           String type = mHttpConnection.getType();
           //Si ens retorna un fitxer de text és un error
           if (type.startsWith("text/plain")) {
               in = mHttpConnection.openDataInputStream();
               int ch;
               while ((ch = in.read()) != -1) {
                   buf.append((char) ch);
                   System.out.println((char) ch);
               }
               this.getGestioAulesMIDlet().errorObtenirProfe(buf.toString());
               return;
           }

           //Si ens retorna alguna cosa diferent a binari és un error
           if (!type.startsWith("application/octet-stream")) {
               this.getGestioAulesMIDlet().fitxerProfesNoText("La URL " +
                    strURLFitxerProfeMesParametre + " no és un fitxer binari");
               return;
           }

I la obtenció de l'objecte Profe:

           //Llegir l'objecte profe
           byte[] persistedProfe = null; // Codi per obtenir l'objecte
           Professor profe = new Professor();

           int len = (int) mHttpConnection.getLength();

           if (len > 0) {
               in = mHttpConnection.openDataInputStream();
               persistedProfe = new byte[len];
               in.read(persistedProfe);
           } else {
               this.getGestioAulesMIDlet().longitudDadesNula(
                       "La URL: " + strURLFitxerProfeMesParametre +
                       "retorna un binari de longitud nula");
               return;
           }

Modificació del MIDlet per connectar a un objecte binari

Ara només ens cal executar la connexió per a obtenir l'objecte Profe. Aquesta connexió la farem justa abans de mostrar la pantalla Seleccionaungrup.

Igual que varem fer amb la pantalla frmPreferències, hem d'afegir entre el menú principal i la pantalla Seleccionaungrup, una pantalla de connexió:

SeleccionaUnGrup2.png

Podem tornar a utilitzar la mateixa pantalla que varem utilitzar per al formulari frmPreferències.

Ara cal canviar el codi font. Seleccioneu la opció Go to Source de la opció de menú Passar llista del menú principal. El codi font us ha de quedar de la següent manera:

String __selectedString = getLstMenu().getString(getLstMenu().getSelectedIndex());
 if (__selectedString != null) {
      if (__selectedString.equals("Passar llista")) {
           // write pre-action user code here
           switchDisplayable(null, getFrmConnectant());
           con1.go();
 }

On tant con com con1 són una variable membre del MIDlet:

...
private ConnexionsHTTP con;
private ConnexionsHTTP con1;
...

Que s'inicialitzen al final del mètode initialitze:

private void initialize() {
       ...
       // write post-initialize user code here
       con = new ConnexioHTTPProfes(this);
       con1 = new ConnexioHTTPProfe(this);
       con.start();
       con1.start();
   }

I es destrueixen al finalitzar el MIDlet:

public void destroyApp(boolean unconditional) {
       con.stop();
       con1.stop();
}

Ara només falta mostrar la llista de grups del professor actual. Hem de modificar el codi font de la pantalla Seleccionaungrup (utilitzeu Go To Source):

Al final del mètode getSeleccionaungrup() afegiu:

public List getSeleccionaungrup() {
....

           // write post-init user code here

           //Connectar-se al Servlet ProfeServlet i obtenir l'objecte profe, que
           //conté la llista de grups del professor.
           Professor profeActual = this.getProfeActual();
           Enumeration enumGrups = profeActual.getGrups().elements();
           int count = 0;
           boolean[] slctFlags = new boolean[100];
           for (; enumGrups.hasMoreElements();) {
               Grup grup = (Grup) enumGrups.nextElement();
               Seleccionaungrup.append(grup.getNom(), null);
               slctFlags[count] = false;
               count++;
           }
           slctFlags[0] = true;
           Seleccionaungrup.setSelectedFlags(slctFlags);
       }
       return Seleccionaungrup;

Com podeu veure, hem hagut de crear una variable membre de tipus Professor i els seus corresponents getters/setters:

   public Professor profeActual;
   private ConnexionsHTTP con;
   private ConnexionsHTTP con1;
   public Professor getProfeActual() {
       return profeActual;
   }
   public void setProfeActual(Professor profeActual) {
       this.profeActual = profeActual;
   }

El professor actual s¡estableix des de el mètode connect de la classe ConnexioHTTPProfe:

this.getGestioAulesMIDlet().setProfeActual(profe);

5é Exercici. LCDUI a baix nivell. Animació durant la connexió

Per realitzar aquest exercici, podeu partir de la versió 0.5.

Del repositori SVN del curs, baixeu-vos l'exercici:

/tags/GestioAules_v0.5

Consulteu, si encara no heu consultat, l'apartat Organització del SVN del curs i seguiu l'exemple checkout amb Netbeans de l'exemple Hola Mon! canviant el tag a /tags/GestioAules_v0.5.

Crear una animació

L'objectiu d'aquest exercici, és substituir la pantalla frmConnectant per una pantalla animada que mostri el progrés de la connexió. Per tal de realitzar aquesta finestra utilitzarem l' api de baix nivell de LCDUI.

La idea és mostrar una animació (un molinet rodant), que indica a l'usuari que s'està establint una connexió i que s'està a l'espera de rebre les dades.

El molinet el mostrarem en un pantalla de tipus Canvas. El primer pas és doncs crear una classe que hereti de l'objecte Canvas seguint els següents passos:

  • Creem una nova classe dins del paquet edu.upc.ice.GestioAules amb Crtl+n
  • Anomeneu a la classe connectantCanvas:
public class ConnectantCanvas extends Canvas {

...
}

De moment, l'únic que hem de fer és mostrar un missatge de text per la pantalla indicant que s'estant obtenint dades.

Les passes a seguir són:

  • Afegiu una variable membre de tipus String que contingui el missatge a mostrar. Creeu els mètodes get i set d'aquest objecte.
  • Implementeu el mètode paint(). Pinteu el missatge (si existeix) utilitzant l'objecte Graphics. Utilitzeu anchor points per tal de mostrar el missatge abaix de la pantalla i centrat (Graphics.BOTTOM | Graphics.HCENTER)
  • Al mètode setMessage(), invoqueu el mètode repaint() després d'establir el missatge. Així podeu força que es repinti el missatge en qualsevol moment del codi on utilitzeu el mètode setMessage.

El codi de la classe ConnectantCanvas us ha de quedar similar a:

package edu.upc.ice.GestioAules;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;
  
public class ConnectantCanvas extends Canvas {

 private String mMessage;

 public ConnectantCanvas() {
   this.setMessage(null);
 }
 
 public void setMessage(String s) {
   mMessage = s;
   repaint();
 }

 public String getMessage() {
   return mMessage;
 }

 public void paint(Graphics g) {
    
   // Mostrar un missatge si hi ha un missatge
   if (mMessage != null)
     g.drawString(mMessage, mWidth / 2, mHeight,
         Graphics.BOTTOM | Graphics.HCENTER);
 }
}

Ara es tracta d'eliminar l'objecte frmConnectant (recordeu que l'haureu d'eliminar des de la vista Flow) i el substituïu pel nou ConnectantCanvas. Us proporcionem el mètode getConnectantCanvas() que heu de substituir pel mètode getFrmConnectant():

public ConnectantCanvas getConnectantCanvas() {
    if (connectantCanvas == null) {
        connectantCanvas = new ConnectantCanvas();
        mCancelCommand = new Command("Cancel", Command.CANCEL, 0);
        connectantCanvas.addCommand(mCancelCommand);
        connectantCanvas.setCommandListener(this);
        connectantCanvas.setMessage("Rebent dades...");
    }
    return connectantCanvas;
}

Un cop ho tingueu, executeu l'aplicació per comprovar que es mostra un pantalla amb un missatge.

Ara anem a afegir el molinet. El molinet és una imatge similar a:

MolinetJavaME.png

El codi per pintar el molinet és:

// Mostrar el molinet
g.setColor(0, 0, 0);
g.drawArc(mX, mY, mRadius, mRadius, 0, 360);
g.fillArc(mX, mY, mRadius, mRadius, theta +  90, 90);
g.fillArc(mX, mY, mRadius, mRadius, theta + 270, 90);
  • On mX i mY són el centre del cercle (que correspon al centre de la pantalla).
  • On mRadius és el radi del molinet
  • El mètode drawArc pinta un cercle
  • I els mètodes fillArc pinten els arcs negres del molinet.

El valor de theta és el que hem d'anar modificant en el temps per tal d'animar el molinet. Abans de pintar el molinet, podem restablir la pantalla a blanc amb:

// Posar la pantalla en blanc
g.setColor(255, 255, 255);
g.fillRect(0, 0, mWidth, mHeight);

On mWidth i mHeight són les mides de la pantalla que s'obtenen amb:

mWidth = getWidth();
mHeight = getHeight();

Per augmentar el valor de la variable theta, utilitzarem una tasca temporitzada de Java (TimerTask):

// Crear un temporitzador per actualitzar el display
TimerTask task = new TimerTask() {
  public void run() {
    mCount = (mCount + 1) % mMaximum;
    repaint();
  }
 };
 Timer timer = new Timer();
 timer.schedule(task, 0, mInterval);

On:

  • mInterval és el número de milisegons que hi haura entre cada execució del mètode run de la tasca task
  • El mètode run augmenta un comptador anomenada mCount (que s'inicialitza a 0) de +1 en +1 fins a un valor màxim de mMaximum.

Theta es calcula amb:

int theta = -(mCount * 360 / mMaximum);

Per tant, mMaximum és el nombre d'iteracions de la tasca temporitzada per tal de donar una volta completa.

El codi final de la classe és:

package edu.upc.ice.GestioAules;

import java.util.Timer;
import java.util.TimerTask;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics; 
 

public class ConnectantCanvas extends Canvas {
  private int mCount, mMaximum;
  private int mInterval;

  private int mWidth, mHeight, mX, mY, mRadius;
  private String mMessage; 

  public ConnectantCanvas() {
    mCount = 0;
    mMaximum = 36;
    mInterval = 100; 

    mWidth = getWidth();
    mHeight = getHeight();

   // Calcular el radi
   int halfWidth = (mWidth - mRadius) / 2;
   int halfHeight = (mHeight - mRadius) / 2;
   mRadius = Math.min(halfWidth, halfHeight);

   // Calcular la posició
   mX = halfWidth - mRadius / 2;
   mY = halfHeight - mRadius / 2;

   // Crear un temporitzador per actualitzar el display
   TimerTask task = new TimerTask() {
     public void run() {
       mCount = (mCount + 1) % mMaximum;
       repaint();
     }
   };
   Timer timer = new Timer();
   timer.schedule(task, 0, mInterval);
 }

 public void setMessage(String s) {
   mMessage = s;
   repaint();
 }

 public void paint(Graphics g) {
   int theta = -(mCount * 360 / mMaximum);

   // Posar la pantalla en blanc
   g.setColor(255, 255, 255);
   g.fillRect(0, 0, mWidth, mHeight);

   // Mostrar el molinet
   g.setColor(0, 0, 0);
   g.drawArc(mX, mY, mRadius, mRadius, 0, 360);
   g.fillArc(mX, mY, mRadius, mRadius, theta +  90, 90);
   g.fillArc(mX, mY, mRadius, mRadius, theta + 270, 90);

   // Mostrar un missatge si hi ha un missatge
   if (mMessage != null)
     g.drawString(mMessage, mWidth / 2, mHeight,
         Graphics.BOTTOM | Graphics.HCENTER);
 }
}

6é exercici. Incorporar una animació gràfica realitzada amb l'API de jocs

Per realitzar aquest exercici, podeu partir de la versió 0.6.

Del repositori SVN del curs, baixeu-vos l'exercici:

/tags/GestioAules_v0.6

Consulteu, si encara no heu consultat, l'apartat Organització del SVN del curs i seguiu l'exemple checkout amb Netbeans del codi Hola Mon!, canviant el tag a /tags/GestioAules_v0.6.

Crear una nova entrada al menú principal i mostrar un canvas buit

A la pantalla principal del programa (objecte lstMenu), afegiu una nova entrada al menú (botó dret sobre l'objecte, escolliu la opció New/Addd i afegiu un Element List). Anomenarem a la nova entrada:

Passar l'estona ;-)

La pantalla us ha de quedar:

Exercici6Cursmobils.png

I la pantalla Flow:

Exercici6Cursmobils1.png

Ara es tracta de crear un nou objecte de tipus Canvas, és a dir una classe que hereti de Canvas. Col·loqueu-vos al paquet edu.upc.ice.gestioaules i creeu una nova classe amb Ctrl+N:

  • A la primera pantalla escolliu crear un objecte MIDP Canvas
  • A la segona li poseu nom a l'objecte: PersonaCaminantCanvas i finalitzeu l'assistent amb Finish.

De moment la classe PersonaCaminantCanvas ja ens està bé tal qual està. Ara cal lligar la opció de menú "Passar l'estona ;-)" amb aquesta pantalla. Aneu a la pestanya Flow i escolliu la opció Go To Source del menu contextual de l'entrada de menú corresponent.

On posa:

// write post-action user code here

Utilitzeu la funció 'switchDisplayable que us proporciona Netbeans per tal de mostrar el Canvas PersonaCaminantCanvas::

switchDisplayable(null, getCnvPersonaCaminantCanvas());

El tros de codi de la funció lstMenuAction(), us quedara de la següent manera:

} else if (__selectedString.equals("Passar l\'estona ;-)")) {                                     
    // write pre-action user code here
    switchDisplayable(null, getCnvPersonaCaminantCanvas());
    }  

Ara de moment la funció getCnvPersonaCaminantCanvas() no existeix i per aquesta raó us dona un error de compilació. Amb l'ajuda de Netbeans (poseu-vos a sobre la funció i seleccioneu la opció Create Method... del menú que apareix al prémer les tecles Alt+Enter). El resultat és un mètode com el següent:

private Displayable getCnvPersonaCaminantCanvas() {
       throw new UnsupportedOperationException("Not yet implemented");
}

Elimineu la línia:

throw new UnsupportedOperationException("Not yet implemented");

I posem el codi de creació de la pantalla PersonaCaminantCanvas, deixant el mètode de la següent manera:

private Displayable getCnvPersonaCaminantCanvas() {
       PersonaCaminantCanvas cnvPersona = new PersonaCaminantCanvas();

       return cnvPersona;
   }

Arribats a aquest punt, executeu el MIDlet i comproveu el seu funcionament.

IMPORTANT: Veureu que el MIDlet no acaba de funcionar del tot. Us apareixerà una finestra com la següent:
Exercici6Cursmobils2.png

Penseu, quin és el problema a l'hora de mostrar la nova pantalla? A que es degut aquest problema.

Podeu consultar la solució a aquí.

Ara ens cal afegir les comandes Sortir i Tornar, que ens permetran finalitzar el MIDlet o tornar al menú principal des de la pantalla PersonaCaminantCanvas.

Fins ara, havíem controlat totes les comandes des del mètode commandAction() del MIDlet GestioAules. en aquest cas aquestes dos comandes les controlarem des de la pantalla PersonaCaminantCanvas.

Al mètode commandAction de la classe PersonaCaminantCanvas posem el codi:

public void commandAction(Command command, Displayable displayable) {
       if (command==exitCommandPersonaCaminantCanvas) {
           midlet.exitMIDlet();
       } else if (command==backCommandPersonaCaminantCanvas) {
           midlet.switchDisplayable(null, midlet.getLstMenu());
       }
}

Per que no doni cap error aquest codi necessitem:

1) Afegir les variables membre exitCommandPersonaCaminantCanvas i backCommandPersonaCaminantCanvas:

...
public Command exitCommandPersonaCaminantCanvas;
public Command backCommandPersonaCaminantCanvas;
...

2) I afegir també una variable membre anomenada midlet, que contindra una referència al MIDlet principal:

public Command exitCommandPersonaCaminantCanvas;
public Command backCommandPersonaCaminantCanvas;
public GestioAules midlet;

Ara cal iniciar aquests valors al constructor. Eliminem el constructor que teníem i creem un nou constructor al qual se li pugui passar el MIDlet:

public PersonaCaminantCanvas(GestioAules midlet) {
       this.midlet = midlet;
       try {
           // Set up this canvas to listen to command events
           setCommandListener(this);
           // Add the Exit command
           exitCommandPersonaCaminantCanvas = new
                   Command("Sortir", Command.EXIT, 2);
           backCommandPersonaCaminantCanvas = new
                   Command("Tornar", Command.BACK, 1);
           addCommand(exitCommandPersonaCaminantCanvas);
           addCommand(backCommandPersonaCaminantCanvas);
       } catch (Exception e) {
           e.printStackTrace();
       }
       mHeight=getHeight();
       mWidth=getWidth();
   }

L'últim paràmetre a l'hora d'especificar una comanda és la prioritat que especifica quina és la importància de la comanda respecte a altres comandes de la mateixa pantalla.

Ara només cal modificar la classe GestioAules per tal de millorar el nostre programa i adaptar-lo als canvis que hem fet. Al mètode getCnvPersonaCaminantCanvas:

private Displayable getCnvPersonaCaminantCanvas() {
       if (cnvPersona == null) {
           cnvPersona = new PersonaCaminantCanvas(this);
           return cnvPersona;
       }
       return cnvPersona;
}

Creeu la variable membre cnvPersona:

...
private ConnectantCanvas connectantCanvas;
private PersonaCaminantCanvas cnvPersona;
...

Recordeu que aquesta forma de procedir (només crear la pantalla si no existia encara), correspon a la opció lazy initialized que utilitza Netebeans. Aquest mètode es força recomanable en programes on cal tenir cura dels recursos que s'utilitzen.

Per provar el correcte funcionament, podeu canviar l'idioma del mòbil a les propietats de WTK:

Menú Tools -> Java Platforms  -> Wireless Toolkit -> Pestanya Tools & Extensions -> Open Preferences

I18nSystemPropierty.png

Incorporar una animació sense l'API de jocs

Us proposem que basant-vos en l' exercici del molinet animat (ConnectantCanvas) , mostreu al canvas una animació d'un home caminant. Es tracta d'anar repintant la pantalla amb diferents imatges:

La solució (que podeu trobar a la versió 0.7 del programa), basant-nos en el codi de l'exercici del Molinet animat és:

package edu.upc.ice.GestioAules; 

import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import javax.microedition.lcdui.*;

public class PersonaCaminantCanvas extends Canvas implements CommandListener {

   private int mWidth;
   private int mHeight;
   public GestioAules midlet;
   public Command exitCommandPersonaCaminantCanvas;
   public Command backCommandPersonaCaminantCanvas;
   private int mCount;
  

   /**
    * constructor
    */
   public PersonaCaminantCanvas() {
       try {
           // Set up this canvas to listen to command events
           setCommandListener(this);
           // Add the Exit command
           exitCommandPersonaCaminantCanvas = new Command("Sortir", Command.EXIT, 2);
           backCommandPersonaCaminantCanvas = new Command("Tornar", Command.BACK, 1);
           addCommand(exitCommandPersonaCaminantCanvas);
           addCommand(backCommandPersonaCaminantCanvas);
       } catch (Exception e) {
           e.printStackTrace();
       }
       mHeight = getHeight();
       mWidth = getWidth();

       // Crear un temporitzador per actualitzar el display
       mCount=0;
       TimerTask task = new TimerTask() {
           public void run() {
               mCount = (mCount + 1) % 6;
               repaint();
           }
       };
       Timer timer = new Timer();
       timer.schedule(task, 0, 500);
   }

   public PersonaCaminantCanvas(GestioAules midlet) {
       this.midlet = midlet;
       try {
           // Set up this canvas to listen to command events
           setCommandListener(this);
           // Add the Exit command
           exitCommandPersonaCaminantCanvas = new Command("Sortir", Command.EXIT, 2);
           backCommandPersonaCaminantCanvas = new Command("Tornar", Command.BACK, 1);
           addCommand(exitCommandPersonaCaminantCanvas);
           addCommand(backCommandPersonaCaminantCanvas);
       } catch (Exception e) {
           e.printStackTrace();
       }
       mHeight = getHeight();
       mWidth = getWidth();
       // Crear un temporitzador per actualitzar el display
       mCount=0;
       TimerTask task = new TimerTask() {
           public void run() {
               mCount = (mCount + 1) % 6;
               repaint();
           }
       };
       Timer timer = new Timer();
       timer.schedule(task, 0, 500);
   }

   /**
    * paint
    */
   public void paint(Graphics g) {

       // Posar la pantalla en blanc
       g.setColor(255, 255, 255);
       g.fillRect(0, 0, mWidth, mHeight);

       Image current=null;
       try {
       switch (mCount) {
           case 0:
               current= Image.createImage("/PersonaCaminant1.png");
               break;
           case 1:
               current= Image.createImage("/PersonaCaminant2.png");
               break;
           case 2:
               current= Image.createImage("/PersonaCaminant3.png");
               break;
           case 3:
               current= Image.createImage("/PersonaCaminant4.png");
               break;
           case 4:
               current= Image.createImage("/PersonaCaminant5.png");
               break;
           case 5:
               current= Image.createImage("/PersonaCaminant6.png");
               break;
       }
       } catch (IOException io ) {
           
       }
       if (current!=null)
           g.drawImage(current, mWidth / 2, mHeight/2 ,
                   Graphics.HCENTER|Graphics.VCENTER);
       
   }

   /**
    * Called when a key is pressed.
    */
   protected void keyPressed(int keyCode) {
   }

   /**
    * Called when a key is released.
    */
   protected void keyReleased(int keyCode) {
   }

   /**
    * Called when a key is repeated (held down).
    */
   protected void keyRepeated(int keyCode) {
   }

   /**
    * Called when the pointer is dragged.
    */
   protected void pointerDragged(int x, int y) {
   }

   /**
    * Called when the pointer is pressed.
    */
   protected void pointerPressed(int x, int y) {
   }

   /**
    * Called when the pointer is released.
    */
   protected void pointerReleased(int x, int y) {
   }

   /**
    * Called when action should be handled
    */
   public void commandAction(Command command, Displayable displayable) {
       if (command == exitCommandPersonaCaminantCanvas) {
           midlet.exitMIDlet();
       } else if (command == backCommandPersonaCaminantCanvas) {
           midlet.switchDisplayable(null, midlet.getLstMenu());
       }
   }
}

Incorporar una animació amb l'API de jocs

Per realitzar aquest exercici, podeu partir de la versió 0.7.

Del repositori SVN del curs, baixeu-vos l'exercici:

/tags/GestioAules_v0.7

Consulteu, si encara no heu consultat, l'apartat Organització del SVN del curs i seguiu l'exemple checkout amb Netbeans del codi Hola Mon!, canviant el tag a /tags/GestioAules_v0.7.

Animacions amb l'API de jocs

L' API de jocs de Java ME ens proporciona l'objecte GameCanvas. Per tant el primer pas que hem de dur a terme és convertir la classe PersonaCaminantCanvas en filla de GameCanvas, i també, implementació de la classe Runnable:

public class PersonaCaminantCanvas extends GameCanvas implements CommandListener,Runnable {
...
}

Per tant la classe PersonaCaminantCanvas passa a ser també un fil d'execució.

Podeu utilitzar la utilitat Alt+Enter de Netbeans per implementar el mètode abstracta run() (opció implements all abstract methods).

El constructor de la classe GameCanvas requereix d'un paràmetre booleà. Aquest paràmetre especifica si volem ignorar els mètodes de control d'esdeveniments de teclat (mètodes keyPressed, keyReleased, etc...). En el nostra cas marcarem que sí, ja que controlarem els esdeveniments de teclat amb el mètode getKeyStates().

Afegiu a dins del/s constructor/s:

super(true);

Ara doncs podem esborrar tots els mètodes de control d'esdeveniments de teclat i de punter ja que no els utilitzarem.

Al mètode run, podeu afegir l'esquelet típic d'un joc implementat amb GameCanvas:

public void run() {
       Graphics g = getGraphics();
       while (true) {
           // Actualitzar l'estat del joc
           int k = getKeyStates();
           // controlar els esdeveniments de teclat

           //Aquí heu d'afegir els mètodes per pintar
           flushGraphics(0, 0, this.getWidth(), this.getHeight());
       }
   }

flushGraphics s'encarrega de mostrar per pantalla el que hi hagi al buffer off-screen. Teniu un mètode genèric:

flushGraphics();

Que ho repinta tot. Però normalment, amb repintar el que cap per pantalla n'hi suficient (és el cas de l'exemple).

Finalment també estaria bé canviar el nom de la classe a PersonaCaminantGameCanvas. Utilitzeu la opció Refactor del menú contextual. Canvieu el nom de la classe:

PersonaCaminantCanvas --> PersonaCaminantGameCanvas

Ara anem a modificar el MIDlet GestioAules, per adaptar-lo al nou Canvas de tipus GameCanvas. Com hem comentat, els GameCanvas són a més de Canvas, fils d'execució, i per tant els haurem d'incovar amb el mètode start(). Aneu a la pestanya Flow del MIDlet GestioAules i seleccioneu la opció Go To Source del menú contextual que es desplega quan feu clic al botó dret de la opció de menú "Passar l'estona ;" del menú principal.

Abans de la crida al mètode switchDisplayable afegiu les línies:

...
this.gameCanvas = (PersonaCaminantGameCanvas)  getCnvPersonaCaminantCanvas();
this.tPersona = new Thread(gameCanvas);
tPersona.start();

De forma que el codi quedi de la següent forma:

} else if (__selectedString.equals("Passar l\'estona ;-)")) {
   // write pre-action user code here
   
   this.gameCanvas = (PersonaCaminantGameCanvas)  getCnvPersonaCaminantCanvas();
   this.tPersona = new Thread(gameCanvas);
   tPersona.start();
   switchDisplayable(null, gameCanvas);
}

On tPersona i gameCanvas són dos variables membre de la classe GestioAules:

...
private PersonaCaminantGameCanvas gameCanvas;
private Thread tPersona;
...

Si no les teniu creades les podeu crear amb l'eina de Netbeans: Ctrl+Enter.

Abans de començar a utilitzar objectes Sprite i Layer, de moment podeu mostrar l'animació tal i com la mostràvem anteriorment cridant al mètode repaint dins de run():

public void run() {
       Graphics g = getGraphics();
       while (true) {
           // Actualitzar l'estat del joc
           int k = getKeyStates();
           // controlar els esdeveniments de teclat
           repaint();
           flushGraphics();
       }
}

Per controlar la velocitat d'execució tenim dos opcions:

  • Utilitzar el mètode Thread.sleep() per fer una pausa entre execucions del mètode run().
  • Utilitzar una tasca temporitzada (TimerTask)

La primera opció la podeu implementar afegint les línies

           mCount = (mCount + 1) % 6;
           try {
               Thread.sleep(200);
           } catch (InterruptedException ex) {
               ex.printStackTrace();
           }

Al mètode run, el codi us ha de quedar de la següent forma:

public void run() {
       Graphics g = getGraphics();
       while (true) {
           // Actualitzar l'estat del joc
           int k = getKeyStates();
           // controlar els esdeveniments de teclat
           repaint();
           flushGraphics();
           mCount = (mCount + 1) % 6;
           try {
               Thread.sleep(200);
           } catch (InterruptedException ex) {
               ex.printStackTrace();
           }
       }
}

Executeu el MIDlet amb diferents valors al mètode sleep(). Observeu el parpelleig a altes velocitats.

Podeu consultar la solució del que hem vist fins ara baixant-vos la versió 0.7.1 del projecte GestioAules.

L'altre alternativa és utilitzar un TimerTask. El podeu crear al constructor de la classe PersonaCaminantGAmeCanvas. Al final del constructor afegiu:

       // Crear un temporitzador per actualitzar el display
       mCount = 0;
       TimerTask task = new TimerTask() {

           public void run() {
               mCount = (mCount + 1) % 6;
               repaint();
           }
       };
       Timer timer = new Timer();
       timer.schedule(task, 0, 500);

I al mètode run(), NO poseu cap sleep():

public void run() {
       Graphics g = getGraphics();
       while (true) {
           // Actualitzar l'estat del joc
           int k = getKeyStates();
           // controlar els esdeveniments de teclat

           repaint();
           flushGraphics(0, 0, this.getWidth(), this.getHeight());
       }
}

Podeu consultar aquesta versió de l'exercici, baixant-vos la versió 0.7.1.1 del repositori de versions Subversion.

Sprites, Layers, TiledLayers i altres objectes de l'API de jocs:

Anem a utilitzar les eines que ens proporciona l'API de jocs i les ajudes de l'entorn de desenvolupament Netbeans. Al paquet edu.upc.ice.Gestioaules creeu una nova classe amb l'assistent de Netbeans:

  • Premeu Ctrl+n i seleccioneu un classe de tipus GameBuilder dins les opcions de MIDP
  • Anomeneu a la classe:
PersonaCaminantGameDesign

Us apareixerà l'assistent per al disseny de jocs de Netbeans:

GameBuilderPersonaCaminant0.png

Seleccioneu la opció Create Sprite. Creeu un sprite amb la imatge /mysprit3.png amb mides 40x91:

GameBuilderPersonaCaminant4.png

Ara utilitzeu l'assistent per a crear la seqüència d'animació de 6 imatges:

GameBuilderPersonaCaminant5.png

Podeu escollir els valors de velocitat que desitgeu i fer proves amb els botons "Endavant" i "Endarrere" de l'assistent.

Observeu que el codi font són mètodes de creació del objectes:

public Image getMysprite3() throws java.io.IOException {                                 
       if (mysprite3 == null) {                               
           // write pre-init user code here
           mysprite3 = Image.createImage("/mysprite3.png");                                  
       }                                
       // write post-init user code here
       return this.mysprite3;                        
   }
                      

   public Sprite getPersona() throws java.io.IOException {                                 
       if (persona == null) {                               
           // write pre-init user code here
           persona = new Sprite(getMysprite3(), 40, 91);                                  
           persona.setFrameSequence(provaseq001);                                
           // write post-init user code here
       }                        
       return persona;
   }

També es important que observeu la variable:

public int provaseq001Delay = 200;

Aquesta variable és la que heu d'utilitzar als vostres TimerTask per tal d'indicar la velocitat de l'animació.

Al costat de la pestanya GameBuilder, hi ha una pestanya que us permet escollir entre els diferents objectes del vostre joc. Torneu a la pantalla principal seleccionant Game Design. Des d'aquí, afegiu una nova escena i l'anomeneu:

EscenaPersonaCaminant.

Un cop dins de l'escena, amb el botó dret afegiu l'sprite de la persona caminant:

GameBuilderPersonaCaminant6.png

Arribats a aquest punt, aneu a la pestanya Source i observeu el codi font. El codi més important és:

public void updateLayerManagerForEscenaPersonaCaminant(LayerManager lm) throws java.io.IOException {
       // write pre-update user code here
       getPersona().setPosition(0, 0);                                                  
       getPersona().setVisible(true);
       lm.append(getPersona());                                                
       // write post-update user code here
   }     

Que és el mètode que utilitzarem per obtenir un objecte de tipus LayerManager amb tot el disseny gràfic del nostre joc.

Ara tornem a la classe PersonaCaminantGameCanvas per utilitzar aquest nou objecte per tal de canviar el repaint() del mètode run() per un lm.paint()

public class PersonaCaminantGameCanvas extends GameCanvas implements Runnable {
   ...
   ...
   public void run() {
      Graphics g = getGraphics();
      while(true) {
         // Actualitzar l'estat del joc
         int k = getKeyStates();
         // controlar els esdeveniments de teclat
	  lm.paint(g, 0, 0);
         flushGraphics();
      }
   }
}

lm és un objecte de tipus LayoutManager que conté tot el disseny gràfic del nostre joc. Aquest objecte s'ha d'inicialitzar. Una de les millors formes de fer-ho és amb un mètode init() que executarem al constructor de la classe PersonaCaminantGamaCanvas.

Un exemple del mètode init() podria ser:

private void init() throws IOException {
       gameDesign = new PersonaCaminantGameDesign();
       lm = new LayerManager();
       gameDesign.updateLayerManagerForEscenaPersonaCaminant(lm);
       PersonaCaminant = gameDesign.getPersona();
}

Fixeu-vos que el mètode pot llançar una excepció de tipus IOException. Caldrà doncs capturar IOException al cridar el mètode init (el podeu envolcallar amb un try-catch generat amb l'ajuda de Netbeans). El mètode init() el cridem al final del constructor:

public PersonaCaminantGameCanvas(this) {
       ...
       ...
       try {
           this.init();
       } catch (IOException ex) {
           ex.printStackTrace();
       }
}

Arribats a aquest punt executeu el MIDlet. Veureu que la escena no està animada. Per animar el joc només cal que utilitzeu el mètode PersonaCaminant.nextFrame():

public void run() {
       Graphics g = getGraphics();
       while (true) {
           // Actualitzar l'estat del joc
           int k = getKeyStates();
           // controlar els esdeveniments de teclat

           // Posar la pantalla en blanc
           g.setColor(255, 255, 255);
           g.fillRect(0, 0, mWidth, mHeight);
           lm.paint(g, 0, 0);
           PersonaCaminant.nextFrame();
           flushGraphics(0, 0, this.getWidth(), this.getHeight());
           try {
               Thread.sleep(gameDesign.provaseq001Delay);
           } catch (InterruptedException ex) {
               ex.printStackTrace();
           }
       }
   }

Observeu que cal pintar la pantalla de blanc cada cop que executem run per tal de que l'animació funcioni correctament.

Recordeu que podeu controlar la velocitat d'execució de l'animació amb :

  • Thread.sleep
  • TimerTask

Podeu consultar els diferents resultats, baixant-vos les versions 0.7.2 i 0.7.3.

Creació d'una escena més completa. Sprite més fons de pantalla TiledLayer

Per treballar amb l'API GFC (Generic Connection Framework) i els fils d'execució partirem de la versió 0.7.3.

Del repositori SVN del curs, baixeu-vos l'exercici:

/tags/GestioAules_v0.7.3

Consulteu, si encara no heu consultat, l'apartat Organització del SVN del curs i seguiu l'exemple checkout amb Netbeans de l'exemple Hola Mon! canviant el tag a /tags/GestioAules_v0.7.3.

Escena més complexa

Anem a fer l'escena una mica més completa. La idea és afegir un fons de pantalla pel qual es desplaçara l'sprite Persona.

La imatge de fons serà de tipus TiledLayer i ha de quedar similar al següent:

GameBuilderPersonaCaminant1.png

Aquest fons de pantalla el podeu crear amb l'opció Create Tile Layer de la classe PersonaCaminantGameDesign. Utilitzeu la imatge platform_tiles.png per tal de crear el fons de pantalla.

A l'escena afegim la persona caminant:

GameBuilderPersonaCaminant.png

Per fer caminar la persona per la pantalla de fons, només cal afegir una tasca temporitzada que actualitzi la posició horizontal (x) de l'sprite:

       xPosition = PersonaCaminant.getX();
       posicio_inicial= xPosition;
       TimerTask task2 = new TimerTask() {
           public void run() {
               xPosition = (xPosition + 1);
               if (xPosition > 190) {
                   xPosition = posicio_inicial;
           }
           PersonaCaminant.setPosition(xPosition, PersonaCaminant.getY());
       }};
       Timer timer2 = new Timer();
       timer2.schedule(task2, 0, 10);

Aquest codi es pot afegir al mètode init(). Arribats a aquest punt es quan potser observareu que és molt més flexible tenir tasques temporitzades que no pas utilitzar la funció sleep(). Al mètode init() tindrem dos tasques temporitzades:

  • Una tasca que s'encarrega d'animar l'sprite de la persona caminant (simular el moviment de cames)
  • Una tasca encarregada de desplaçar l'sprite horitzontalment per la pantalla.
private void init() throws IOException {
       gameDesign = new PersonaCaminantGameDesign();
       lm = new LayerManager();
       lm.setViewWindow(0, 0, mWidth, mHeight);
       gameDesign.updateLayerManagerForEscenaPersonaCaminant(lm);
       PersonaCaminant = gameDesign.getPersona();

       TimerTask task = new TimerTask() {

           public void run() {
               PersonaCaminant.nextFrame();
           }
       };
       Timer timer = new Timer();
       timer.schedule(task, 0, gameDesign.provaseq001Delay);

       xPosition = PersonaCaminant.getX();
       xPosition = PersonaCaminant.getX();
       posicio_inicial = xPosition;
       TimerTask task2 = new TimerTask() {

           public void run() {
               xPosition = (xPosition + 1);
               if (xPosition > 190) {
                   xPosition = posicio_inicial;
               }
               PersonaCaminant.setPosition(xPosition, PersonaCaminant.getY());
           }}
           ;
           Timer timer2 = new Timer();

           timer2.schedule (task2, 0,10);

   }
}

Fixeu-vos que la persona anirà caminant fins desaparèixer de la pantalla (al superar la posició x 190). En aquest moment tornarà a aparèixer per la posició inicial.

Canviar la direcció de la persona caminant amb les tecles

Anem a utilitzar el mètode getKeyStates() per tal de canviar la direcció cap a la qual camina la persona. Al mètode run(), després de la línia:

int k = getKeyStates();

Afegiu:

           // controlar els esdeveniments de teclat
           if ((k & LEFT_PRESSED) != 0) {
               if (endavant) {
                   endavant = false;
                   PersonaCaminant.setTransform(
                           PersonaCaminant.TRANS_MIRROR);
               }
           } else if ((k & RIGHT_PRESSED) != 0) {
               if (!endavant) {
                   endavant = true;
                   PersonaCaminant.setTransform(
                           PersonaCaminant.TRANS_NONE);
               }
           }

On endavant és un booleà que indicarà en quina direcció ha d'anar la persona (endavant=true ---> avança cap endavant | endavant=false avança cap a l'esquerre).

Creeu el booleà amb l'ajuda de Ctrl+Enter de Netbeans:

private booolean endavant; 

Fixeu-vos que per canviar la direcció de l'sprite persona només cal aplicar la transformació TRANS_MIRROR:

PersonaCaminant.setTransform(
                           PersonaCaminant.TRANS_MIRROR);

Fixeu-vos que per tornar a canviar la direcció no s'utilitza un altre cop TRANS_MIRROR. Les transformacions són sempre sobre la imatge original i no s'apliquen transformacions a transformacions:

PersonaCaminant.setTransform(
                           PersonaCaminant.TRANS_NONE);

TRANS_NONE ens permet tornar a la posició inicial de l'sprite.

Per tal que la transformació sigui del tot correcte cal indicar quin és el píxel de referència. Sinó indiquem quin és el píxel, la transformació es realitza a partir del píxel (0,0) de l'sprite.

Al mètode init() afegiu:

PersonaCaminant.defineReferencePixel(20, 45);

On 20,45 és el centre més aproximat de l'sprite persona (que recordeu fa 40,91 de dimensions).

Ara també cal controlar "l'increment" de la posició x de l'sprite persona, ja que al canviar de direcció l'increment passarà a ser un decrement. A més aturarem a la persona quan arribi als límits de la pantalla. La tasca2 del mètode init ha de quedar de la següent forma:

       timer.schedule(task, 0, 200);

       xPosition = PersonaCaminant.getX();
       TimerTask task2 = new TimerTask() {

           public void run() {
               if (endavant) {
                   xPosition = (xPosition + 1);
               } else {
                   xPosition = (xPosition - 1);
               }

               if (xPosition > 190) {
                   xPosition = 190;
               }
               if (xPosition < 10) {
                   xPosition = 10;
               }
               PersonaCaminant.setPosition(xPosition, PersonaCaminant.getY());
           }
       };
       Timer timer2 = new Timer();
       timer2.schedule(task2, 0, 10);

Per fer l'escena una mica més realista, afegirem unes parets que impediran que la persona avanci més enllà dels límits esquerre i dret de la pantalla. Les següents captures de pantalla us poden donar una idea de l'efecte que desitgem:

GameBuilderPersonaCaminant2.png
GameBuilderPersonaCaminant3.png

El TiledLayer està format per 20 files i 15 columnes. Cada Tile (rajola) té les mides 16x16, i per tant la pantalla fa 320 píxels d'alçada i 240 d'amplada.

7é exercici. Suportar internacionalització (i18n)

Per realitzar aquest exercici, podeu partir de la versió 0.7.5.

Del repositori SVN del curs, baixeu-vos l'exercici:

/tags/GestioAules_v0.7.5

Consulteu, si encara no heu consultat, l'apartat Organització del SVN del curs i seguiu l'exemple checkout amb Netbeans del codi Hola Mon!, canviant el tag a /tags/GestioAules_v0.7.5.

Afegir suport per a la internacionalització

Per tal que la nostra aplicació suporti internacionalització, podeu utilitzar l'assistent de Netbeans per tal de crear la classe LocalizationSupport i el fitxer de propietats:

Fitxer:LocalizationSupport1.png

Com podeu veure a la següent pantalla escolliu on crear el fitxer de propietats:

LocalizationSupport2.png

Ara només us fa falta omplir els fitxers de propietats i fer-ne tants com idiomes vulgueu suportar. En aquest exemple només fem internacional el menú principal:

LocalizationSupport3.png

I només posem 3 idiomes. Els fitxers queden de la següent manera:

Anglès:

# MIDP Localization Support Default Messages Bundle
# Here you can put your keys and values
# To add another language, select "Add New Locale" from pop-up menu
# on this file ...
#Opcions del menú principal
MENU_PASSAR_LLISTA=Take the attendance
MENU_GESTIO=Managment
MENU_INFORMES=Reports
MENU_PREFERENCIES=Preferences
MENU_PASSA_LESTONA=Get Fun! ;-) 

Català:

# MIDP Localization Support Default Messages Bundle
# Here you can put your keys and values
# To add another language, select "Add New Locale" from pop-up menu
# on this file ...
#Opcions del menú principal
MENU_PASSAR_LLISTA=Passar llista
MENU_GESTIO=Gestió
MENU_INFORMES=Informes
MENU_PREFERENCIES=Preferències
MENU_PASSA_LESTONA=Passar l'estona ;-)

I castellà:

# MIDP Localization Support Default Messages Bundle
# Here you can put your keys and values
# To add another language, select "Add New Locale" from pop-up menu
# on this file ...
#Opcions del menú principal
MENU_PASSAR_LLISTA=Pasar lista
MENU_GESTIO=Gestión
MENU_INFORMES=Informes
MENU_PREFERENCIES=Preferencias
MENU_PASSA_LESTONA=Pasar el rato ;-)

Els diferents fitxers es poden crear amb la opció Create locale del menú contextual del fitxer message.propierties.

Ara només cal utilitzar aquests missatges al menú principal. Des del menú Flow en comptes de posar un text fix, poseu:

LocalizationSupport4.png

El codi font (el qual recordeu que no podeu editar) queda de la següent forma:

public List getLstMenu() {
       if (lstMenu == null) {
           // write pre-init user code here
           lstMenu = new List("Men\u00FA", Choice.IMPLICIT);
           lstMenu.append(LocalizationSupport.getMessage("MENU_PASSAR_LLISTA"), null);
           lstMenu.append(LocalizationSupport.getMessage("MENU_GESTIO"), null);
           lstMenu.append(LocalizationSupport.getMessage("MENU_INFORMES"), null);
           lstMenu.append(LocalizationSupport.getMessage("MENU_PREFERENCIES"), null);
           lstMenu.append(LocalizationSupport.getMessage("MENU_PASSA_LESTONA"), null);
           lstMenu.addCommand(getExitCommand());
           lstMenu.setCommandListener(this);
           lstMenu.setFitPolicy(Choice.TEXT_WRAP_DEFAULT);
           lstMenu.setSelectedFlags(new boolean[] { false, false, false, false, false });
       }
       return lstMenu;
   }

8é exercici. Suport per a serveis web

Per realitzar aquest exercici, podeu partir de la versió 0.8.

Del repositori SVN del curs, baixeu-vos l'exercici:

/tags/GestioAules_v0.8

Consulteu, si encara no heu consultat, l'apartat Organització del SVN del curs i seguiu l'exemple checkout amb Netbeans del codi Hola Mon!, canviant el tag a /tags/GestioAules_v0.8.

Modificacions de la part servidora. GestioAulesServlet

El nostre objectiu és que l'aplicació mòbil Java ME es connecti a un servidor (GestioAulesServlet) el qual li proporcionarà un fitxer XML amb la informació d'un grup d'alumnes específics.

En un sistema real, el document XML es generaria de forma dinàmica a partir de la informació d'una base de dades. En el nostre cas, per simplificar el sistema, el document XML serà un document estàtic que prèviament haurem generat.

Podem crear aquest document XML directament amb Netbeans. Descarregueu-vos la versió 0.1 del Servlet (el tag GestioAulesServlet_v0.1). Per crear un nou document XML, situe-vos a la carpeta Web Pages i creeu un nou document XML amb Ctrl+N:

NouDocumentXML.png

Assigneu-li en nom de la imatge:

NouDocumentXML2.png
NouDocumentXML3.png

El document XML podria contenir per exemple:

<?xml version="1.0" encoding="UTF-8"?>


<grup nom="1r ESI Tarda">
<alumne>
    <nom>Pere</nom>
    <cognoms>Abad Abellà</cognoms>    
    <foto>http://localhost:8084/GestioAulesServlet/no_avatar.gif</foto>
</alumne>  

<alumne>
    <nom>Pere</nom>
    <cognoms>Abad Abellà</cognoms>
    <foto>http://localhost:8084/GestioAulesServlet/no_avatar.gif</foto>
</alumne>  

<alumne>
    <nom>Joan</nom>
    <cognoms>Coll Català</cognoms>
    <foto>http://localhost:8084/GestioAulesServlet/no_avatar.gif</foto>
</alumne>

<alumne>
    <nom>Julia</nom>
    <cognoms>Mauri Soler</cognoms>
    <foto>http://localhost:8084/GestioAulesServlet/no_avatar.gif</foto>
</alumne> 

</grup>

Comproveu que el document és accessible des de un navegador web (caldrà que executeu el projecte GestioAulesServlet):

http://localhost:8084/GestioAulesServlet/exempleGrup.xml

Accés i parsing d'un fitxer remot XML. UNMARSHALLING

Al procés de convertir un fitxer XML en una sèrie d'objectes Java, també se l'anomena marchalling.

Amb Java SE podem utilitzar ajudes per a fer Marshalling (per exemple utilitzar Commons Digester). Amb Java ME ho haurem de fer des de 0 amb SAX.

Primer de tot cal disposar de les classes equivalents per a guardar un fitxer XML com el que hem vist en l'apartat anterior. Per tant necessitarem una classe Grup i una classe Alumne.

La classe Grup ja la teniem però hi hem afegit un vector d'objectes de tipus Alumne i un mètode toString():

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package edu.upc.ice.gestioAules;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Vector; 

/**
 *
 * @author sergi
 */
public class Grup implements Persistent {  

   public String nom;
   public Vector alumnes;

   public Vector getAlumnes() {
       return alumnes;
   }

   public void setAlumnes(Vector alumnes) {
       this.alumnes = alumnes;
   }

   public String getNom() {
       return nom;
   }

   public void setNom(String nom) {
       this.nom = nom;
   }

   public Grup() {
       alumnes = new Vector();
   }

   public Grup(String nom) {
       this.nom = nom;
       alumnes = new Vector();
   }

   public Grup(String nom, Vector alumnes) {
       this.nom = nom;
       this.alumnes = alumnes;
   }

   public String toString() {
       String newline = System.getProperty("line.separator");
       StringBuffer buf = new StringBuffer();

       buf.append("Grup:").append(this.getNom()).append(newline);
       buf.append("--- Alumnes ---").append(newline);
       for (int i = 0; i < alumnes.size(); i++) {
           buf.append(alumnes.elementAt(i)).append(newline);
       }

       return buf.toString();
   }

   public void deserialize(byte[] data) throws IOException {
       //Pendent d'implementar la llista d'alumnes
       ByteArrayInputStream bin = new ByteArrayInputStream(data);
       DataInputStream din = new DataInputStream(bin);

       this.setNom(din.readUTF());
       din.close();
       bin.close();
   }

   public byte[] serialize() throws IOException {
       ByteArrayOutputStream bout = new ByteArrayOutputStream();
       DataOutputStream dout = new DataOutputStream(bout);

       dout.writeUTF(this.getNom());
       dout.flush();
       return bout.toByteArray();
   }
}

La classe Alumne pot ser tant senzilla com:

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package edu.upc.ice.gestioAules;

/**
 *
 * @author sergi
 */
public class Alumne {

   public String nom;

   public String cognoms;

   public String fotoURL;

   public Alumne() {
       nom="";
       cognoms="";
       fotoURL="";
   }

   public Alumne(String nom, String cognoms, String fotoURL) {
       this.nom = nom;
       this.cognoms = cognoms;
       this.fotoURL = fotoURL;
   }

   public String toString() {
       String newline = System.getProperty("line.separator");
       StringBuffer buf = new StringBuffer();

       buf.append("---Alumne---:").append(newline);
       buf.append("Nom:").append(this.getNom()).append(newline);
       buf.append("Cognoms:").append(this.getCognoms()).append(newline);
       buf.append("FotoURL:").append(this.getFotoURL()).append(newline);

       return buf.toString();
   }

   
   
   public String getCognoms() {
       return cognoms;
   }

   public void setCognoms(String cognoms) {
       this.cognoms = cognoms;
   }

   public String getFotoURL() {
       return fotoURL;
   }

   public void setFotoURL(String fotoURL) {
       this.fotoURL = fotoURL;
   }

   public String getNom() {
       return nom;
   }

   public void setNom(String nom) {
       this.nom = nom;
   }

}


Ara es tracta de crear un Handler per a parsejar aquest tipus de document. Creem una classe anomenada GrupHandler :

package edu.upc.ice.gestioAules;

import java.util.Stack;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;  

/**
 *
 * @author sergi
 */
public class GrupHandler extends DefaultHandler {

   public Grup grup;
   private Stack stack;
   private boolean isStackReadyForText = false;

   public GrupHandler() {
       stack = new Stack();
       isStackReadyForText = false;
   }

   public Grup getGrup() {
       return grup;
   }

   public void endDocument() throws SAXException {
       super.endDocument();
   }

   public void endElement(String uri, String localName, String qName) throws SAXException {

       isStackReadyForText = false;

       Object tmp = stack.pop();

       if (qName.equals("grup")) {
           grup = (Grup) tmp;
       } else if (qName.equals("alumne")) {
           ((Grup) stack.peek()).getAlumnes().addElement((Alumne) tmp);
       } else if (qName.equals("nom")) {
           ((Alumne) stack.peek()).setNom( tmp.toString() );
       } else if (qName.equals("cognoms")) {
           ((Alumne) stack.peek()).setCognoms(tmp.toString() );
       } else if (qName.equals("foto")) {
           ((Alumne) stack.peek()).setFotoURL(tmp.toString() );
       } else {
           stack.push(tmp);
       }

   }

   public void startDocument() throws SAXException {
       super.startDocument();
   }

   public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

       if ("grup".equals(qName)) {
           stack.push(new Grup());
           String nom= attributes.getValue("nom");
           Grup grup = ((Grup) stack.peek());
           grup.setNom(nom);
       } else if ("alumne".equals(qName)) {
           stack.push(new Alumne());
       } else if ("nom".equals(qName) || "cognoms".equals(qName) ||
               "foto".equals(qName)) {
           stack.push(new StringBuffer());
           isStackReadyForText = true;
       }
   }

   public void characters(char[] ch, int start, int length) throws SAXException {

       // if stack is not ready, data is not content of recognized element
       if (isStackReadyForText == true) {
           ((StringBuffer) stack.peek()).append(ch, start, length);
       } else {
           // read data which is not part of recognized element
       }
   }
}

El truc consisteix en anar emmagatzemant els objectes en una pila.

Ara cal utilitzar una connexió HTTP per accedir al fitxer XML. Com totes les connexions que hem fet fins ara, creem una classe anomenada ConnexioHTTPGrup:

package edu.upc.ice.gestioAules;

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Vector;
import javax.microedition.io.ConnectionNotFoundException;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory; 

/**
 *
 * @author Sergi Tur Badenas
 */
public class ConnexioHTTPGrup extends ConnexionsHTTP {

    public static final String GRUP_XML = "/exempleGrup.xml"; 

   public ConnexioHTTPGrup(GestioAules gestioAulesMIDlet) {
       super(gestioAulesMIDlet);
   }

   protected void connect() {
       InputStream in = null;
       StringBuffer strURLServidor = null;
       StringBuffer buf = new StringBuffer();
       GrupHandler handler = new GrupHandler();

       try {

           int rc;
           strURLServidor = new StringBuffer(this.getGestioAulesMIDlet().
                   getTextFieldURLServidor().getString());
           strURLServidor.append(ConnexioHTTPGrup.GRUP_XML);

           mHttpConnection = (HttpConnection) Connector.open(
                   strURLServidor.toString());

           //Obtenir el codi de resposta
           rc = mHttpConnection.getResponseCode();
           if (rc != HttpConnection.HTTP_OK) {
               this.getGestioAulesMIDlet().urlNotFound(
                       "No s'ha trobat la URL: " + strURLServidor);
               return;
           }

           String type = mHttpConnection.getType();

           //Si ens retorna alguna coda diferent a XML és un error
           if (!type.startsWith("application/xml")) {
               this.getGestioAulesMIDlet().fitxerProfesNoText("La URL " +
                       strURLServidor.toString() + " no és un fitxer XML");
               return;
           }

           int len = (int) mHttpConnection.getLength();

           if (len > 0) {
               in = mHttpConnection.openDataInputStream();
           } else {
               this.getGestioAulesMIDlet().longitudDadesNula(
                       "La URL: " + strURLServidor +
                       "retorna un XML de longitud nula");
               return;
           }
           //Parsejar el fitxer XML
           SAXParserFactory factory = SAXParserFactory.newInstance();
           try {
               SAXParser saxParser = factory.newSAXParser();
               //Obtenir el document XML d'una URL remota
               saxParser.parse(in, handler);
               System.out.println(handler.getGrup());
           } catch (Throwable t) {
               t.printStackTrace();
           }           
           // Tancar la connexió
           in.close();
           mHttpConnection.close();

           this.getGestioAulesMIDlet().setGrupActual(handler.getGrup());
           //Mostrar la pantalla SeleccionUnGrup
           this.getGestioAulesMIDlet().mostrarLstAlumnesGrup();
       } catch (ConnectionNotFoundException cne) {
           if (mCancel == false) {
               try {
                   if (in != null) {
                       in.close();
                   }
                   if (mHttpConnection != null) {
                       mHttpConnection.close();
                   }
               } catch (IOException ioe) {
                   ioe.printStackTrace();
               }
               cne.printStackTrace();

               this.getGestioAulesMIDlet().urlNotFound(
                       "ConnectionNotFoundException. No s'ha trobat la URL: " +
                       strURLServidor.toString());
               cne.printStackTrace();
           }
           mCancel = false;
           return;
       } catch (Exception e) {
           if (mCancel == false) {
               try {
                   if (in != null) {
                       in.close();
                   }
                   if (mHttpConnection != null) {
                       mHttpConnection.close();
                   }
               } catch (IOException ioe) {
                   ioe.printStackTrace();
               }
               e.printStackTrace();
               this.getGestioAulesMIDlet().networkException(e);
           }
           mCancel = false;
       }

   }
}

A la comanda OkCommand de la pantalla d'alerta infoDataGrupActual, fem la connexió:

          else if (displayable == infoDataGrupActual) {
           if (command == cancelCommand) {
               // write pre-action user code here
               switchDisplayable(null, getSeleccionaungrup());
           // write post-action user code here
           } else if (command == okCommand) {
               // write pre-action user code here
               con2.go();
               switchDisplayable(null, getConnectantCanvas());

           // write post-action user code here
           }


On con 2 és una variable de tipus ConnexionsHTTP:

private ConnexionsHTTP con2;

Que hem inicialitzar al mètode initialize:

private void initialize() {
    ...

       // write post-initialize user code here
       con = new ConnexioHTTPProfes(this);
       con1 = new ConnexioHTTPProfe(this);
       con2 = new ConnexioHTTPGrup(this);
       con.start();
       con1.start();
       con2.start();
   }

I hem eliminat al destructor del MIDlet:

public void destroyApp(boolean unconditional) {
       if (con != null) {
           con.stop();
       }
       if (con1 != null) {
           con1.stop();
       }
       if (con2 != null) {
           con2.stop();
       }

       if (gameCanvas != null) {
           gameCanvas.stop();
       }
   }

Al mètode que prepara la pantalla LstAlumnesGrup:

public List getLstAlumnesGrup() {
        if (lstAlumnesGrup == null) {
           // write pre-init user code here
           lstAlumnesGrup = new List("Alumnes del grup", Choice.IMPLICIT);
           lstAlumnesGrup.addCommand(getExitCommand2());
           lstAlumnesGrup.addCommand(getCancelCommand3());
           lstAlumnesGrup.addCommand(getHelpCommand1());
           lstAlumnesGrup.setCommandListener(this);
           lstAlumnesGrup.setSelectedFlags(new boolean[] {  });


           if (this.getGrupActual() != null) {
               Enumeration enumLines = this.getGrupActual().getAlumnes().elements();
               for (; enumLines.hasMoreElements();) {
                   Alumne alu = (Alumne) enumLines.nextElement();
                   try {
                       image1 = Image.createImage("/no_avatar.gif");
                   } catch (java.io.IOException e) {
                       e.printStackTrace();
                   }
                   lstAlumnesGrup.append(alu.getNom() + " " + alu.getCognoms(),
                           image1);
               }
           }
       }
       return lstAlumnesGrup;
   }

Recursos:

Resolució de problemes. Troubleshooting

IllegalStateException

Els objectes de tipus formulari (Form), poden emetre una excepció de tipus IllegalStateException, si durant la construcció de l'objecte, s'intenta afegir un Item de formulari que ja sigui estant utilitzat en un altre formulari.

Abans de cridar al mètodes get dels formularis (per exemple getFrmFormularis), cal tornar a establir a zero els objectes que vulguem reutilitzar:

       this.frmPreferencies=null;
       this.textFieldURLServidor=null;
       this.choiceGroupProfessor=null;
       this.spacer=null;
       switchDisplayable(null, getFrmPreferencies());

Problemes de refresc. No s'actualitzen els formularis

public void mostrarFormulariPreferencies() {
       this.frmPreferencies=null;
       this.textFieldURLServidor=null;
       this.choiceGroupProfessor=null;
       this.spacer=null;
       switchDisplayable(null, getFrmPreferencies());
   }

Urlencode

Les URL de crida al Servlet ProfeServlet tenen caràcters no vàlids com l'espai:

http://localhost:8084/GestioAulesServlet/ProfeServlet?name=Tim Berners-Lee

Introduir una URL com l'anterior en una navegador web actual no és cap problema per que la converteix a:

http://localhost:8084/GestioAulesServlet/ProfeServlet?name=Tim%20Berners-Lee

Però en Java ME cal que realitzem nosaltres aquesta tasca. A Java SE, diposem d'un objecte URL i un mètode anomenat encodeURL que soluciona aquest tipus de problemes. Aquest mètode no està disponible però a Java ME.

Us proporcionem una classe que amb un mètode estàtic que realitza la codificació:

 package edu.upc.ice.GestioAules;
 
 /**
  *
  * @author sergi
  */
 public class Utilities {
 
    public static String urlEncode(String s) {
    StringBuffer sbuf = new StringBuffer();
    int len = s.length();
    for (int i = 0; i < len; i++) {
        int ch = s.charAt(i);
        if ('A' <= ch && ch <= 'Z') { // 'A'..'Z'
            sbuf.append((char)ch);
        } else if ('a' <= ch && ch <= 'z') { // 'a'..'z'
            sbuf.append((char)ch);
        } else if ('0' <= ch && ch <= '9') { // '0'..'9'
            sbuf.append((char)ch);
        } else if (ch ==':') {
            sbuf.append((char)ch);
        } else if (ch =='/') {
            sbuf.append((char)ch);
        } else if (ch =='?') {
            sbuf.append((char)ch);
        } else if (ch =='=') {
            sbuf.append((char)ch);
        } else if (ch == ' ') { // space
            sbuf.append('+');
        } else if (ch == '-' || ch == '_' // unreserved
                || ch == '.' || ch == '!'
                || ch == '~' || ch == '*'
                || ch == '\\' || ch == '('
                || ch == ')') {
            sbuf.append((char)ch);
        } else if (ch <= 0x007f) { // other ASCII
            sbuf.append(hex[ch]);
        } else if (ch <= 0x07FF) { // non-ASCII <= 0x7FF
            sbuf.append(hex[0xc0 | (ch >> 6)]);
            sbuf.append(hex[0x80 | (ch & 0x3F)]);
        } else { // 0x7FF < ch <= 0xFFFF
            sbuf.append(hex[0xe0 | (ch >> 12)]);
            sbuf.append(hex[0x80 | ((ch >> 6) & 0x3F)]);
            sbuf.append(hex[0x80 | (ch & 0x3F)]);
        }
    }
    return sbuf.toString();
 }
 
 // Hex constants.
 final static String[] hex = {
    "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
    "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
    "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
    "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
    "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
    "%28", "%29", "%2a", "%2b", "%2c", "%2d", "%2e", "%2f",
    "%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37",
    "%38", "%39", "%3a", "%3b", "%3c", "%3d", "%3e", "%3f",
    "%40", "%41", "%42", "%43", "%44", "%45", "%46", "%47",
    "%48", "%49", "%4a", "%4b", "%4c", "%4d", "%4e", "%4f",
    "%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57",
    "%58", "%59", "%5a", "%5b", "%5c", "%5d", "%5e", "%5f",
    "%60", "%61", "%62", "%63", "%64", "%65", "%66", "%67",
    "%68", "%69", "%6a", "%6b", "%6c", "%6d", "%6e", "%6f",
    "%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77",
    "%78", "%79", "%7a", "%7b", "%7c", "%7d", "%7e", "%7f",
    "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
    "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
    "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
    "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
    "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
    "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
    "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
    "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
    "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
    "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
    "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
    "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
    "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
    "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
    "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
    "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
 };
 
}

/dev/dsp: Device or resource busy

Si a l'executar aplicacions Java ME amb l'emulador Wireless Toolkit de Java no tenim so, fixeu-vos si us apareix un missatge com el següent a la finestra Output:

/dev/dsp: Device or resource busy

Sembla ser que està relacionat amb el plugin de Flash de Firefox. Aquest plugin deixà bloquejat aquest dispositiu. Una solució simple és tancar Firefox al provar aplicacions mòbils de Java amb só.

OpenFPnet
IES Nicolau Copèrnic