|
Java Tutorial - Parte 2 0.1
Un tutorial per esempi
|
La AI di Google™ definisce una Release Candidate come:
Benchè tutte le funzionalità che avevamo previsto nella roadmap iniziale (vedi La lista delle versioni) sono state implementate, in questa versione finale aggiungiamo quegli "accessori" che completano una applicazione.
Nella seguente tabella trovate l'elenco dei files sorgente che andrò a commentare e che fanno parte della nuova versione.
| nome file | package | descrizione |
|---|---|---|
| ConnectionTest10.java | mastermind.net.impl | implementa un eastern egg |
| MasterMindServer10.java | mastermind.game | implementa una backdoor |
| Main10.java | mastermind.app | la classe principale della versione 1.0 |
Il giocatore può modificare diverse proprietà della applicazione attraverso il pannello delle proprietà (vedi Versione 0.7: Il pannello delle proprietà) tuttavia egli si aspetta anche che le modifiche apportate persistano. Il giocatore trova tedioso dover modificare le proprietà ad ogni avvio della applicazione specialmente se le sue preferenze nei parametri di gioco sono diverse da quelle di default.
La classe MMProperties possiede due membri dati che possono essere usati per questo scopo; uno di essi scrive le proprietà su un file su disco mentre l'altro legge le proprietà da un file su disco:
Dobbiamo solo decidere quale nome di file usare e in che momento richiamare i due metodi dal Main.
Ovviamente il nome del file è a piacere del programmatore e quello che ho scelto io per il file delle proprietà è: mastermind.properties: un nome che fa capire al volo il suo contenuto. Ora debbiamo scegliere la cartella in cui posizionarlo. Vi sono sostanzialmente due strade da seguire:
Delle due, la scelta migliore è senza alcun dubbio la seconda. Il runtime di Java mette a disposizione il metodo statico System.getProperties che ritorna un oggetto di classe Properties nel quale vengono elencate una serie di proprietà di sistema come per esempio il sistema operativo che equipaggia la macchina, la sua versione, etc.
Una di queste proprietà è la home directory (la cartella di casa) dello user che ha eseguito la applicazione e si può ottenere con il seguente codice:
Pertanto, è facile ottenere il percorso completo del file delle proprietà per ogni user che gioca concatenando la home del user con il nome del file mastermind.properties. Il nome del file che contiene le proprietà viene determinato nel costruttore della classe MMProperties.
Le proprietà vanno lette dal file allo stratup della applicazione: il luogo più consono per questa operazione è il metodo overrideDefaultProperties che viene richiamato da run subito dopo che la classe MMProperties è stata istanziata ma prima di interpretare la command-line.
Per quanto riguarda la scrittura delle proprietà sul file, il metodo più logico dove eseguirla è il terminate considerato che esso viene sempre richiamato quando la app stà per terminare. Non riporto il codice poichè non servono commenti; potete leggerlo facilmente nel file sorgente Main10.java.
Nella versione 0.4 della applicazione abbiamo introdotto la interfaccia Application (vedi La interfaccia Application) che viene implementata solo dalla classe Main. Oltre a gestire i giocatori, il frame principale e la creazione di nuove partite definiva anche il metodo terminate il quale, come intuibile, termina la applicazione.
In quella versione primitiva, il metodo era piuttosto semplice:
Con il passare delle versioni, il metodo terminate è diventato molto più complesso ed in questa versione RC è chiamato a diversi compiti:
Benchè i moderni sistemi operativi rilasciano tutte le risorse (memoria allocata, flussi e canali aperti) quando un processo termina, è buona abitudine di programmazione procedere alla chiusura dei flussi e dei canali direttamente nel codice. Il metodo terminate viene richiamato da diversi punti della applicazione:
Ma ci è sfuggito un evento; la chiusura della applicazione tramite il bottone di chiusura del frame, quello che sta in alto a destra in ogni frame principale. Ovviamente, non possiamo trascurarlo; il metodo terminate và richiamato sempre quando la applicazione termina.
Per ottenere questo dobbiamo intercettare l'evento WindowClosing che viene accodato nella coda degli eventi quando una applicazione sta per terminare. Per intercettare l'evento dobbiamo:
WindowListener La interfaccia WindowListener ha molti metodi da implementare:
e dovremmo implementarli tutti anche se noi siamo interessati al solo evento windowClosing. Per evitare di scrivere tutti gli altri metodi vuoti ci viene in aiuto la classe WindowAdapter: essa implementa tutti i metodi di WindowListener senza fare alcuna operazione nel metodo implementante. Purtroppo, non possiamo derivare Main10 da WindowAdapter poichè la classe Main10 già è una derivata di Main09 e Java non permette ad una classe di derivare da più di una classe base.
Dobbiamo quindi implementare tutti i metodi della interfaccia WindowListener anche se siamo interessati ad uno solo di essi? Non necessariamente, una soluzione esiste ed è quella di derivare da WindowListener il capostipite della applicazione principale e cioè la classe Main. Quest'ultima non sovrascrive alcuno dei metodi di interfaccia quindi si comporta esattamente come prima e tutte le versioni precedenti si comporteranno esattamente come quando le avevamo scritte.
Con questo accorgimento abbiamo ottenuto che, comunque, la classe Main10 deriva, anche se indirettamente, da WindowAdapter e possiamo pertanto disinteressarci degli eventi WindowEvent che non riguardano la chiusura della applicazione; possiamo implementare il solo metodo di interesse. Dobbiamo anche modificare, nel metodo run, il comportamento di default del bottone "chiusura finestra" del frame:
In questo modo, abbiamo la certezza che, quando la applicazione termina, il metodo terminate viene sempre correttamente eseguito e le risorse allocate saranno tutte liberate.
Un easter egg (= uovo di Pasqua) in informatica è un contenuto, di solito di natura faceta o bizzarra e innocuo, che i progettisti o gli sviluppatori di un prodotto, specialmente software, nascondono nel prodotto stesso (vedi Easter Egg per maggiori informazioni).
La applicazione Mastermind non sarebbe completa senza un eastern egg: normalmente, gli eastern eggs sono nascosti nella applicazione e quindi non andrebbero assolutamente documentati ma questo è un tutorial e quindi non soggiace alle regole non scritte dello sviluppo software.
Il codice che implementa l'eastern egg in Mastermind è contenuto nel file sorgente ConnectionTest10.java ma non lo commenterò (è di facile lettura) tuttaiva, vi svelerò come attivarlo: selezionando un avversario remoto e collegandosi alla porta 2112 (21 dicembre) del server ConnectionTEST, nel messaggio di presentazione inviato dal remoto fittizio si otterrà una citazione del cantautore statunitense Frank Zappa.
Mentre un eastern egg è innocuo, le backdoors (=porte sul retro) sono metodi nascosti che consentono l'accesso a un sistema, una rete o un'applicazione software, aggirando i normali meccanismi di sicurezza. Questo accesso può essere utilizzato sia da utenti autorizzati, ad esempio per scopi di manutenzione, sia da malintenzionati per scopi illeciti.
Essendo un giochino per bambini, non esiste alcun accesso remoto nel Mastermind ma in questa versione finale è stato nascosta una specie di "porta sul retro" che permette ad un giocatore di barare: inviando una guess (ipotesi di soluzione) pari a "000" (se k=3) oppure "0000" (se k=4) il server notificherà al giocatore che ha inviato la sequenza, la solution (sequenza segreta) dell'avversario che sarà visualizzata sulla riga della solution nel pannello delle sequenze (vedi Il pannello delle sequenze).
La backdoor viene elaborata nel metodo del server haveGuess:
Che altro dire sulle backdoors? Beh, possono essere strumenti molto utili per monitorare una applicazione; creando un piccolo server in stile telnet (vedi Un piccolo telnet) è possibile inviare comandi specifici alla applicazione stessa mentre essa è in esecuzione. Considerata la enorme, gigantesca falla nella sicurezza usando un telnet come quello presentato in questo tutorial, è necessario mitigare il rischio di intrusioni indesiderate con un trucchetto che consiste nel verificare, da parte del server telnet, l'indirizzo IP di chi si è collegato e pretendere che sia solo il localhost.
Con questo accorgimento, solo chi ha accesso fisico alla macchina dove la applicazione è in esecuzione si può collegare tramite il piccolo server telnet e se un malintenzionato ha accesso fisico alla macchina il nostro piccolo telnet non costituisce certo un problema: ci sono problemi ben più gravi da risolvere.
Quando la applicazione è pronta per il rilascio è consigliato creare un unico archivio che contiene tutto il necessario per la sua esecuzione. Questo archivio deve sempre includere almeno:
.class che contengono il bytecode Benchè sia possibile usare strumenti come 7-Zip per creare un archivio, nel caso di una applicazione Java è preferibile usare lo strumento specifico: il Java Archiver (JAR) che crea un unico file contenente la applicazioe e che, al conreario del file compresso generato da un semplice compressore, può essere eseguito dalla JVM. Mi riferirò a questo tipo di file col nome: jarfile. Il JDK mette a disposizione uno strumento CLI per creare i jarfiles; per verificarne la disponibilità sul vostro sistema, digitate:
jar --version
Dovreste ottenere la versione installata sul vostro computer. Cosa inseriamo nel jarfile oltre a quanto elencato sopra? Normalmente, le applicazioni Java contengono solo i files compilati, cioà i files .class ma, se la applicazione è open source tanto vale inserire i sorgenti nel jarfile stesso.
In pratica, possiamo inserire nel jarfile tutto ciò che si trova nella cartella mastermind e in tutte le sue sottocartelle. Anche la cartella docfiles dovrebbe essere inserita nel jarfile poichè essa contiene i files sorgente per produrre la documentazione javadoc delle diverse versioni della applicazione. Non dovrebbe, invece, essere inserita nel jarfile la cartella docs che contiene la documentazione tecnica delle classi: il contenuto della cartella non è interessante per l'user (cioè per il giocatore) e, comunque, può essere generato dal comando javadoc.
In generale, per creare un jarfile si usa il comando in questo modo:
jar cvfe jar-file entry-point input-file(s)
dove cvfe sono opzioni il cui significato è il seguente:
--create ed è usato per creare un jarfile --verbose ed è usato per avere un output verboso delle operazioni eseguite dal comando jar --file ed è usato per creare il jatfile il cui nome è specificato nell'argomento jar-file; in assenza di questa opzione, l'output del comando jar viene diretto sullo standard output (il terminale) --main-class ed è usato per specificare il entry-point della applicazione cioè la classe che contiene il metodo statico main Quindi il comando per creare il jar-file di mastermind è il seguente:
>jar cvfe mastermind.jar mastermind.app.Main mastermind/ docfiles/
Per eseguire la applicazione impacchettata nel jarfile si usa sempre il comando java ma con una opzione specifica:
>java -jar mastermind.jar
il comando può essere seguito dagli argomenti della command-line che contiene le coppie "chiave=valore" come, peraltro già si poteva fare in precedenza. Per esempio:
>java -jar mastermind.jar version=2048 opponentType=AI
La documentazione completa delle features descritte in questo capitolo può essere visualizzata clikkando il seguente link che riporta alla documentazione Javadoc del progetto: The MasterMind Project Version 1.0
Il giochino è completo ma, come riportato in Introduzione una release candidate non è del tutto finita. Essa non è destinata al grande pubblico ma di solito ad una cerchia ristretta di amici e clienti per un test esaustivo che potrebbe perdurare anche parecchio tempo.
In questo contesto le filosofie di rilascio sono contrastanti. Mentre il software commerciale segue la regola descritta poc'anzi, il software libero, (inteso come open source non come "gratis") usa un approccio completamente diverso e segue la filosofia che in inglese viene chiamata release soon, release often che significa "rilascia presto, rilascia spesso".
Alla base di questo approccio vi è la convinzione che se il software è rilasciato subito al grande pubblico vi sono molti più occhi che possono testare la applicazione e scovare i bugs. Non appena trovato un bug lo sviluppatore lo corregge e rilascia immediatamente una versione bug-fix della applicazione: ecco perchè i rilasci si susseguono molto spesso nel software libero.
Premetto che non sono previste versioni successive alla 1.0 salvo, ovviamente, le versioni bug-fix se dovessero rendersi necessarie. Tuttavia, proprio mentre ero in procinto di pubblicare questa mia opera, ho avuto una idea per aggiungere una nuova funzionalità alla applicazione.
Per facilitare la ricerca dei giocatori in rete viene usato il protocollo UDP che invia ad un client Mastermind le informazioni essenziali al collegamento col server (vedi Version 0.9: la ricerca dei giocatori in rete). Le informazioni inviate nel payload UDP sono sostanzialmente due: l'indirizzo IP (o hostname) del server e la porta TCP/IP a cui collegarsi.
Come informazione aggiuntiva viene inviato anche il nickname del giocatore che funge da server. Si potrebbe aggiungere al payload UDP altre due informazioni:
Non ho alcuna previsione di implementare questa nuova funzionalità nella app presentata in questo tutorial: lascio questo compito al lettore.
Argomento precedente - Argomento successivo - Indice Generale