Visualizzazione post con etichetta Java. Mostra tutti i post
Visualizzazione post con etichetta Java. Mostra tutti i post

venerdì 8 ottobre 2010

La classe InetAddress

La classe InetAddres (del package java.net) è in grado di rappresentare un indirizzo IP di rete. All'interno troviamo alcuni metodi statici (non hanno bisogno di un'istanza della classe) particolarmente utili:
  • static InetAddress[] getAllByName(String host): ritorna un array di indirizzi IP riconducibili al nome dell'host;
  • static InetAddress getByAddress(byte[] addr): ritorna un oggetto InetAddress a partire da un indirizzo IP (passato in forma binaria, per IPv4 occorrono 4 byte);
  • static InetAddress getByAddress(String host, byte[] addr): ritorna un oggetto InetAddress riconducibile ai paramatri host ed addr;
  • static InetAddress getByName(String host): ritorna un oggetto InetAddress corrispondente all'host passato;
In caso di errori, nome dell'host o indirizzo IP errato, questi metodi scatenano un'eccezione di tipo UnknownHostException, ragion per cui vanno sempre innescati all'interno di blocchi try/catch. Vi occorre sapere l'indirizzo IP di un host? Poche righe di codice e il problema è risolto:
package address;

import java.net.*;

public class AddressTest {
 public static void main(String[] args) {
  try {
   String hostname=args[0];
   InetAddress ip=InetAddress.getByName(hostname);
   System.out.println(" Hostname: "+hostname);
   System.out.println(" Indirizzo IP: "+ip.toString().substring(args[0].length()+1));
  }
  catch(ArrayIndexOutOfBoundsException e) {
   System.out.println("Attenzione: fornire il nome dell'host come parametro!");
  }
  catch(UnknownHostException e) {
   System.out.println("Attenzione: hostname sconosciuto!");
  }
 }
}

Qui trovate il file jar del programma.

giovedì 7 ottobre 2010

Applicazioni client/server con Java

La comunicazione e lo scambio dell'informazione sono da sempre alla base di Internet. Anche Java, nato inizialmente con lo stesso obiettivo, supporta la programmazione di rete. Il package java.net raccoglie le interfacce, le classi e le possibili eccezioni utili proprio alla programmazione per questo tipo di applicazioni, spesse volte dette anche applicazioni client/server.

Queste applicazioni osservano un noto modello di programmazione che è appunto il modello client/server. Durante lo scambio dell'informazione il suddetto modello assegna ai partecipanti dei ruoli ben precisi: il client è l'applicazione che, interfacciandosi attraverso la rete al server, richiede un preciso servizio; il server è invece quell'applicazione che soddisfa, attraverso la rete, la richiesta del client.

La comunicazione fra client e server si svolge inevitabilmente attraverso la rete, grazie ai protocolli di rete. A livello di trasporto essa può allora avvenire attraverso due dei principali protocolli di trasporto: TCP o UDP. Il protocollo di rete TCP realizza un canale logico per la connessione affidabile e orientato alla connessione, si impegna a fare del suo meglio per garantire l'arrivo dei pacchetti e l'ordine con cui essi vengono spediti. Il protocollo di rete UDP è assai più sbrigativo, non prevede una connessione e non garantisce ne l'arrivo ne l'ordine dei pacchetti. UDP viene usato, ad esempio, per lo streaming dei flusi audio e/o video. La scelta del protocollo per il trasporto può quindi variare in base al tipo di applicazione che abbiamo in mente.

Tale scelta va fatta ancora prima di mettersi a scrivere il codice dell'applicazione. Java implementa nel package jav.net diverse classi, alcune di queste sono orientate al protocollo TCP, altre invece seguono il protocollo UDP. La comunicazione fra client e server avviene attraverso le socket (presa o spina). Ogni applicazione si può dotare di una socket, la finestra sulla rete.

La classe Socket implementa uno dei due lati della connessione di rete (end-point) ed è orientata al protocollo TCP, tipicamente quella di un'applicazione client. La classe ServerSocket, invece, realizza la socket per l'applicazione server. Tale socket viene usata dai server per rimanere in ascolto e accettare le richieste di connessione dei client. La classe DatagramSocket realizza l'astrazione necessaria all'invio e alla ricezione dei dati attraverso il protocollo UDP.

In questo articolo mi occuperò esclusivamente delle classi Socket e ServerSocket. E' evidente che l'applicazione server deve trovarsi già in esecuzione prima di ricevere richieste dai client che altrimenti si perdono nella rete. Osserveremo allora prima la struttura di un'applicazione server. L'istanza di una socket per il server avviene attraverso il costruttore della classe ServerSocket:

public ServerSocket(int port) throws IOException

dove port rappresenta il numero di porta usato dal server, presso il proprio indirizzo di rete, per l'ascolto. Alcuni numeri di porta sono assegnati di default e spettano a vari servizi di rete (sono cioè usati da altri protocolli per applicazioni). Se l'istanza di una ServerSocket non riesce vi consiglio di cambiare il numero della porta usato nel costruttore, potrebbe infatti essere già impegnato! Il costruttore va poi eseguito all'interno di un blocco try/catch per intercettare proprio l'errore che vi descrivevo poco fa. Esistono altri tipi di cotruttori, date come sempre uno sguardo alla documentazione della classe:

server=new ServerSocket(10000);

Se l'istanza della socket riesce possiamo allora collocare la stessa in uno stato di attesa. Il metodo accept() blocca l'esecuzione del programma e lo pone in attesa di richieste di connessione. Il metodo si sblocca alla prima richiesta del client, ritornando l'oggetto socket usato dallo stesso! Da questo oggetto il server, se vuole, può estrarre l'indirizzo IP con il metodo getInetAddress():

client=server.accept();

Affinchè il server possa realmente mandare al client dei byte (testo o informazioni varie) deve necessariamente estrarre dalla socket del client gli stream per l'input:

input=new BufferedReader(new InputStreamReader(client.getInputStream()));
e per l'output:
output=new PrintWriter(client.getOutputStream(),true);
I metodi getInputStream() e getOutputStream() ritornano rispettivamente InputStream e OutputStream. La comunicazione adesso è davvero semplice, se il server intende leggere cosa ha scritto il client non deve fare altro che usare lo stream input. Nell'esempio ho appositamente convogliato lo stream di input in un oggetto BufferedReader che semplifica la lettura con il metodo readLine() (che ritorna una stringa). Per leggere, ad esempio, si potrebbe avere questa istruzione:
String clientMessage=input.readLine();
Se il server intende scrivere qualcosa al client userà allora lo stream di output:
output.println("Ciao!");
Prestate attenzione alla chiusura dell'applicazione e liberate sempre le risorse impegnate. Chiudete prima gli stream di input e di output, quindi la socket del client e infine quella del server:
input.close();
output.close();
client.close();
server.close();
L'applicazione client opera allo stesso modo di quella server, la prima operazione da fare è la connessione al server (che sarà in attesa, bloccato sul metodo accept()). Il client può allora usare la classe Socket in questo modo:
client=new Socket(ip,10000);
dove ip è la stringa con l'indirizzo ip del server e 10000 il numero di porta del servizio. Se la connessione riesce senza eccezzioni (anche questa operazione, così come le altre, va sempre innescata all'interno di un blocco try/catch) il client può allora ricavare dalla socket (ora connessa al client) gli stream per l'input e per l'output. Per l'input, come visto prima, ad esempio:
input=new BufferedReader(new InputStreamReader(client.getInputStream()));
per l'output, analogamente:
output=new PrintWriter(client.getOutputStream(),true);
Così come fa il server, anche il client può usare gli strem per scrivere al server e per leggerne i messaggi inviati dallo stesso, il protocollo TCP realizza infatti un canale bidirezionale. Le applicazioni client e server dovranno in ogni modo accordarsi su quando e come scriversi, dovranno cioè osservare un protocollo per lo scambio dei messaggi (un compito è lasciato alla fantasia del programmatore). Non dimenticate di chiudere nell'ordine esatto le risorse impegnate anche nell'applicazione client.
Qui potete scaricare l'archivio jar di un'applicazione server, a questo indirizzo invece trovate il client. Lanciate prima il server (con il comando "java -jar ServerSocket.jar", nella cartella che ospita il file scaricato) e successivamente il client (con "java -jar ClientSocket.jar", sempre nella cartella che ospita il file scaricato) se volete utilizzare l'applicazione nella vostra rete locale (con indirizzo ip di loopback 127.0.0.1). Altrimenti eseguite le due applicazioni su due computer con indirizzo ip differente. L'esecuzione del client inizia proprio con la richiesta dell'indirizzo ip del server (la porta usata è di default la 10000), se non conoscete il vostro indirizzo ip potete ricavarlo sfruttandore ad esempio i tanti servizi di rete, ad esempio qui. Qui potete vedere la parte server in esecuzione (su un computer con sistema operativo Linux, il mio Aspire One!):

Qui invece la parte client (in esecuzione su un PC con sistema operativo Windows XP):

L'applicazione permette lo scambio di messaggi fra client e server, dopo aver effettuato la connessione al server il client inizia a scrivere al server (che si mette quindi in attesa di un messaggio). Subito dopo è il client che si mette in attesa di un messaggio di risposta da parte del server. La comunicazione si svolge quindi su turni. Va inoltre osservata la capacità del server di rispondere a una sola richiesta di connessionne del client. Per implementare un server che accetta più connessioni quest'ultimo deve necessariamente usare una socket per l'ascolto e generare ad ogni connessione una socket per il client.

giovedì 30 settembre 2010

DBConnector

DBConnector è un client per database, basato sulle API JDB, viste in un precedente articolo. Lo sviluppo del client è stato portato avanti attraverso NetBeans IDE, che favorisce e semplifica di molto il disegno delle GUI! Per la progettazione ho cercato invece di seguire il noto schema MVC (model-view-controller). Potete scaricare a questo indirizzo l'intera cartella contenente i file di progetto, oltre al file jar che esegue l'applicazione stessa. Il file in questione è un archivio tar che va dunque estratto con il comando: tar zxvf DBConnector.tar.gz, da eseguire all'interno della cartella che ospita il file appena scaricato. L'estrazione produce la cartella DBConnector, al suo interno trovate il codice sorgente (cartella /src), la documentazione generata con il comando javadoc (cartella /dist/javadoc) e il file jar per eseguire l'applicazione (nella cartella dist). Quest'ultima avviene digitando il comando: java -jar DBConnector.jar.

Il programma si compone di cinque classi, una dedicata all'avvio dell'applicazione (DBConnectorApp). Una classe descrive l'interfaccia grafica (DBGui), altre due si preoccupano della connessione (DBConnector) e dell'invio dei comandi SQL al database (DBQuest). L'ultima classe è quindi dedicata alla gestione degli eventi (DBController), raccoglie i comandi SQL e li fa eseguire all'oggetto incaricato, quindi aggiorna l'interfaccia grafica riportando errori e risultati. Nell'esempio viene mostrata l'applicazione in esecuzione, il driver usato nell'esempio permette la connessione a un database MySQL che ho progettato in precedenza per un'applicazione web.

venerdì 24 settembre 2010

Una classe davvero utile: la classe Properties

Ho già utilizzato in passato questa classe per aggiugere propietà all'oggetto Session dell'API JavaMail e solo adesso mi rendo conto della sua molteplice utilità. In questo articolo vi spiego un utilizzo davvero interessante di questa classe!

La classe Properties, package java.util.Properties, è in grado di rappresentare un set di coppie chiave-valore in maniera persistente, salvandole e caricandole da uno stream. Le coppie di valori scritte e lette dal file vanno interpretate come stringhe, ogni eventuale valore numerico associato a una chiave va quindi ripristinato con un parsing.

Il costruttore della classe (Properties()) genera una lista vuota, da personalizzare quindi in seguito. Esiste poi un secondo costruttore che permette sia l'istanza dell'oggetto e sia l'inizializzazione dello stesso con una lista di proprietà (Properties(Properties defaults)).

Le coppie di valori possono essere assegnate con:

Object setProperty(String chiave, String valore)
che ritorna il precedente valore aggiunto alla lista, se presente, altrimenti null. La ricerca dei valori avviene invece con:
String getProperty(String chiave)
che cerca la chiave all'interno della lista restituendone il valore precedentemente assegnato (altrimenti null se la chiave non viene trovata). Direi che fin qui nulla di nuovo o di eclatante. All'inizio dell'articolo parlavo però di persistenza e in effetti la classe è in grado di garantirla! Attraverso due metodi possiamo scrivere su file e leggere da file le proprietà impostate in una precedente sessione dell'oggetto. Con il metodo:
void store(OutputStream out, String header) throws IOException
possiamo scrivere le coppie assegnate all'oggetto sullo stream out. Se header è diverso da null una nuova riga di commento verrà aggiunta in testa al file, per spiegare ad esempio l'utilità del file. La scrittura su file provoca la stampa all'interno dello stesso, subito sotto l'header, della data di creazione del file. Con il metodo:
void load(InputStream in) throws IOException

possiamo, invece, leggere la lista di valori dallo stream in. Attenzione, gli ultimi due metodi, poiché lavorano su stream, possono lanciare un IOException (che va in qualche modo gestita, oppure propagata).

L'utilità di questa classe è davvero interessante. Pensate, ad esempio, a tutte quelle volte che dovete salvare su file le opzioni che configurano la vostra applicazione. E di come intercettare queste opzioni, leggendole dal file, ne vogliamo parlare? Insomma, chi deve preoccuparsi di questo compito? Il programmatore, di sicuro. Ecco allora che Properties viene in nostro soccorso, usiamo questa classe per liberarci in modo elegante da un compito!

Dopo aver letto la documentazione sulla classe non ho perso tempo e ho subito scritto un package di supporto per la gestione della classe in questione. Ho dotato la classe del costruttore base, caratterizzando gli oggetti in base al nome del file da usare (più altri campi). I metodi per aggiungere e cercare coppie di valori sono stati sistemati all'interno di istruzioni di controllo per evitare l'assegnazione di valori già esistenti o nulli. Quindi ho aggiunto due metodi per leggere e salvare su file (load() e save()), inserendo i metodi della classe (load() e store()) all'interno di istruzioni utili alla gestione degli stream di input e di output. Ogni metodo (ad eccezione di quello assegnato alla lettura del valore di una chiave, che ritorna una stringa) ritona un valore logico che riflette l'esito dell'operazione. Ho poi dotato la classe di un buffer per gli errori, da consultare non appena un'operazione non va a buon fine. Trovate la classe da me scritta a questo indirizzo. All'interno della classe trovate un metodo main utile al testing della classe, ecco ad esempio cosa genera:

giovedì 23 settembre 2010

JDBC, esempio

State scrivendo un'applicazione che usa un driver JDBC? Ecco i passi da seguire:

  1. Caricare il driver, specifico per il database in uso;
  2. Creare, mediante url, una connessione al database da usare;
  3. Scrivere e inviare il comando SQL alla connessione;
  4. Scorrere il risultato alla ricerca dei valori utili;
  5. Chiudere la connessione al database;

Per caricare il driver occorre specificare al metodo Class.forName(driver).newInstance() una stringa che identifica il nome del driver.  Ad esempio, la stringa che identifica il driver JDBC per MySQL è com.mysql.jdbc.Driver. Non dimenticate di invocare newIstance(), dopo aver cercato il driver, per creare una nuova istanza del driver! Altre stringhe di driver sono: sun.jdbc.odbc.JdbcOdbcDriver per un bridge (ponte) JDBC-ODBC, oracle.jdbc.driver.OracleDriver per Oracle e org.postgresql.Driver per PostgreSQL.

Anche l'url da usare per la connessione va cercato nella dicumentazione. MySQL e altri database sono sviluppati sul modello client/server. Il server che ospita dati, sia locale che remoto, è quindi identificato da un indirizzo di rete. Per MySQL questo url ha la seguente struttura: jdbc:mysql://indirizzo_host:porta/nomeDataBase. Se il server MySQL è dunque locale occorre indicare come host l'indirizzo localhost oppure il noto ip di loopback 127.0.0.1. Il numero di porta usato di default dal server MySQL è 3306 (se tale numero non è stato modificato dopo l'installazione del server). Con DriverManager.getConnection(url,user,password) viene tentata una connessione al database (specificato nell'url), se il database richiesto non è pubblico occorre allora indicare il nome utente e la password usate in fase di login (come quando usiamo il client MySQL dal terminale).

Terminata la fase di setup per la connessione si possono iniziare a utilizzare i metodi visti in precedenza per l'invio dei comandi SQL. Terminata l'elaborazione chiudete la connessione con il metodo connessione.close().


import java.sql.*;



public class Main {
 public static void main(String[] args) {
  String driver="com.mysql.jdbc.Driver";
  String user="root";
  String password="root";
  String url="jdbc:mysql://localhost:3306/php_training";
  String query="SELECT * FROM contatti WHERE nome='Luca'";
  try {
   System.out.println("Caricamento del driver ("+driver+")...");
   Class.forName(driver).newInstance();
   Connection connessione=DriverManager.getConnection(url,user,password);
   if(!connessione.isClosed()) System.out.println("Connessione...");
   else {
    System.out.println("Connessione non riuscita!");
    System.exit(1);
   }
   Statement stmt=connessione.createStatement();
   System.out.println("Esecuzione della query: "+query);
   System.out.println("Risultato:");
   ResultSet srs = stmt.executeQuery(query);
   while (srs.next()) {
    System.out.println(srs.getString("nome")+" "+srs.getString("cognome"));
   }
   connessione.close();
  }
  catch(Exception errore) {
   System.err.println("Si è verificata un'eccezione: " + errore.getMessage());
  }
 }
}

Ecco cosa riporta la finestra di output in NetBeans IDE (ho usato il database php_training):

mercoledì 22 settembre 2010

JDBC: Java DataBase Connectivity, introduzione

Java DataBase Connectivity, da ora in avanti JDBC, è una libreria Java che implementa i driver per l'accesso e la gestione dei database nelle applicazioni Java.vEsistono diverse implementazioni dei driver JDBC, che possiamo classificare così come segue:

  • Driver di tipo 1: sono driver che implementano le API JDBC facendo corrispondere le chiamate ad altre API per l'accesso ai dati. Effettuano cioè un ponte (bridge) verso altre tipologie di driver, traducendo quindi ogni chiamata in chiamate dell'altro driver. Il driver JDBC-ODBC è un esempio di driver di tipo 1;
  • Driver di tipo 2: sono driver che, essendo scritti parzialmente in Java e parzialmente con il codice nativo del database, traducono ogni chiamata in una chiamata interna al database;
  • Driver di tipo 3: sono driver scritti interamente in Java e comunicano con un server middleware usando un protocollo indipendente dal database. Il server comunica, poi, le richieste del client all'origine dei dati;
  • Driver di tipo 4: sono driver scritti interamente in Java che si connettono direttamente all'origine dei dati, senza server intermedio;

I driver di tipo 1 e 2, poiché scritti con codice nativo, possono limitare l'applicazione Java in termini di portabilità. L'impiego di questi driver implica di conseguenza l'installazione delle librerie di supporto usate nel codice nativo del driver. Ad esempio, il driver di tipo 1 JDBC-ODBC dipende anche dal driver ODBC. Il driver di tipo 2 è da preferire a quello di tipo di 1 (ogni chiamata è tradotta in una chiamata interna al database). In ogni caso, se il produttore di database ne ha previsto uno, è preferibile usare un driver scritto interamente in Java.

I driver di tipo 3 e 4 hanno in genere prestazioni migliori, per i motivi scritti sopra. La portabilità dell'applicazione è quindi maggiore (non dovendo installare altro software). I driver di tipo 4 usano un protocollo nativo per scambiare i dati con il database, se possibile si consiglia di usare sempre driver di tipo 4. I driver di tipo 3 usano invece un protocollo di rete per dialogare con un server che interagisce con il database per conto del client. E' proprio quest'ulteriore passaggio a degradare le prestazioni del driver!

L'uso di JDBC prevede in primo luogo la creazione di una connessione verso il database, che può avvenire seguendo uno dei due meccanismi di seguito elencati:

  • DriverManager: carica un driver specifico utilizzando un indirizzo registrato che sintetizza la connessione al database;
  • DataSource: da preferire al precedente meccanismo poiché offre maggiori informazioni sui dati;

Per stabilire una connession sono necessari due passaggi: caricare un driver ed eseguire la connessione (usando il driver). Il caricamento di un driver si risolve in una riga di codice:

Class.forName("NOME_DEL_DRIVER");

Il nome del driver viene indicato nella documentazione del driver. La chiamata a Class.forName realizza automaticamente un'istanza  del driver registrandola con DriverManager. Solo dopo aver caricato un driver si può tentare di aprire una connessione verso il database desiderato. Vediamo come funzionano i due meccanismi.

La classe DriverManager fa parte dell'interfaccia Driver e realizza pertanto i metodi richiesti dalla stessa. Il metodo più usato è getConnection che ritorna un oggetto Connection. Esistono più implementazioni di questo metodo, che ovviamente  differiscono per la firma e quindi dai parametri richiesti dal metodo. La forma base è la seguente:

DriverManager.getConnection(String url);
dove url è l'url al database (nella forma jdbc:subprotocol:subname). Altre varianti di getConnection permettono poi di specificare proprietà, come:
DriverManager.getConnection(String url,Properties info);
dove info rappresenta un set di proprietà per la connessione. In molti casi va specificata la password e il nome utente assegnati allo stesso sul database. Il metodo:
DriverManager.getConnection(String url,String user,String password);
permette di fare proprio questo! In caso di errore tutti e tre i metodi appena visti lanciano un'eccezione di tipo SQLException. Altri metodi utili di DriverManager sono setLoginTimeout(int x) che attende x secondi prima di eseguire il login, setLogWriter(PrintWriter out) che imposta out come standard output per gli eventi di logging e println(String messaggio) che stampa su out un evento utile all'operazione di logging.
Con DataSource anziché fornire informazioni specifiche sul driver è sufficiente fornire un nome logico che punta alla sorgente di dati. Ecco un esempio:
InitialContext ic=new InitialContext();
DataSource ds=ic.lookup("java:comp/env/jdbc/myDB");
Connection con=ds.getConnection();
DataSource ds=(DataSource)org.apache.derby.jdbc.ClientDataSource();
ds.setPort(1527);
ds.setHost("localhost");
ds.setUser("APP");
ds.setPassword("APP");
Connection con=ds.getConnection();

L'interfaccia ResultSet fornisce i metodi per recuperare e manipolare i risultati generati dalle query eseguite. Gli oggetti ResultSet (una struttura dati in grado di ospitare i dati generati da una query) possono avere diverse funzionalità e caratteristiche. Tali caratteristiche possono stabilire il tipo di dati, la concorrenza e i cursori per lo scorrimento dei risultati. Il tipo di un oggetto ResultSet stabilisce i modi in cui il cursore può essere manipolato e le modifiche concorrenti che possono essere apportate ai dati. Esistono tre tipologie di ResultSet:

  • TYPE_FORWARD_ONLY: non è possibile scorrere a piacimento il risultato ottenuto (eseguendo una query), il cursore si sposta sempre e solo in avanti, dalla prima all'ultima riga;
  • TYPE_SCROLL_INSENSITIVE: è possibile scorrere il risultato in entrambe le direzioni oppure usare scostamenti relativi per l'accesso a una riga del risultato;
  • TYPE_SCROLL_SENSITIVE: è possibile scorrere il risultato in entrambe le direzioni oppure usare scostamenti relativi per l'accesso a una riga del risultato. La differenza con l'altro tipo di cursore risiede nella possibilità di rendere visibili i cambiamenti nei dati ad una applicazione;
JDBC restituisce i risultati in un oggetto ResultSet, quindi la nostra applicazione deve necessariamente dichiarare un'istanza di tale classe per raccogliere i risultati. Tale oggetto viene restituito ad esempio quando eseguiamo una chiamata a executeQuery o getResultSet. I metodi appena citati appartengono alla classe Statement, che permette di definire oggetti per l'esecuzione di codice SQL (il cui risultato è quindi ospitato in un oggetto ResultSet).
Statement stmt=con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);
ResultSet srs = stmt.executeQuery("SELECT * FROM contatti");
Il primo argomento è una delle tre costanti definite in ResultSet per indicare il tipo di cursore: TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE e TYPE_SCROLL_SENSITIVE. Il secondo argomento specifica (con due costanti definite sempre in ResultSet) se un set di risultati è di sola lettura oppure aggiornabile: CONCUR_READ_ONLY e CONCUR_UPDATABLE. Un altro metodo della classe Statement è int executeUpdate(String sql) che esegue codice SQL che non produce oggetti ResultSet (come operazioni di INSERT, UPDATE o DELETE). Ricorda che se specifichi il tipo di cursore devi sempre precisare, poi, se questo è di sola lettura oppure aggiornabile. L'istanza di un semplice oggetto Statement, con createStatement, invece, dichiarerà di default un  oggetto ResultSet di tipo TYPE_FORWARD_ONLY e accessibile nella modalità CONCUR_READ_ONLY.
Avete notato la variabile srs nel codice precedente? Si tratta di un oggetto ResultSet che racchiude al suo interno il risultato della query! Tale oggetto può essere manipolato attraverso svariati metodi. L'istanza di tale oggetto posiziona di default un puntatore per lo scorrimento della tabella (il risultato di un query è in ogni caso una tabella) sulla prima riga del risultato. Particolarmente utili sono i metodi:
  • next(): sposta il cursore sulla riga successiva a quella puntata, se esiste;
  • previous(): sposta il cursore sulla riga precedente a quella puntata, se esiste;
  • first(): sposta il cursore sulla prima riga dell'oggetto ResultSet;
  • last: sposta il cursore sull'ultima riga dell'oggetto ResultSet;
  • relative(int n): sposta il cursore di n righe (in avanti o dietro, dipende dal segno di n) rispetto alla riga puntata;
  • absolute(int n): sposta il cursore di n righe a partire dalla prima riga dell'oggetto ResultSet;
Tutti i metodi detti sopra ritornano un valore booleano, true in caso di successo, altrimenti false. Altri metodi che solitamente utilizzo sono: deleteRow() che cancella la riga puntata, int findColumn(String ColumnName) che ritorna l'indice per accedere alla riga che contiene la colonna ColumnName e Array getArray(String ColumnName) che ritorna un array composto da tutte le occorrenze della colonna ColumnName. Come posso leggere i campi dati di una riga?

Finché il puntatore dell'oggetto ResultSet insiste su una riga dell'oggetto è possibile accedere ai campi dati attraverso uno dei tanti metodi getXXX (fornendo il nome della colonna come parametro). Esistono metodi per estrarre valori numerici (come int getInt(String ColumnName), float getFloat(String ColumnName), double getDouble(String ColumnName) e long getLong(String ColumnName)) e stringhe (come String getString(String ColumnName)). Tutto dipende dal tipo di informazione da estrarre (esistono altri metodi per altri tipi di dati, vi consiglio di dare uno sguardo alla documentazione in linea). Quasi tutti i metodi per la ricerca dei campi dati hanno anche la versione basata sulla ricerca numerica della colonna (anziché usare il nome della colonna viene usato un numero che fa riferimneto all'ordine numerico della colonna nella tabella).

while(srs.next()){
 String username=srs.getString("username");
 System.out.println("Username: "+username);
}
Se usiamo l'ultima versione di NetBeans IDE possiamo sviluppare fin da subito applicazioni JDBC, in caso contrario dobbiamo necessariamente scaricare il driver e configurarlo all'interno del nostro classpath.

venerdì 9 luglio 2010

JavaSendMail con le API JavaMail

Dopo aver descritto le API JavaMail in questo articolo ho realizzato un semplice client per l'invio di e-mail, JavaSendMail.


Avendo abilitato l'opzione mail.debug ed effettuato il redirect dell'output per la console nella variabile infoArea (di tipo JTextArea) è possibile vedere nella parte inferiore della finestra (quella in grigio) i comandi scambiati fra l'API e il server SMTP! Il riquadro rosso nella stessa figura testimonia l'invio del messaggio. Così come questi screenshot che testimoniano la ricezione del messaggio:



JavaSendMail può essere scaricato a questo indirizzo. Il file zip contiene la cartella dist generata da NetBeans IDE per la distribuzione del programma. All'interno trovate, oltre al file jar dell'applicazione, anche le librerie usate (activation.jar e mail.jar). Nella cartella src trovate invece il codice sorgente dell'applicazione. Come sempre, copiate, modificate e distribuite!

giovedì 8 luglio 2010

API JavaMail

JavaMail è un API che con l'aiuto di JavaBeans Activation Framework (JAF) permette l'accesso all'ormai famoso servizio per la posta elettronica. Si tratta di un package opzionale che, sfruttando i servizi di rete offerti dai livelli sottostanti, consente di inviare e ricevere l'email. L'obiettivo principale di JavaMail è la realizzazione di un accesso al servizio indipendente dai protocolli sottostanti, realizza cioè un'interfaccia al servizio.
Nella prima parte di questo articolo ci occuperemo di richiamare le funzioni dei protocolli di rete usati per il servizio della posta elettronica, quindi passeremo a una breve presentazione delle classi che compongono il package. Al termine di questa prima parte sarà quindi possibile usare il package per scrivere semplici programmi.
Nella seconda parte vedremo un uso più avanzato della libreria.
E' giunto il momento di rivedere i principali protocolli di rete usati da JavaMail: SMTP, POP ed IMAP. Ciò non può che essere di aiuto per la comprensione di tutto quello che faremo a breve con l'API. E' opportuno ricordare che ogni protocollo di rete è stato ideato per precisi scopi ed implementa di conseguenza le dovute funzionalità. Pertanto, se una funzione non viene in alcun modo realizzata presso i protocolli di rete è inutile chiedere a JavaMail di fare il miracolo!

SMTP
Il protocollo SMTP (Simple Mail Transfer Protocol) definito nell'RFC 821 si preoccupa della consegna, quindi dell'invio, dei messaggi. Quando inviamo un messaggio di posta elettronica il nostro client dialoga con il server SMTP del provider.

POP
Il protocollo POP (Post Office Protocol) definito nell'RFC 1939 permette la lettura dei propri messaggi di posta elettronica. Se vogliamo prelevare la nostra posta elettronica dobbiamo necessariamente dialogare con un server POP (attualmente giunto alla versione POP3).

IMAP
Il protocollo IMAP (Internet Message Access Protocol) definito nell'RFC 2060 permette la ricezione della posta elettronica. IMAP è più evoluto di POP3, anche se aggiunge più lavoro al server!

MIME
MIME non è un vero e proprio protocollo di rete, l'acronimo sta per Multipurpose Internet Mail Extensions. Viene usato da JavaMail e non potevo non menzionarlo! Esso definisce la struttura dei messaggi, i contenuti, gli allegati, etc...

Installazione di JavaMail
Esistono due versioni di JavaMail. Una di queste è parte integrante della piattaforma J2EE (assicuratevi di avere il file j2ee.jar), JavaMail risulta quindi già installato. Se invece state usando una versione di JDK che non dispone del supporto alle applicazioni enterprise (quelle di J2EE) è possibile scaricare l'API JavaMail a questo indirizzo (per la versione 1.4.3).
Come già detto all'inizio per usare JavaMail vi occorre anche JAF, che trovate qui.
Per l'installazione vanno estratti entrambi i file compressi, le cartelle generate dall'operazione di estrazione vanno poi spostate nella cartella /CARTELLA_INSTALLAZIONE_JDK/jre/lib/ext/. Se volete potete anche modificare la variabile d'ambiente per Java e aggiungere, quindi, le nuove cartelle. Nella cartella demo di JavaMail è possibile trovare diversi file di esempio.

Le classi dell'API
Prima ancora di vedere un programma completo ho preferito descrivere sommariamente le principali classi che compongono l'API, queste sono: Session, Message, Address, Authenticator, Transport, Store e Folder. Tutte nel package javax.mail.

Session
La classe Session descrive una sessione utile all'invio o ricezione di messaggi di posta elettronica. La nostra applicazione che fa uso di JavaMail dovrà sempre istanziare un oggetto Session e caratterizzarne le proprietà. Le informazioni che caratterizzano un oggetto Session vengono passate attraverso l'oggetto Properties, della classe java.util. Poiché i costruttori della classe sono privati è possibile ottenere un oggetto Session mediante il metodo getDefaultInstance():

Properties props=new Properties();
Session session=Session.getDefaultInstance(props, null);

Il valore null passato al metodo va a un eventuale oggetto Authenticator che per adesso non usiamo. Un utile metodo di questa classe è il metodo setDebug(boolean debug) che permette di vedere o meno la sequenza di comandi scambiata con il server. Per caratterizzare una sessione occorre prima definire i valori di nuove proprietà, ad esempio con:

String host = "smtp.tiscali.it";
props.put("mail.smtp.host",host);

impostiamo il server SMTP da contattare. Il metodo setProperty(String key, String value) imposta il valore value alla chiave key. Ecco un elenco di alcuni dei dei possibili valori di key:
  • mail.debug: può essere true oppure false (valore di default) e abilita la modalità di debugging;
  • mail.user: una stringa con il nome utente da usare quando l'API contatta il server per la posta elettronica;
  • mail.from: una stringa con l'indirizzo dell'utente (che può essere aggiunto al messaggio con il metodo setFrom());
  • mail.smtp.host: una stringa con l'indirizzo del server SMTP;
Qui trovate una lista completa di valori. Se impostiamo una o più proprietà va poi chiamato il metodo getInstance(Property props) per costruire un oggetto Session con quelle caratteristiche!

Message
Con la classe Message descriviamo il messaggio da inviare (contiene il testo dell'email). Siccome Message è una classe astratta nei programmi si avrà a che fare con una delle sue sottoclassi. Una di queste è la classe MimeMessage, definita in javax.mail.internet. Come avete ben capito la classe MimeMessage rispetta lo standard descritto dal protocollo MIME. Per creare un oggetto Message, a partire da un oggetto Session, si ha queste sequenza di istruzioni:

MimeMessage message=new MimeMessage(session);
message.setContent("Hello world!","text/plain");

dove con il metodo setContent(Object o, String type) ho impostato il contenuto e l'attributo MIME. E' possibile indicare anche solo il contenuto in questo modo:

MimeMessage message=new MimeMessage(session);
message.setContent("Hello world!");

Per specificare il campo subject dell'email esiste il metodo setSubject(String subject):

message.setSubject("An \"Hello world!\" e-mail!");

Altri metodi della classe MimeMessage si trovano in questa pagina.

Address
Con la classe Address possiamo costruire oggetti capaci di descrivere un indirizzo di posta elettronica. Anche la classe Address, così come Message, è una classe astratta. Gli oggetti Address vengono allora costruiti attraverso una delle sue sottoclassi, come la classe InternetAddress in javax.mail.internet.

Address fromAddress=new InternetAddres("writer@domain.site");
Address toAddress=new InternetAddres("user@domain.site");

Se vogliamo far apparire un nome al posto dell'indirizzo (che va sempre indicato) possiamo allora usare una variante del metodo visto prima:

Address fromAddress=new InternetAddres("writer@domain.site","Luca");

L'aggiunta di questi oggetti al messaggio avviene con i metodi setFrom(), che imposta l'header From del messaggio, e addRecipients(Message.RecipientType type, Address[] addresses), che imposta invece l'header To. Quest'ultimo metodo permette l'invio multiplo del messaggio a più utenti. Possiamo costruire un array di indirizzi e passare la struttura al metodo! Possibili valori di type sono: Message.RecipientType.TO, Message.RecipientTypeCC (indirizzo a cui inviare una copia carbone del messaggio) e Message.RecipientType.BCC (indirizzo nascosto a cui inviare una copia del messaggio). Ecco un esempio:

Address toAddress=new InternetAddress("mrRed@color.rgb");
Address ccAddress=new InternetAddress("msRed@color.rgb");
message.addRecipient(Message.RecipientType.TO, toAddress);
message.addRecipient(Message.RecipientTypeCC, ccAddress);

Attenzione, l'API non prevede alcun meccanismo per convalidare un indirizzo di posta elettronica! Si potrebbe costruire qualcosa usando le espressioni regolari.

Authenticator
La classe Authenticator permette l'accesso a servizi protetti da password e che richiedono quindi una forma di autenticazione. Attenzione, la classe Authenticator di java.net è diversa da quella in javax.mail (pur avendo lo stesso nome, proprio per questo si trovano in package differenti!). Se un servizio di posta elettronica richiede il riconoscimento dell'utente è importante, allora, implementare il metodo getPasswordAuthentication(), che crea un oggetto PasswordAuthentication contenente il nome dell'utente e la sua password per accedere al servizio.

Authenticator authenticator = new Authenticator();
Session sessione = new Session.getdefaultInstance(properties,authenticator);

Trovate qui altre informazioni.

Transport
La classe Transport rende possibile l'invio del messaggio fin qui costruito. Il metodo che permette di inviare il messaggio è:

Transport.sendMessage(messagge);

Viene usato di default il protocollo SMTP. Attenzione, il metodo lancia un eccezione di tipo MessagingException. Se voglio invece caratterizzare l'invio del messaggio disponiamo poi di altri metodi.

Store e Folder
La classe Store si preoccupa della ricezione dei messaggi di posta elettronica, realizzando la connessione al server. I messaggi vengono poi organizzati all'interno di cartelle, modellate con oggetti della classe Folder. Approfondiremo ulteriormente queste due classi in seguito.

Esempio: inviare un e-mail
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;

public class Invio {
   public static void main(String[] args) {
      String to="user@domain.site";
      String from="writer@domain.site";
      String host="smtp.server.site";
      Properties props = new Properties();
      props.put("mail.smtp.host",host);
      props.put("mail.debug","true");
      Session session=Session.getInstance(props);
      try {
         Message msg=new MimeMessage(session);
         msg.setFrom(new InternetAddress(from));
         InternetAddress[] address ={new InternetAddress(to)};
         msg.setRecipients(Message.RecipientType.TO, address);
         msg.setSubject("An \"Hello World e-mail\"!");
         msg.setSentDate(new Date());
         msg.setText("Hello world!");
         Transport.send(msg);
      }
      catch (MessagingException mex) {
         mex.printStackTrace();
      }
   }
}

giovedì 1 luglio 2010

Java: ColorRes


Si tratta di un piccolo ma utile programma per la lettura dei resistori. E' sufficiente indicare i colori delle bande presenti sul componente per ricavarne immediatamente il valore della resistenza. Il programma è stato realizzato con NetBeans IDE. Vi segnalo il link per il download.

lunedì 28 giugno 2010

Aggiungere la piattaforma JDK a NetBens IDE

Se avete seguito l'installazione della piattaforma JDK è giunto adesso il momento di aggiungerla a NetBeans IDE. Vi ricordo, infatti, che se siete su Ubuntu il sistema operativo ha e usa di default open-jdk (la versione open-source di JDK, creata per risolvere alcuni problemi sulle licenze d'uso). Avviamo NetBeans IDE, da riga di comando scrivendo netbeans nel terminale oppure dal menu delle applicazioni di Xubuntu (l'installazione di NetBeans IDE crea un collegamento all'applicazione nel menu "Sviluppo"). Cliccate sul menu "Tool", e selezionate la voce "Java Plattform":


Il riquadro rosso fa notare la presenza di open-jdk, usato di default da NetBeans IDE. Cliccate sul bottone "Add Plattform" e navigate nel filesystem fino a raggiungere la cartella in cui è stato installato JDK:


Per andare avanti premete sul bottone "Next". La prossima schermata vi chiede di dare un nome alla piattaforma:


Se avete scaricato anche la documentazione per la piattaforma è il momento di indicarne il path nell'area di input preceduta dall'etichetta "Plattform Javadoc". Cliccate su "Finish" per concludere l'aggiunta, la piattaforma JDK è adesso nota a NetBeans IDE che tuttavia continua a usare open-jdk di default.


Nelle proprietà di ogni progetto è adesso possibile indicare la piattaforma da usare. Se volete usare di default JDK editate il file che si trova nella cartella /etc/netbeans.conf. Dovete modificare la riga:

netbeans_jdkhome="/usr/lib/jvm/java-6-openjdk"

in maniera tale da far puntare la variabile netbeans_jdkhome alla cartella di JDK. Attenzione, questa operazione trasferisce la piattaforma di default da open-jdk a JDK (eliminando open-jdk dalla lista delle piattaforme disponibili). Se volete continuare a usare in seguito anche open-jdk vi consiglio allora di segnare il path che punta alla cartella di open-jdk e di aggiungerla in un secondo momento come nuova piattaforma. Ripetendo i passaggi visti sopra. Se sviluppate applicazioni Java è preferibile farlo con JDK!

Installazione di JDK su Linux

Quella che segue è la procedura suggerita da SUN Microsystems sulle pagine di supporto all'installazione del noto ambiente di sviluppo: Java Development Kit (JDK). L'ambiente viene distribuito attraverso due tipologie di file: un pacchetto rpm per le distribuzioni Linux che sanno già come trattare questo formato (come Fedora, SuSe e Red Hat) e un file binario più generico per tutte le altre distribuzioni Linux.
In questo articolo ci concentreremo proprio su quest'ultima tipologia di file. Sun Microsystems distribuisce i tipi di file detti sopra con dei nomi strutturati in un preciso modo. Nel caso del file binario, il nome del file da scaricare ha la seguente struttura: jdk-6u-numero_versione-linux-i586.bin. Trovate il file in questa pagina. Scaricato? Il primo passaggio che ci viene chiesto dalle note di installazione, oltre al donwload del file, è la verifica delle sue dimensioni. Possiamo segnare questo parametro nella pagina che precede l'operazione di download. Inutile procedere, quindi, con le successive istruzioni se il file non rispetta le dimensioni indicate in precedenza (detto in altre parole, il download è incompleto!). Prima di eseguire il file assicuratevi di aver dato allo stesso i permessi per l'esecuzione con:

chmod a+x jdk-6u-numero_versione-linux-i586.bin

Spostate il file nella directory in cui volete installare JDK. Attenzione, il file è eseguibile da qualsiasi utente ma solo l'utente root è abilitato a scrivere in certe cartelle del filesystem! Pertanto, se non disponete dei permessi in scrittura nel path inizialmente scelto si consiglia di installare JDK nella propria home. Per spostare il file binario potete usare il comando mv jdk-6u-numero_versione-linux-i586.bin /CARTELA_INSTALLAZIONE (eventualmente preceduto da sudo se non si hanno i permessi in scrittura nella cartella di destinazione).
L'esecuzione del file binario può avvenire sia dalla directory di lavoro corrente che da quella che ospita il file. Se ci troviamo nella cartella che ospita il file (spostato, ad esempio, in precedenza) e raggiunta con il comando cd /CARTELLA_INSTALLAZIONE è allora possibile eseguire il file binario con:

./jdk-6u-numero_versione-linux-i586.bin

Ci verrà proposta la licenza che regola l'utilizzo del prodotto, se siamo interessati a continuare l'installazione non ci resta che accettare. JDK verrà installato, dopo qualche minuto, in una cartella dal nome jdk1.6.0_numero_versione all'interno della cartella che ospita il file binario. L'ambiente appena installato può essere adesso utilizzato nei vari IDE di sviluppo (ad esempio NetBeans IDE ed Eclipse). Per configurare JDK in NetBeans IDE date uno sguardo a questa pagina.

venerdì 25 giugno 2010

Java: componenti swing per le GUI

Una volta conosciuti i limiti e le caratteristiche di alcuni layout possiamo adesso dare uno sguardo ai componenti Swing per le GUI. Vi presenterò i componenti più usati. Trovate, come sempre, un elenco assai più lungo nella documentazione in linea messa a disposizione da SUN Microsystems.

Campi di testo
Per l'immissione del testo esistono essenzialmente due componenti Swing: JTestField e JTextArea. Si tratta di due classi che estendono la classe (astratta) JTextComponent ereditando, quindi, dalla stessa alcuni metodi come:
  • setText(): che riceve una stringa, come parametro, da aggiunge al componente;
  • getText(): che ritorna il testo inserito dall'utente nel componente;
  • setEditable(): che riceve un valore booleano come parametro affinchè si possa abilitare/disabilitare in scrittura il componente;
Ecco subito un esempio:

JTextField area1=new JTextField("Inserire un valore",15);

Esistono più versioni del costruttore JTextField, uno specifica con un numero intero il numero di colonne del componente, mentre un secondo costruttore (come quello usato nell'esempio) permette di specificare una stringa iniziale e il numero di colonne. Se il testo supera il numero di colonne previste quest'ultimo, allora, scorrerà sull'unica riga di input! Se invece vogliamo dare all'utente la possibilità di inserire un testo su più righe abbiamo allora bisogno di un'area di testo:

JTextArea area2=new JTextArea(5,15);

Dove i parametri passati indicano,rispettivamente, il numero di righe e il numero di colonne da assegnare al componente. Il solo uso di un componente JTextArea, ne sono sicuro, non soddisfa quasi mai ne l'utente ne il programmatore. Perchè? Se il testo eccede il numero di colonne il componente viene ridimensionato! Per fissare la sua dimensione e adagiare il testo al suo interno occorre aggiungere al componente un'utile barra di scorrimento con il seguente codice:

JTextArea area2=new JTextArea(5,15);
JScrollPane area2_con_barra=new JScrollPane(area2);


Esiste, infine, un secondo costruttore per il componente JTextArea che permettte di specificare una stringa iniziale da aggiungere al componente (vedere l'esempio successivo). Un metodo usato spesse volte con un componente JTextArea, per l'aggiunta di nuovo testo, è il metodo append. Attraverso questo metodo la strnga passata come parametro viene aggiunta in coda al testo già esistente nel componente!

public class Pannello_GUI extends JPanel {
   public Pannello_GUI() {
      this.setLayout(new BorderLayout());
      JPanel pannello_CENTER=new JPanel();
      JTextField area1=new JTextField("Inserire un valore",15);
      JTextArea area2=new JTextArea("Inserire una descrizione",5,15);
      JScrollPane area2_con_barra=new JScrollPane(area2);
      pannello_CENTER.add(area1);
      pannello_CENTER.add(area2_con_barra);
      this.add(pannello_NORTH,BorderLayout.CENTER);
   }
}


Un particolare componente JTextField è il componente JPasswordField che, come avrete sicuramente intuito, permette l'input di password o dati sensibili nascondendoli da sguardi indiscreti. Il costruttore permette di specificare una stringa iniziale e il numero di colonne da assegnare al componente. Ogni carattere lì digitato verrà sostituito da un carattere che farà da echo (solitamante un asterisco). Inoltre, se vogliamo modificare il carattere di echo possiamo invocare sul componente il metodo setEchoChar e passare a questo il nuovo carattere di echo! L'accesso al valore nascosto, invece, avviene con il metodo getPassword che ritorna un array di caratteri:

public class Pannello_GUI extends JPanel {
   public Pannello_GUI() {
      this.setLayout(new BorderLayout());
      JPanel pannello_NORTH=new JPanel();
      JPasswordField area1=new JPasswordField("",15);
      area1.setEchoChar('*');
      pannello_NORTH.add(area1);
      this.add(pannello_NORTH,BorderLayout.CENTER);
   }
}

Label o etichette per i componenti
Finora ogni componente aggiunto al pannello dell'interfaccia grafica era privo di un riferimento utile a far capire all'utente cosa effettivamente scrivere nei campi dati. Per suggerire all'utente l'utilità di un campo dati si ricorre al componente JLabel:

public class Pannello_GUI extends JPanel {
   public Pannello_GUI() {
      this.setLayout(new BorderLayout());
      JLabel etichetta1=new JLabel("Password:");
      JPanel pannello_CENTER=new JPanel();
      JPasswordField area1=new JPasswordField("",10);
      area1.setEchoChar('*');
      pannello_CENTER.add(etichetta1);
      pannello_CENTER.add(area1);
      this.add(pannello_NORTH,BorderLayout.CENTER);
   }
}

Attenzione all'ordine con cui inserite i componenti!


Caselle per la scelta di opzioni
L'interfaccia Java, così avviene per i form html, offre all'utente la possibilità di effettuare una scelta di un input attraverso l'uso di caselle o checkbox. Una checkbox può avere un proprio significato, essere cioè indipendente da altre checkbox, oppure può appartenere a un gruppo di checkbox (in cui la selezione di una disabilita tutte le altre). A differenza dei precedenti componenti, le checkbox prevedono all'interno del costruttore una label da passare allo stesso componente (sotto forma di stringa). Ecco un esempio:

JCheckBox protocollo=new JCheckBox("Protocollo sicuro");

Per stabilire lo stato del componente si può chiamare su di esso il metodo isSelected (che ritorna true se il componente è selezionato, false se deselezionato). Se invece bisogna selezionare di default una checkbox si deve allora chiamare su di essa il metodo setSelected e passare, quindi, un valore booleano. Per un gruppo di checkbox va invece usato il componente ButtonGroup che permette di istanziare un componente in grado di integrare (attraverso il metodo add), sotto un unico gruppo, più checkbox (create questa volta attraverso JRadioButton):

ButtonGroup server=new ButtonGroup();
JRadioButton server1=new JRadioButton("Server Nro 1",true);
JRadioButton server2=new JRadioButton("Server Nro 2",false);
server.add(server1);
server.add(server2);

Al costruttore di JRadioButton occorre passare una label e un valore booleano. Attenzione, uno solo dei valori appartenenti al componente ButtonGroup dovrà avere il valore true!

public class Pannello_GUI extends JPanel {
   public Pannello_GUI() {
      this.setLayout(new BorderLayout());
      JLabel etichetta1=new JLabel("Password:");
      JPanel pannello_NORTH=new JPanel();
      JPasswordField area1=new JPasswordField("",10);
      area1.setEchoChar('*');
      pannello_NORTH.add(etichetta1);
      pannello_NORTH.add(area1);
      JPanel pannello_CENTER=new JPanel();
      JCheckBox protocollo=new JCheckBox("Protocollo sicuro");
      pannello_CENTER.add(protocollo);
      JPanel pannello_SOUTH=new JPanel();
      ButtonGroup server=new ButtonGroup();
      JRadioButton server1=new JRadioButton("Server Nro 1",true);
      JRadioButton server2=new JRadioButton("Server Nro 2",false);
      server.add(server1);
      server.add(server2);
      pannello_SOUTH.add(server1);
      pannello_SOUTH.add(server2);
      this.add(pannello_NORTH,BorderLayout.NORTH);
      this.add(pannello_CENTER,BorderLayout.CENTER);
      this.add(pannello_SOUTH,BorderLayout.SOUTH);
   }
}


Combo box
Con il componente JComboBox possiamo inserire in un pannello un menu attraverso cui selezionare un valore:

JComboBox server=new JComboBox();
server.addItem("Server N.ro 1");
server.addItem("Server N.ro 2");
server.addItem("Server N.ro 3");

Il metodo AddItem permette l'aggiunta di nuove voci all'interno del componente. L'elemento selezionato viene quindi recuperato attraverso la chiamata al metodo getSelectedItem. Se si vuole dare la possibilità di modificare una voce all'interno della lista (anche a runtime) occorre abilitare in scrittura il componente mediante il metodo setEditable (che accetta come parametro un valore booleano).

public class Pannello_GUI extends JPanel {
   public Pannello_GUI() {
      this.setLayout(new BorderLayout());
      JLabel etichetta1=new JLabel("Password:");
      JPanel pannello_NORTH=new JPanel();
      JPasswordField area1=new JPasswordField("",10);
      area1.setEchoChar('*');
      pannello_NORTH.add(etichetta1);
      pannello_NORTH.add(area1);
      JPanel pannello_CENTER=new JPanel();
      JCheckBox protocollo=new JCheckBox("Protocollo sicuro");
      pannello_CENTER.add(protocollo);
      JPanel pannello_SOUTH=new JPanel();
      JLabel etichetta2=new JLabel("Server:");
      JComboBox server=new JComboBox();
      server.setEditable(true);
      server.addItem("Server N.ro 1");
      server.addItem("Server N.ro 2");
      server.addItem("Server N.ro 3");
      pannello_SOUTH.add(etichetta2);
      pannello_SOUTH.add(server);
      this.add(pannello_NORTH,BorderLayout.NORTH);
      this.add(pannello_CENTER,BorderLayout.CENTER);
      this.add(pannello_SOUTH,BorderLayout.SOUTH);
   }
}


Menu
I menu a discesa caratterizzano da sempre gli ambienti e i programmi organizzati in frame. Si tratta di un componente indispensabile per alcune applicazioni, soprattutto quando non si vuole riempire eccessivamente il pannello utente. Ecco allora che conviene organizzare all'interno di un menu a discesa ogni comando che è possibile inviare al programma in esecuzione. La barra contenente un menu raggruppa sotto alcune voci principali i comandi e/o le funzionalità del programma e può, quindi, essere articolato in un o più sotto livelli di menu. La classica barra dei menu presente nei programmi Windows ne è un tipico esempio, lì solitamente troviamo una voce File, Edit, View etc... Per aggiungere una barra per il menu bisogna creare un oggetto JMenuBar:

JMenuBar menu=new JMenuBar();

che farà da contenitore a tutte le voci. Quindi bisogna aggiungere le voci principali aggiungendo in ognuna di queste una o più oggetti JMenuItem, attraverso il metodo add:

JMenu fileMenu=new JMenu("File");
JMenuItem apriFile=new JMenuItem("Apri file");
JMenuItem chiudiFile=new JMenuItem("Chiudi file");
JMenuItem esciFile=new JMenuItem("Esci");
fileMenu.add(apriFile);
fileMenu.add(chiudiFile);
fileMenu.add(esciFile);

Fatto ciò occorre aggiungere il singolo menu al contenitore dei menu, overro alla barra in alto dei menu con il comando:

menu.add(fileMenu);

Attenzione, la barra dei menu va aggiunta al frame che contiene i pannelli e non ai pannelli! Per questo motivo ho modificato leggermente la classe che descrive e dispone i componenti nel frame:

public class Pannello_GUI extends JPanel {
   public Pannello_GUI(Finestra finestra) {
      this.setLayout(new BorderLayout());
   JLabel etichetta1=new JLabel("Password:");
   JPanel pannello_NORTH=new JPanel();
   JPasswordField area1=new JPasswordField("",10);
   area1.setEchoChar('*');
   pannello_NORTH.add(etichetta1);
   pannello_NORTH.add(area1);
   JPanel pannello_CENTER=new JPanel();
   JCheckBox protocollo=new JCheckBox("Protocollo sicuro");
   pannello_CENTER.add(protocollo);
   JPanel pannello_SOUTH=new JPanel();
   JLabel etichetta2=new JLabel("Server:");
   JComboBox server=new JComboBox();
   server.setEditable(true);
   server.addItem("Server N.ro 1");
   server.addItem("Server N.ro 2");
   server.addItem("Server N.ro 3");
   pannello_SOUTH.add(etichetta2);
   pannello_SOUTH.add(server);
   JMenuBar menu=new JMenuBar();
   JMenu fileMenu=new JMenu("File");
   JMenuItem apriFile=new JMenuItem("Apri file");
   JMenuItem chiudiFile=new JMenuItem("Chiudi file");
   JMenuItem esciFile=new JMenuItem("Esci");
   fileMenu.add(apriFile);
   fileMenu.add(chiudiFile);
   fileMenu.add(esciFile);
   JMenu impostazioniMenu=new JMenu("Impostazioni");
   JMenuItem modificaImpostazioni=new JMenuItem("Modifica impostazioni");
   impostazioniMenu.add(modificaImpostazioni);
   JMenu infoMenu=new JMenu("Info");
   JMenuItem guidaInfo=new JMenuItem("Guida");
      infoMenu.add(guidaInfo);
      menu.add(fileMenu);
      menu.add(impostazioniMenu);
      menu.add(infoMenu);
   finestra.setJMenuBar(menu);
      this.add(pannello_NORTH,BorderLayout.NORTH);
   this.add(pannello_CENTER,BorderLayout.CENTER);
   this.add(pannello_SOUTH,BorderLayout.SOUTH);
   }
}


Termino qui questa breve presentazione di componenti per interfacce Java. Vorrei comunque ricordare, nuovamente, che i componenti fin qui visti non esauriscono l'intero set di componenti Swing. Per questo motivo invito gli utenti a cercare gli stessi, nonchè i metodi, all'interno della documentazione in linea (e nelle rispettive classi padre)!

martedì 22 giugno 2010

Java: gestione dei layout per le GUI

Le GUI, graphical user interface, svolgono l'importante compito dell'interfacciamento fra programma e utente. Java offre al programmatore, e indirettamente all'utente, numerosi componenti per l'interfaccia grafica. La libreria di riferimento è ancora una volta la libreria Swing di Java. Attraverso questa si possono istanziare bottoni, aree di testo, campi di testo per password, menu etc... Ancora prima di vedere come ciò può essere fatto dobbiamo necessariamente preoccuparci di un'importante questione, la gestione del layout.
Come andranno posizionati all'interno del frame i componenti dell'interfaccia Swing? Di default, Java utilizza un layout non sempre idoneo all'applicazione che abbiamo in mente. Questo, infatti, inserisce ogni componente al centro del frame, a partire dalla prima riga utile nel frame. Quando lo spazio per un nuovo componente non è sufficiente a contenere lo stesso sulla medesima riga, quest'utlimo viene dunque inserito su una riga a parte!



JButton buttonA=new JButton("A");
JButton buttonA=new JButton("B");
JButton buttonA=new JButton("C");
JButton buttonA=new JButton("D");
JButton buttonA=new JButton("E");
JButton buttonA=new JButton("F");
add(buttonA);
add(buttonB);
add(buttonC);
add(buttonD);
add(buttonE);
add(buttonF);

Il gestore di layout di flusso, quello usato di default da Java, permette l'allineamento dei componenti a destra o a sinistra del frame. La scelta può essere fatta invocando il metodo setLayout nel costruttore del pannello e passando allo stesso una delle due costanti FlowLayout.LEFT oppure FlowLayout.RIGHT. Ecco un esempio:

setLayout(new FlowLayout(FlowLayout.LEFT));

Esiste anche un costruttore per layout di flusso che permette di specificare oltre all'allineamento anche le distanze orizzontali e verticali fra i componenti dell'interfaccia. Per ulteriori informazioni consultate la documentazione in linea. Come dicevo poco fa non sempre questo layout può andare bene per la GUI che abbiamo in mente. Esiste allora un diverso gestore di layout, ossia il gestore di layout di bordi. Quest'ultimo, suddividendo il frame in più aree, permette l'aggiunta dei componenti in ognuna di esse.


this.setLayout(new BorderLayout());
JButton northButton=new JButton("Nord");
JButton westButton=new JButton("Ovest");
JButton centerButton=new JButton("Centro");
JButton centerButtonFlip=new JButton("Inverti");
JButton eastButton=new JButton("Est");
JButton southButton=new JButton("Sud");
add(northButton,BorderLayout.NORTH);
add(westButton,BorderLayout.WEST);
add(centerButtonFlip,BorderLayout.CENTER);
add(centerButton,BorderLayout.CENTER);
add(eastButton,BorderLayout.EAST);
add(southButton,BorderLayout.SOUTH);


L'immagine dovrebbe mostrare alcune importanti limitazioni di questo layout. La prima cosa che salta subito agli occhi è che i componenti vengono sì aggiunti nelle rispettive aree del frame ma vanno, purtroppo, ad occupare l'intero spazio! Ed ancora, osservando attentamente il codice scoprirete che all'interno di esso erano stati chiesti 2 bottoni al centro, nel frame ne appare uno solo! Questo perchè ogni componente aggiunto in una zona del frame sovrasta, se presente, un componente aggiunto in precedenza. Il problema può essere tranquillamente aggirato assegnando il componente in un pannello a parte e aggiungendo, quindi, lo stesso pannello al pannello con il layout!

this.setLayout(new BorderLayout());
JPanel northPanel=new JPanel();
JButton northButton=new JButton("Nord");
northPanel.add(northButton);
this.add(northPanel,BorderLayout.NORTH);
JPanel westPanel=new JPanel();
JButton westButton=new JButton("Ovest");
westPanel.add(westButton);
this.add(westPanel,BorderLayout.WEST);
JPanel centerPanel=new JPanel();
JButton centerButton=new JButton("Centro");
JButton centerButtonFlip=new JButton("Inverti");
centerPanel.add(centerButton);
centerPanel.add(centerButtonFlip);
this.add(centerPanel,BorderLayout.CENTER);
JPanel eastPanel=new JPanel();
JButton eastButton=new JButton("Est");
eastPanel.add(eastButton);
this.add(eastPanel,BorderLayout.EAST);
JPanel southPanel=new JPanel();
JButton southButton=new JButton("Sud");
southPanel.add(southButton);
this.add(southPanel,BorderLayout.SOUTH);


Direi che va decisamente meglio. Nell'esempio mostrato sopra ho quindi creato un pannello per ogni area o bordo. Ho poi aggiunto i componenti dell'interfaccia in questi pannelli e infine ho aggiunto gli stessi al frame, collocandoli nelle rispettive aree. Ciò funziona perchè di default, per un pannello, il gestore di layout è un gestore di layout di flusso! L'ultimo gestore di layout che vi presento permette di disporre i componenti dell'interfaccia utente secondo una griglia:

setLayout(new GridLayout(4,4,5,5));
JButton button1=new JButton("1");
JButton button2=new JButton("2");
JButton button3=new JButton("3");
JButton button4=new JButton("4");
JButton button5=new JButton("5");
JButton button6=new JButton("6");
JButton button7=new JButton("7");
JButton button8=new JButton("8");
JButton button9=new JButton("9");
JButton button0=new JButton("0");
JButton buttonSum=new JButton("+");
JButton buttonDiff=new JButton("-");
JButton buttonMul=new JButton("*");
JButton buttonDiv=new JButton("/");
JButton buttonCalc=new JButton("=");
JButton buttonCanc=new JButton("Canc");
add(button1);
add(button2);
add(button3);
add(buttonSum);
add(button4);
add(button5);
add(button6);
add(buttonDiff);
add(button7);
add(button8);
add(button9);
add(buttonMul);
add(buttonCanc);
add(button0);
add(buttonCalc);
add(buttonDiv);


L'ordine di aggiunta per i componenti ha dunque inizio a partire dalla prima cella in alto a sinistra. Il costruttore del layout permette di specificare il numero di righe e di colonne (i primi due parametri), se necessario è poi possibile indicare le distanze verticali e orizzontali, in pixel, che devono esistere fra i componenti (gli ultimi due parametri).