static import

Da Head First Java O'Reilly, capitolo 10.

L'idea della direttiva static import é quella di risparmiare un po' di lavoro di tastiera. D'altro canto il codice che usa static import può risultare poco chiaro. Conviene quindi fare un uso molto limitato delle sue potenzialità.

Questo un'esempio d'uso:

//...
import static java.lang.System.out;
import static java.lang.Math.*;
//...

public void importStatic() {
out.println("sqrt 2: " + sqrt(2));
out.println("tan 60: " + tan(60));
}
//...

Nota che é possibile fare un'import static su un intera classe (Math, nel nostro caso) mettendo a disposizione tutti i suoi membri statici in un sol colpo. L'impressione che può avere il lettore causale del codice é che esista un oggetto statico (out) e metodi statici globali, il che é evidentemente impossibile in Java. Oppure che out e i metodi di Math facciano parte della classe corrente. Insomma, la cosa può creare qualche malinteso.

Date e calendari

Da Head First Java O'Reilly, capitolo 10. Parte dedicata alla formattazione delle date.

Per formattare le date si usa la funzione static format() di String in maniera molto simile a quanto visto per i numeri.

La differenza fondamentale é che nella formattazione delle date si usano due caratteri per specificare il tipo del parametro e non uno:
String.format("%tc", new Date());
  • %tc: la data completa (dom dic 13 21:11:42 CET 2009)
  • %tr: il solo tempo nel giorno corrente (09:16:34 PM)
  • %tA: il giorno della settimana (domenica)
  • %tB: il mese dell'anno (dicembre)
  • %tr: il giorno nel mese (13)

Un utile flag da usarsi come numero di argomento é "<", che permette di riferirsi facilmente allo stesso parametro. Esempio:

System.out.println(String.format("%tA %<td %<tB", new Date()));

stampa "domenica 13 dicembre".

Calendar

Se java.util.Date é la classe di elezione per ottenere il timestamp corrente, per manipolare le date é meglio usare la classe java.util.Calendar che, fra l'altro, é stata disegnata per supportare diversi calendari e non solo il nostro, il cosiddetto calendario gregoriano. In ogni caso, se non specifichiamo quale calendario vogliamo utilizzare otteniamo il default per localizzazione corrente. Il meccanismo per far ciò é implementato nel metodo getInstance() della classe Calendar che deciderà quale delle sue classi figlie instanziare per noi.

Ecco un esempio d'uso del Calendar:

public void calendar() {
Calendar c = Calendar.getInstance();
System.out.println(c.getTime());

c.set(2012, 11, 20, 20, 12); // Thu Dec 20 20:12:22 CET 2012
System.out.println(c.getTime());

long day = c.getTimeInMillis() + 1000 * 60 * 60; // + 1h
c.setTimeInMillis(day); // Thu Dec 20 21:12:29 CET 2012
System.out.println(c.getTime());
System.out.println("Hour: " + c.get(Calendar.HOUR_OF_DAY)); // Hour: 21
c.add(Calendar.DATE, 35); // Thu Jan 24 21:12:29 CET 2013
System.out.println("35 days later: " + c.getTime());
c.roll(Calendar.DATE, 35); // Mon Jan 28 21:12:29 CET 2013
System.out.println("35 days rolling: " + c.getTime());
c.set(Calendar.DATE, 1); // Tue Jan 01 21:12:29 CET 2013
System.out.println("set day to 1: " + c.getTime());
}

Alcuni tra i campi del calendario più usati sono:
  • DATE, DATE_OF_MONTH: il giorno del mese;
  • HOUR, HOUR_OF_DAY: l'ora del giorno;
  • MILLISECOND;
  • MINUTE;
  • MONTH: il mese;
  • YEAR: l'anno;
Alcuni tra i metodi più utilizzati:
  • add(int field, int amount): aggiunge o sottrae tempo dal campo specificato;
  • get(int field): ritorna il valore del campo specificato;
  • getInstance(): un calendario per la localizzazione corrente;
  • getTimeInMillis(): la data convertita in millisecondi (long);
  • roll(int field, boolean up): aggiunge o toglie una unità dal campo specificato, senza modificare gli altri campi;
  • roll(int field, int amount): varia il campo specificato, senza impattare sugli altri;
  • set(int field, int value): specifica direttamente il valore per un determinato campo;
  • set(int year, int month, int day, int hour, int minute): specifica il tempo;
  • setTimeInMillis(long millis): specifica la data del calendario a partire dal tempo in millisecondi.

Formattare numeri

Da Head First Java O'Reilly, capitolo 10. Parte dedicata alla formattazione dei numeri.

Ci sono svariate possibilità per formattare i numeri in Java.

Ad esempio, se vogliamo inserire punti e virgole tra le cifre, per rendere più leggibile grosse cifre, possiamo usare il metodo statico format() della classe String usando l'opzione ",":

public void commaDecimal() {
String s = String.format("%,11d", 10000000);
System.out.println(s);
}

public void commaFloating() {
String s = String.format("%,14.2f", 10000000.23);
System.out.println(s);
}

public void commaException() {
try {
String s = String.format("%,d", 10000000.23);
System.out.println(s);
}
catch (java.util.IllegalFormatConversionException ifce) {
System.out.println("Can't do that: " + ifce.getMessage());
}
}

public static void main(String[] args) {
Formatting f = new Formatting();

f.commaDecimal();
f.commaFloating();
f.commaException();
}

E il risultato dovrebbe essere questo:

10.000.000
10.000.000,230000
Can't do that: d != java.lang.Double

Da notare che i separatori utilizzati sono funzione della localizzazione utilizzata. Nel mio caso uso i settaggi italiani, e quindi una virgola per i decimali e un punto ogni tre cifre alla sua sinistra.

Il metodo format() opera in modo molto simile alle convenzioni della printf del linguaggio C. Il percento indica di inserire un parametro in quella posizione.

La stringa di formattazione "%,14.2f" va interpretata come: stampa il numero floating point (f) passato come parametro (%) inserendo i separatori (,) visualizzando almeno 14 caratteri (14) di cui due sono cifre frazionarie (.2).

La struttura generica di un formattatore é la seguente:

%[numero di argomento][opzioni][ampiezza][.precisione]tipo

Il tipo é l'unico elemento obbligatorio. In caso di mancata compatibilità con il parametro effettivamente passato viene generata una eccezione, come nell'esempio sopra riportato, dove si é specificato che ci si attendeva un decimale (un intero, quindi) ma in realtà é stato passato un floating point.
  • %d: decimale, é atteso un intero (byte, short, int, char o un wrapper a questi tipi)
  • %f: floating point, ovvero float, double o BigDecimal
  • %x: esadecimale, applicabile a byte, short, int, long e BigInteger.
  • %c: carattere, applicabile a byte, short, char, int.
Il metodo format() accetta un numero variabile di argomenti, secondo uno schema varargs mutuato dal linguaggio C, é dunque possibile specificare svariati parametri in una stringa di formattazione. Di default l'associazione é fatta in modo diretto: il primo % corrisponde al primo dei parametri specificati a seguire.

Wrapping e unwrapping

Da Head First Java O'Reilly, capitolo 10. Parte dedicata al wrapping di variabili primitive in oggetti delle corrispondenti classi wrapper, e del loro unwrapping.

Dalla versione 5.0 di Java é disponibile la feature di autoboxing, il che vuol dire che un valore primitivo viene automaticamente convertito in oggetto del tipo wrapper corrispondente dove richiesto. E viceversa, un oggetto di un tipo wrapper viene convertito automaticamente nel valore primitivo se necessario.

In questo modo, ad esempio, si semplifica la gestione dei container, dato che questi, in Java, posso lavorare solo con oggetti e non con tipi primitivi.

I wrapper mettono inoltre a disposizione dei metodi per convertire esplicitamente una stringa nel valore primitivo correlato. Per il boolean occorre invece passare dal costruttore, che ammette l'uso di una stringa. Ovviamente ci si deve aspettare la possibilità di una eccezione in caso la stringa non sia quanto atteso.

try {
int x = Integer.parseInt("2");
double d = Double.parseDouble("2.23");
boolean b = new Boolean("true").booleanValue();
}
catch (NumberFormatException nfe) {
//
}

Il modo più semplice per convertire un numero in una stringa é quello di sfruttare l'overloading dell'operatore + definito per le stringhe. Oppure si può fare riferimento al metodo toString() che é definito per ogni oggetto e, nel caso dei wrapper, anche come metodo static:

String s = "" + 23.4;
String t = Double.toString(12.3);

Un po' di matematica

Da Head First Java O'Reilly, capitolo 10. Si parla di variabili di istanza e metodi statici e, come esempio, della classe Math.

Math ha solo metodi statici. Tra l'altro il suo costruttore é dichiarato privato e perciò non é possibile instanziare oggetti di questa classe.

Tra i metodi citati:

Math.random(): ritorna un double in [0, 1)

public void doRandom() {
System.out.println("a random number:" + Math.random());

int[] result = new int[5];
for(int i = 0; i < 10000; ++i) {
result[(int)(Math.random() * 5)]++;
}
System.out.println("a few random numbers in [0, 4]");
for(int i = 0; i < 5; ++i) {
System.out.println(i + ": " + result[i]);
}
}

Math.abs(): ritorna un float, double, int o long che é il valore assoluto dell'argomento passato

public void doAbs() {
int i = Math.abs(-12);
System.out.println("absolute value of negative int: " + i);

long l = Math.abs(-12L);
System.out.println("absolute value of negative long: " + l);

float f = Math.abs(-12.24f);
System.out.println("absolute value of negative float: " + f);

double d = Math.abs(-12.24);
System.out.println("absolute value of negative double: " + d);
}

Math.round(): ritorna un int se il valore passato come argomento e float, un long se si passa un double, arrotondato al valore intero più vicino

public void doRound() {
int i = Math.round(12.24f);
System.out.println("rounding a float: " + i);

long l = Math.round(-12.24);
System.out.println("rounding a double: " + l);
}

Math.min(): ritorna il minore tra i due argomenti passati (di tipo int, long, float, o double)

public void doMin() {
double d = Math.min(12.34, -2.5);
System.out.println("min of two doubles: " + d);

float f = Math.min(12.34f, -2.5f);
System.out.println("min of two floats: " + f);

long l = Math.min(12L, 2L);
System.out.println("min of two longs: " + l);

int i = Math.min(12, 2);
System.out.println("min of two ints: " + i);
}

Math.max(): ritorna il maggiore tra i due argomenti passati

public void doMax() {
double d = Math.max(12.34, -2.5);
System.out.println("max of two doubles: " + d);

float f = Math.max(12.34f, -2.5f);
System.out.println("max of two floats: " + f);

long l = Math.max(-12L, 2L);
System.out.println("max of two longs: " + l);

int i = Math.max(-12, 2);
System.out.println("max of two ints: " + i);
}

Battaglia navale migliorata

Proseguo la lettura di Head First Java O'Reilly, capitolo 6.

Si era creata una prima versione semplificata del programma di battaglia navale, ora introduciamo cambiamenti per rendere l'applicazione più aderente alle specifiche iniziali.

Per prima cosa creo un nuovo enumeratore che definisce le celle disponibili nel tavoliere della battaglia navale:

package DotCom;

public enum Coord {
A1, A2, A3, A4, A5, A6, A7,
B1, B2, B3, B4, B5, B6, B7,
C1, C2, C3, C4, C5, C6, C7,
D1, D2, D3, D4, D5, D6, D7,
E1, E2, E3, E4, E5, E6, E7,
F1, F2, F3, F4, F5, F6, F7,
G1, G2, G3, G4, G5, G6, G7,
}
Creiamo poi la classe Buster che gestisce le navi sul tavoliere:

package DotCom;

import java.util.ArrayList;

public class Buster {

private ArrayList<Ship> ships = new ArrayList<Ship>();
private int guesses = 0;

public void add(Ship ship) {
ships.add(ship);
}

/**
*
* @param c where to shoot
* @return true if all ships are destroyed
*/
public boolean shoot(Coord c) {
guesses++;

System.out.println("[" + c + "] " + Result.Miss);
return false;
}

public int getGuesses() {
return guesses;
}
}

Le navi da colpire vengono passate all'istanza di Buster per mezzo del metodo add() che semplicemente aggiunge la nave alla lista privata. L'algoritmo per la creazione di una nave nel tavoliere é non banale, fra l'altro occorre evitare che due navi si sovrappongano, e aldilà dello scopo dell'esempio. Ce le dobbiamo costruire a mano e fare attenzione a costruirle in modo coerente.

Il metodo shoot() ha al momento una implementazione minimale, lo completo più avanti. Deve funzionare in questo modo:
  • accetta in input la coordinata del nostro colpo;
  • incrementa il contatore dei colpi sparati;
  • verifica se il colpo ha successo o meno e, se anche l'ultima nave é stata affondata, si ritorna true (parte ancora da implementare);
  • se il colpo é andato a vuoto lo si comunica all'utente prima di tornare false.

Abbiamo poi un getter per la variabile di istanza guesses, che tiene sotto controllo il numero di tentativi fatti.

Aggiungo un nuovo test, che utilizza Buster:

@Test
public void testShip() {
System.out.println("test a ship");

Buster buster = new Buster();

ArrayList<Coord> t1 = new ArrayList<Coord>();
t1.add(Coord.A1);
t1.add(Coord.B1);
t1.add(Coord.C1);
buster.add(new Ship("Endeavour", t1));

for(Coord c : Coord.values()) {
if(buster.shoot(c)) {
int guesses = buster.getGuesses();
assertEquals(Coord.C1.ordinal() + 1, guesses);
return;
}
}

fail("ship still floating");
}

Naturalmente fallisce, data l'implementazione che ho dato a shoot(), ma ora ne completo il codice in questo modo:

public boolean shoot(Coord c) {
guesses++;

for (Ship ship : ships) {
switch (ship.check(c)) {
case Hit:
System.out.print("[" + c + "] " + ship.getName() + ": ");
System.out.println(Result.Hit);

return false;
case Kill:
System.out.print("[" + c + "] " + ship.getName() + ": ");
System.out.println("Destroyed!");

ships.remove(ship);
return ships.isEmpty();
}
}

System.out.println("[" + c + "] " + Result.Miss);
return false;
}

Si cicla sulle navi presenti nella lista di Buster. Se check() riporta che la nave é stata colpita (ma non affondata) si segnala la cosa e si ritorna false, se la nave é affondata viene tolta dalla lista e si ritorna true se la lista é ora vuota.
Se nessuno di questi due casi sono verificati su nessuna delle navi in lista, allora segnaliamo che il colpo é andato a vuoto.

Il test viene passato correttamente, ne aggiungiamo un altro per verificare quello che sarebbe l'uso reale dell'applicativo:

@Test
public void testThreeShip() {
System.out.println("test three ships");

Buster buster = new Buster();

ArrayList<Coord> t1 = new ArrayList<Coord>();
t1.add(Coord.A1);
t1.add(Coord.B1);
t1.add(Coord.C1);
buster.add(new Ship("Endeavour", t1));

ArrayList<Coord> t2 = new ArrayList<Coord>();
t2.add(Coord.B3);
t2.add(Coord.B4);
t2.add(Coord.B5);
buster.add(new Ship("Enterprise", t2));

ArrayList<Coord> t3 = new ArrayList<Coord>();
t3.add(Coord.D1);
t3.add(Coord.D2);
t3.add(Coord.D3);
buster.add(new Ship("Beagle", t3));

for(Coord c : Coord.values()) {
if(buster.shoot(c)) {
int guesses = buster.getGuesses();
assertEquals(Coord.D3.ordinal() + 1, guesses);
return;
}
}

fail("ship still floating");
}

ArrayList

Da Head First Java O'Reilly, capitolo 6. Continuia trattazione della progettazione e sviluppo di un primo semplice programma in Java, una sorta di battaglia navale.

Abbiamo un bug nel nostro programma. Ci siamo dimenticati rimuovere i colpi che sono andati a segno dalla lista di caselle che compongono la nave da colpire.

Il modo più intuitivo di agire é quello di fare in modo che la nave sia un ArrayList di locazioni. Ogni volta che una cella della nave é individuata viene rimossa. Dunque la nostra nave contiene tutte e sole le celle non ancora scoperte.

ArrayList é una classe container template fornita da Java.
Alcuni dei metodi che mette a disposizione sono:
  • add(E elem): aggiunge l'elemento alla lista;
  • remove(int index): rimuove l'elemento all'indice passato;
  • remove(E elem): rimuove l'elemento passato, se presente in lista;
  • contains(E elem): true se c'é un match per l'elemento passato;
  • isEmpty(): true se la lista é vuota;
  • indexOf(E elem): l'indice dell'elemento, o -1;
  • size(): il numero di elementi nella lista;
  • get(int index): l'elemento all'indice passato.
La classe Simple cambia in questo modo:

package DotCom;

import java.util.ArrayList;

public class Simple {
private ArrayList<Integer> target;

public void setTarget(ArrayList<Integer> target) {
this.target = target;
}

public Result check(int x) {
int index = target.indexOf(x);
if(index == -1) {
return Result.Miss;
}

target.remove(index); // hit or kill!
return target.isEmpty() ? Result.Kill : Result.Hit;
}
}

Il target dei colpi ora é un ArrayList di interi, il metodo setTarget() cambia il suo parametro in input; check() risulta semplificata, in quanto usa i metodi di ArrayList.

I test cambiano di conseguenza, in quanto il bersaglio viene ora passato come ArrayList di interi. Ad esempio il finto main di prova prepara il target in questo modo:

int base = (int) (Math.random() * 5); // [0,4]
ArrayList<Integer> target = new ArrayList<Integer>();
target.add(base);
target.add(base + 1);
target.add(base + 2);

Baco risolto, i test ora corrono senza dare errori.

Battaglia navale

Da Head First Java O'Reilly, leggo il capitolo 5 che tratta la progettazione e lo sviluppo di un primo semplice programma in Java.

Si tratta di una piccola battaglia navale giocata dall'utente contro il computer. Si gioca su una tabella 7x7.
  • setup: tre navi vengono messe sulla tabella.
  • il gioco: si itera finché non ci sono più navi:
    • si chiedono all'utente le coordinate ("C6", "A0", ...)
    • il risultato può essere hit, miss, kill
    • In caso di miss si chiede la prossima mossa.
    • In caso di kill si rimuove la nave, se non ci sono più navi, il gioco termina, altrimenti si chiede la prossima mossa.
    • In caso di hit si rimuove la cella e si chiede la mossa successiva.
  • fine: si valuta il risultato dell'utente.
La prima versione é ancora più semplice: la tabella si riduce ad un array [0, 6], chiamiamo la classe che implementa questa soluzione DotCom.Simple, tale classe avrà un metodo per creare un bersaglio e un metodo per verificare un colpo:
package DotCom;

public class Simple {
private int[] target;
private int hits;

public void setTarget(int [] target) {
this.target = target;
}

public Result check(int x) {
return Result.Miss;
}
}
La classe Result é un enum che può assumere i valori Miss, Hit, Kill:
public enum Result {
Miss, Hit, Kill
}
Scrivo un primo test per l'applicazione:
public class SimpleTest {
//...

@Test
public void testSimple() {
System.out.println("first test");
int[] target = {2,3,4};
Simple instance = new Simple();
instance.setTarget(target);

Result result = instance.check(2);
assertEquals(Result.Hit, result);
}
}
Come atteso il test ritorna errore, ma scriviamo ora una prima implementazione reale del metodo check():
public Result check(int x) {
Result result = Result.Miss;

for(int cell : target) {
if(x == cell) {
result = Result.Hit;
hits++;
break;
}
}

if(hits == target.length) {
result = Result.Kill;
}

System.out.println(result);
return result;
}
Scrivo ora un test più completo che simula quello che sarà il main reale:
@Test
public void fakeSimpleMain() {
System.out.println("fake main");

int guesses = 0;
int base = (int) (Math.random() * 5); // [0,4]
int[] target = {base, base + 1, base + 2};
Simple instance = new Simple();
instance.setTarget(target);

for(int i = 0; i < 7; ++i) {
++guesses;
if(instance.check(i) == Result.Kill) {
assertEquals(guesses, base + 3);
return;
}
}
fail("can't kill");
}
Ma c'é un bug evidente, che viene evidenziato da questo test:
@Test
public void bug1() {
System.out.println("bug1");

int base = (int) (Math.random() * 5); // [0,4]
int[] target = {base, base + 1, base + 2};
Simple instance = new Simple();
instance.setTarget(target);

for(int i = 0; i < 7; ++i) {
if(instance.check(base) == Result.Kill) {
fail("no kill expected");
}
}
}
Ripetendo il colpo sulla prima cella del bersaglio si ottiene, incongruamente, l'affondamento della nave.

STL - queue

Dal quarto capitolo di Designing Components with the C++ STL, di Ulrich Breymann.

La coda (queue) permette di inserire oggetti ad un suo estremo e rimuoverli dalla parte opposta. Gli elementi ad entrambi i lati possono essere letti senza essere rimossi.

Può essere implementata usando list o deque.

Questi i metodi messi a disposizione:
  • bool empty() const;
  • size_type size() const: il numero di elementi in coda;
  • value_type& front(), const value_type& front() const: legge il valore all'inizio;
  • value_type& back(), const value_type& back() const: legge il valore alla fine;
  • void push(const value_type& x): immette un nuovo elemento in coda;
  • void pop(): elimina il primo elemento;
Un esempio di uso di uno stack:
#include<iostream>
#include<queue>

using namespace std;

int main() {
queue<int> aQueue;

if(aQueue.empty()) {
cout << "Empty queue created." << endl;
}

cout << "size is " << aQueue.size() << endl;

aQueue.push(5);
aQueue.push(7);
cout << "size is " << aQueue.size() << endl;
cout << "front is " << aQueue.front() << endl;

aQueue.pop();
cout << "size is " << aQueue.size() << endl;
cout << "front is " << aQueue.front() << endl;

aQueue.pop();
cout << "size is " << aQueue.size() << endl;
cout << "front is " << aQueue.front() << endl;
}

Un piccolo tutorial MVC

Il terzo capitolo di Head First - Servlets and JSP edito dalla O'Reilly, é dedicato alla creazione di una piccola web application usando il pattern MVC.

L'applicazione ha lo scopo di fornire suggerimenti su quale birra scegliere, usa:
  • BeerExpert.java (un POJO che fa da Model);
  • beerForm.html e result.jsp (View);
  • una servlet (Controller).
Le componenti suddette interagiscono tra loro in questo modo:
  • l'utente richiede l'accesso alla pagina web beerForm.html;
  • il container reperisce la risorsa;
  • il container rende disponibile beerForm.html all'utente;
  • l'utente specifica le informazioni richieste quindi genera una request per il container;
  • il container determina quale servlet richiamare e le passa la request;
  • la servler chiama BeerExpert per determinare la risposta;
  • la servlet aggiunge il risultato ottenuto da BeerExpert alla request;
  • la servlet inoltra le request a result.jsp;
  • la JSP legge la risposta dalla request;
  • la JSP genera una pagina che passa al container;
  • il container ritorna la pagina all'utente.
Per la creazione dell'ambiente di sviluppo utilizzo Netbeans, per il rilascio su Tomcat uso le funzionalità amministrative di Tomcat che permettono di fare il deploy di una web application (in formato WAR) con estrema semplicità.

Continuo a lavorare sullo stesso progetto creato nel capitolo precedente, HeadFirstWeb.

Iniziamo da beerForm.html, una pagina che contiene un form che richiama, via POST, la servlet SelectBeer.do, che creeremo in seguito. L'unico attributo che possiamo variare é il colore della birra, i cui possibili valori sono presentati in un menù a tendina:
<html>
<head>
<title>Beer selector</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<h1>Beer Selection Page</h1>
<form method="POST" action="SelectBeer.do">
Select beer color:
<select name="color">
<option value="light">light</option>
<option value="amber">amber</option>
<option value="brown">brown</option>
<option value="dark">dark</option>
</select>
<p><input type="SUBMIT"></p>
</form>
</body>
</html>
Facciamo il test la pagina, modificando leggermente il layout secondo il gusto personale. Evidentemente se clicchiamo sul bottone otteremo un messaggio di errore che ci dice che la risorsa che abbiamo richiesto non é disponibile.

Pensiamo ora alla servlet. Decidiamo di darle il nome interno BeerSelector, di mettere il file che la implementa nel package hf.beer con nome BeerSelect.java. L'URL utilizzato dall'utente per accederla é, come abbiamo già visto, SelectBeer.do.

Creo dunque la servlet hf.beer.BeerSelect.java e specifico i nomi come indicato sopra a Netbeans che mi aggiorna il DD, web.xml, in questo modo:
<web-app ...>
...
<servlet>
<servlet-name>BeerSelector</servlet-name>
<servlet-class>hf.beer.BeerSelect</servlet-class>
</servlet>
...
<servlet-mapping>
<servlet-name>BeerSelector</servlet-name>
<url-pattern>/SelectBeer.do</url-pattern>
</servlet-mapping>
</web-app>
Faccio un nuovo test del progetto, e verifico che ora cliccare sul bottone non mi dà un errore ma mi visualizza una pagina bianca. Questo perché l'ho lasciato creare la servlet da Netbeans che mi ha generato il codice di default che non fa nulla.

Dal nome logico al file del servlet:
  • Dalla pagina HTML il browser genera la richiesta di /HeadFirstWeb/SelectBeer.do dove la prima parte del path é la root della Web App e la parte finale é il nome logico della risorsa.
  • Il container cerca nel DD il nome logico della servlet tra gli url-pattern negli elementi servlet-mapping trovando il servlet-name (nome interno).
  • Andiamo a cercarci tra gli elementi servlet quello che ha il servlet-name che abbiamo trovato nel passo precedente.
  • E finalmente troviamo il servlet-class (nome fisico) che viene usato dal container. Se la servlet non é già stata inizializzata la classe viene caricata e la servlet inizializzata.
  • Il container inizia un nuovo thread per gestire la richiesta e passa l'oggetto request al thread.
  • Al termine dell'elaborazione da parte della servlet il container manda la risposta al cliente.
Prima versione della servlet

Modifico il codice di default proposto da Netbeans in questo modo:
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
out.println("<html>");
out.println("<head>");
out.println("<title>Beer Selection Advice</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Got beer color: " + request.getParameter("color") + "</h1>");
out.println("</body>");
out.println("</html>");
} finally {
out.close();
}
}
Il punto fondamentale é che si usa il metodo getParameter() sulla request per accedere il parametro color passato dall'utente, che viene scritto nella response.

Compilo, faccio il deploy, e adesso cliccando sul bottone possiamo avere un primo feedback positivo.

Architettura delle Web App

Il secondo capitolo di Head First - Servlets and JSP edito dalla O'Reilly, é dedicato ad una introduzione ad alto livello sull'architettura di una web application.

Container

Le servlet non hanno un main(), sono sotto il controllo di un'altra applicazione Java, detta Container. Un esempio di Container é Tomcat.

L'applicazione web server delega al container le richieste che arrivano dal client per una servlet. Il Container si fa carico di passare alla servlet la request e la response HTTP chiamando i metodi della servlet.

Il Container gestisce:
  • il supporto alle comunicazioni tra le servlet e il web server;
  • il ciclo di vita delle servlet;
  • il supporto multithreading, creando un nuovo thread per ogni richiesta per servlet che riceve;
  • la sicurezza dichiarativa, usando un descrittore XML per il deployment;
  • il supporto a JSP.
Come il Container gestisce una request:
  • crea un oggetto HttpServlerResponse e un HttpServlerRequest;
  • in base alla URL passata determina la servlet richiesta, crea o alloca un thread e passa gli oggetti creati al passo precedente al thread della servlet;
  • chiama il metodo service() della servlet che, a seconda del tipo della richiesta, invoca doGet() o doPost();
  • il metodo della servlet genera la pagina dinamica nell'oggetto response;
  • il Container riprende il controllo, termina il thread, converte l'oggetto response in una response HTTP e la manda indietro al client, infine distrugge gli oggetti request e response.
I tre nomi di una servlet

Una servlet é identificata da un URL, un nome interno, e un nome reale:
  • dal punto di vista del programmatore Java, una servlet é una classe e avrà quindi un path name determinato dal nome del package in cui si trova e dal nome della classe stessa (classes/registration/SignupServlet.class);
  • a livello di progettazione del sistema, alla servlet può essere associato un nome diverso, slegato dai dettagli implementativi (EnrollServlet);
  • per l'utente la servlet ha un URL (register/registerMe).
Lo scopo di questa organizzazione é avere una migliore flessibilità e sicurezza.

Il mapping tra questi nomi é fatto in un documento XML, detto DD (Deployment Descriptor), che contiene un elemento servlet e un servlet-mapping per ogni servlet definita nella web application.

MVC: Model View Controller

Il principio di MVC sta nella separazione della business logic dalla presentazione, mettendoci qualcosa in mezzo, in modo da garantire l'indipendenza delle parti. In questo modo le applicazioni sono più semplici da gestire e si tende a favorire il riutilizzo delle classi.
  • Model: gestisce lo stato dell'applicazione e la sua logica business, é l'unica parte del sistema che parla con il database. Implementato con classi Java normali.
  • Controller: prende l'input dalla request e lo valuta in funzione del Model a cui dice di cambiare in funzione dell'input; rende disponibile il nuovo stato alla View. Implementato dalla servlet.
  • View: responsabile della presentazione, prende l'input e lo gira al Controller, mostra l'output all'user. Implementato via JSP.
J2EE

Un Application Server J2EE include un Web Container (per servlet e JSP) e un EJB Container (per Enterprise JavaBean). In una architettura J2EE "completa" sono gli EJB a farsi carico di implementare la business logic.

Tomcat é un Web Container, non gestisce EJB.

STL - stack

Dal quarto capitolo di Designing Components with the C++ STL, di Ulrich Breymann.

Lo stack é un container che permette inserimenti, letture, eliminazione a solo ad una estremità. Come tipo implicito é possibile usare ogni container sequenziale che supporti back(), push_back() e pop_back().

L'implementazione standard usa deque.

I metodi implementati sono:
  • bool empty() const: true se non ci sono elementi;
  • size_type size() const: numero di elementi;
  • value_type& top(), const value_type& top() const: l'elemento in cima;
  • void push(const value_type& x): aggiunge un elemento in cima;
  • void pop(): rimuove l'elemento in cima.
Un piccolo esempio d'uso:
#include<iostream>
#include<stack>

using namespace std;

int main() {
stack<int> aStack;

if(aStack.empty()) {
cout << "Empty stack created." << endl;
}

cout << "size is " << aStack.size() << endl;

aStack.push(5);
aStack.push(7);
cout << "size is " << aStack.size() << endl;
cout << "top is " << aStack.top() << endl;

aStack.pop();
cout << "size is " << aStack.size() << endl;
cout << "top is " << aStack.top() << endl;

aStack.pop();
cout << "size is " << aStack.size() << endl;
cout << "top is " << aStack.top() << endl;
}