Introduzione
Uno dei problemi che mi sono trovato ad affrontare nell'apprendere i primi rudimenti di Java, e' stato quello relativo alla sperimentazione di piccole porzioni di codice che mi permettessero di capire come funziona il giocattolo.
E allora? Beh, innanzi tutto incorporavo il testo da sperimentare in una classe con un metodo main o in un applet, poi passavo alla compilazione, procedevo alla correzione degli errori e quindi lanciavo la classe -o l'applet- e ne verificavo il funzionamento.
Si intende che tutte queste operazioni andavano ripetute ogni volta che si introduceva la sia pur minima variazione del codice da sperimentare.
Un giorno, un mio amico javista mi mostra come funziona lo scrapbook di VisualAge: un missile! Si scrive il codice, si seleziona il testo da sperimentare e si clicca su un'icona "run"... il risultato viene istantaneamente visualizzato su una apposita finestra di output.
La velocita' di apprendimento in questo modo si alza vertiginosamente specialmente se si possiede un "onesto" portatile Pentium 100 come ho io...
Problemino: VisuaAge costa parecchio e soprattutto vuole almeno 32 MB di memoria.
Al momento, l'unico ambiente di sviluppo che poteva sopportare la mia macchinetta e' il J++ 2 di Microsoft -che per quanto ne so non implementa questa graziosa funzione-; l'idea e' stata dunque quella di realizzare una specie di scrapbook in Java.
L'applicazione che ne e' uscita e' molto artigianale, ma forse qualcuno la trovera' utile.
Nella versione attuale, lo scrapbook funziona come applicazione e per di piu' con interfaccia carattere; al momento infatti, per me l'uso del package java.awt e' roba da marziani.
Lo scrapbook
Bene, la costruzione dello scrapbook e' iniziata con la definizione di un testo "template" sul quale costruire la classe nella quale verra' poi inserito il codice java da sperimentare: esso e' definito nel file Scrapbook.txt.
import moka.\*;
import java.io.\*;
import java.util.\*;
import java.lang.reflect.\*;
public class Temp implements Interfaccia{
public void esegui(){
\\\*
}
}
I packages importati sono:
- moka.\*: esso contiene due classi Util e UtilIO che permettono di manipolare testi e file IO. Sicuramente ne esistono in giro di migliori... ma al momento, non avendo niente di meglio, uso queste.
- java.io.\*,util e lang.reflect servono se si pensa di utilizzare nel codice da sperimentare (CS), classi di quei packages. E' chiaro che si possono aggiungere e togliere i packages che si desiderano.
La class Temp implementa l'interfaccia Interfaccia; cio' si rendera' necessario in seguito quando si dovra' eseguire il CS. L'interfaccia definisce solo il metodo
public void esegui()
La porzione di testo \\\* e' per finire quella che verra' sostituita dal CS. Ripeto, si tratta di una soluzione artigianale; qualsiasi altro sistema potrebbe andar bene: l'importante e' riuscire a compilare la class Temp ed ottenere il file Temp.class.
Passo successivo: scrittura del CS. Qualsiasi editor va bene, l'importante e' che si scrivano correttamente delle isruzioni java; come e' stato detto queste istruzioni verranno poi sostituite agli asterischi nel corpo del metodo esegui della classe Temp.
Compilazione della classe Temp: questo problema e' stato risolto in modo assai insoddisfacente. Finora infatti non ho trovato niente di meglio se non lanciare un processo che compilasse la classe Temp per mezzo di un compilatore definito (nel mio caso jvc della Microsoft)
Il metodo usato e' esegueExe della classe Util.
public void esegueExe(String fe,boolean spetta){
Runtime r = Runtime.getRuntime();
try{
Process p = r.exec(fe);
if (spetta){
p.waitFor();
}
}
catch (Exception e){
stln("errore runtime");
}
}
L'introduzione della variabile boolean spetta si e' resa necessaria poiche' mi sono accorto che il processo si concludeva correttamente solo in modalita' debug: cio' era dovuto al fatto che in questo modo la compilazione si concludeva prima che fosse eseguita l'istruzione successiva, cio' che spesso non accadeva nella modalita' "run".
Una volta compilato la classe Temp, si dovra' procedere alla sua istanziazione; cio' si ottiene per mezzo della classe CL che e' naturlamente una subclass di ClassLoader; essa ha 2 soli metodi:
public synchronized Class loadClass(byte d\[\])
public synchronized Class loadClass(String st1, boolean b)
Il primo richiede un array di byte corrispondente al file Temp.class; tale array viene prodotto per mezzo di un metodo della class Util che verra' descritto piu' avanti.
Il secondo utilizza una variabile boolean b solo allo scopo di sovrascrivere il metodo corrispondente di ClassLoader.
{
Class result;
try {
result = findSystemClass(st1);
return result;
}
catch (ClassNotFoundException e){
Util u = new Util();
u.stln("Non e' una classe di sistema.");
return null;
}
}
Dopo aver definito la variabile Class result, si verifica se la classe specificata nella variabile di tipo String st1, e' gia' stata caricata fra le classi di sistema. Questo metodo verra' lanciato quando si istanziera' l'oggetto Object o descritto nella class ScrapBook.
Un po' più nel dettaglio
L'uso del ClassLoader e' stato naturalmente indispensabile; pur con qualche dubbio che esistano soluzioni alternative, l'unico modo che ho trovato per ricaricare una classe gia' caricata, e' stato quello di usare il metodo loadClass(byte xx\[\]); se si cerca infatti di ricaricare una classe Temp per mezzo di un metodo loadClass (String Temp) dopo aver modificato il file Temp.class corrispondente, -e questa e' l'operazione che sta alla base dello Scrapbook- si otterra' sempre il medesimo output: quello relativo alla prima classe Temp caricata.
Questo perche' il classLoader tiene nota delle classi di sistema in una variabile privata Hashtable che non essendo modificabile, fara' si che se esiste un file Temp.class in una delle directory definite nel classpath, essa sara' caricata solo la prima volta che si tentera' di farlo.
L'uso del metodo loadClass(byte xx\[\]) permette di aggirare l'ostacolo; bisogna cosi':
- trasformare il file Temp.class in un array di byte e
- cancellare il file Temp.class dato come si e' detto, la sua persistenza vanificherebbe il reload della classe Temp.
Entrambe le operazioni sono eseguite nella classe ScrapBook.
La procedura di istanziazione dunque risultera' (vedi il metodo lancia della classe ScrapBook):
CL cl = new CL();
Object o;
try{
Class c=cl.loadClass(f_a);
try{
try{
o=c.newInstance();
((Interfaccia) o).esegui();
}
catch (IllegalAccessException cv) {
u.stln("Accesso illegale");
}
}
catch (InstantiationException n) {
u.stln("errore di istanziazione");
}
}
catch (ClassNotFoundException e){
u.stln("classe non trovata");
}
Il cuore dello Scrapbook e' chiaramente la riga:
((Interfaccia) o).esegui();
essa infatti permette di eseguire tutte le operazioni definite nel metodo esegui() della classe Temp.
In realta', credo che non siano necessarie tutte le operazioni di catch poiche' non si dovrebbero mai verificare i problemi descritti nelle loro clausole.
La classe ScrapBook governa lo sviluppo dell'applicazione: essa comprende i metodi:
- public ScrapBook (costruttore): cancella il file Temp.timecomp se esiste; tale file serve a registrare il momento dell'ultima compilazione della classe Temp;
- public void notepad(): lancia il notepad di Windows; esso serve per modificare il file metx.txt che poi verra' incorporato nel file Temp.java. Siccome questa operazione potrebbe non essere priva di errori, preferisco modificare direttamente il file Temp.class;
- public void lancia(): esegue la classe Temp: prima di procedere viene lanciato il metodo compila();
- public void compila(): compila il file Temp; prima di procedere, viene verificato se il file Temp.class esiste e -se non c'e'- se e' stato compilata la classe Temp dopo la sua compilazione; si ricordi che il file Temp.class deve essere rimosso e che la "data" della compilazione e' immagazzinato nel file Temp.timecomp;
- public void crea(): crea il file Temp.java a partire da un file ScrapBook.txt e metx.txt.
Per finire, esiste la classe C che ha il metodo main() che lancia la classe ScrapBook.
Il programmino permette di risparmiare un bel po' di tempo nella sperimentazione di porzioni di codice: al momento, mi piacerebbe risolvere alcuni problemi:
- la compilazione per mezzo del metodo compila() di ScrapBook non e' in grado finora di "catturare" l'output per cui non si puo' capire che tipo di errori si verificano nel corso della compilazione; l'unica cosa che viene segnalata e' la mancata compilazione;
- \[spero che il problema sia banale e non serva questo punto\]l'uso nella variabile opt di una stringa che specifica l'attuale directory di lavoro. Molto utile sarebbe poterlo desumere da qualche operazione
- l'uso del metodo loadClass(byte xx\[\]) forse non e' immune da complicazioni. Al proposito segnalo un problema che mi e' capitato con l'uso di alcune classi... si tratta di un problema molto strano che saro' lieto di sottoporre a chi lo desiderasse.
- l'uso dell'interfaccia carattere e' senz'altro provvisorio: sarebbe interessante riproporre l'applicazione con l'uso di del package java.awt
- sara' utile l'uso del garbage collector per eliminare l'oggetto Object o del metodo lancia()?
Conclusione
Per il momento mi fermo qui, continuerò nella mia ricerca: se qualcuno, avesse trovato interessante il lavoro da me fatto, e proseguendo, sullo spunto delle idee da me proposte, dovesse trovare soluzioni o idee più interessanti, sarò ben lieto di accogliere critiche, suggerimenti o quant'altro.
Ovviamente ricordo che tale lavoro rappresenta più una sperimentazione che una effettiva produzione di codice funzionante.
