MokaByte Numero  36  - Dicembre 99

 
 
 
 
 
 
 
di 
D. Micucci
A. Trentini
"Htmlizzazione"
di oggetti Java
Usare HTML per visualizzare lo stato degli oggetti (istanze)


Una delle cose più importanti da capire quando si affronta per la prima volta un linguaggio ad oggetti è com'è fatto un oggetto: cosa rappresenta, come viene allocato in memoria e quali sono le sue relazioni con altri oggetti. E' anche importante capire la struttura di un oggetto in termini di ereditarietà, poiché gli attributi ereditati non si vedono nel sorgente di una classe, ma bisogna ricordarsi che esistono. Generalmente chi insegna linguaggi a oggetti si aiuta disegnando i cosiddetti "cerchiogrammi" (vedere figura sottostante) per rappresentare la costruzione per somme di attributi di un oggetto. Questo articolo vuol mostrare come si possa usare il linguaggio HTML in maniera efficiente ed economica (non c'è bisogno di comprare costosi debugger grafici) per rappresentare graficamente un oggetto, usando link ipertestuali per i riferimenti ad altri oggetti e mantenendo l'informazione di derivazione degli attributi (ereditarietà).

Scopo
Benchè inizialmente questo package sia stato ideato per fare debugging di software scritto in Java, la sua utilità si è subito dimostrata anche nell'insegnamento del linguaggio stesso, infatti col package htmlstream (tale è il nome ;-) è possibile generare pagine html che rappresentano istanze di oggetti Java, come potete vedere da questo esempio. In tal modo si possono generare automaticamente rappresentazioni (in HTML) molto simili, per informazioni contenute, ai "cerchiogrammi" nominati poco sopra.circle.gif (5401 byte)

Dato il suo uso previsto (l'insegnamento), tra le nostre specifiche ci eravamo posti quella di un uso molto semplice da parte dell'utente/sviluppatore. Infatti, per usare il package è sufficiente includerlo nel proprio CLASSPATH e creare un oggetto particolare (HtmlStream) a cui si forniscono le istanze di oggetti da convertire in html, allo stesso modo con cui si serializzano oggetti con un ObjectStream. L'oggetto HtmlStream è capace di seguire i reference da solo, ha bisogno solo di un oggetto "radice" da cui partire.
La scelta del linguaggio HTML (avremmo potuto usare XML) non è stata fatta a cuor leggero, i motivi sono:

     
  • al giorno d'oggi, per fortuna o per sfortuna, gli studenti hanno molta familiarità con i browser web e htmlstream fa da ponte fra i linguaggi ad oggetti e la metafora del "clicca e segui il link" perciò pensiamo che così potranno cogliere i concetti basilari della programmazione a oggetti meglio di quando parliamo in aula solamente
  • XML è un linguaggio ancora poco supportato nei browser...
Uso
Come detto più sopra, il package è molto semplice da usare, per "stampare" un oggetto attraverso htmlstream bisogna:
  • Avere l'oggetto da stampare, vedremo che tale oggetto deve rispettare certi vincoli;
  • Creare un'istanza di HtmlStream;
  • Passare il reference dell'oggetto da stampare all'HtmlStream (metodo add);
  • Stampare l'HtmlStream, nel modo java standard (System.out.println) e quindi redirigere l'output dell'applicazione, o usare il metodo print di HtmlStream che crea una finestra (JFrame) e visualizza il sorgente html.
Il sorgente completo (anche degli esempi) lo trovate qui. Per ottenere l'output d'esempio lanciate:
java MyClass >out.html
e poi visualizzate il file out.html aprendolo con un browser (è html molto basilare, va bene qualunque browser che supporti le tabelle).
 
 

Un esempio di codice
Anche se htmlstream è sviluppato in Java, è realizzato in modo da poter essere portato in altri linguaggi object-oriented... per il momento ci limiteremo a Java ;-)
 

// NOTA: l'oggetto deve implementare HtmlizableI
MyClass toBePrinted;
toBePrinted = new MyClass();

// questa è la "stampante"
HtmlStream hStream;
hStream = new HtmlStream(toBePrinted);

// stampo la rappresentazione html
System.out.println(hStream);

Il perché si debbano seguire questi passi verrà accennato nella sezione "Implementazione". In ogni caso l'utente dovrà sottostare ad un unico vincolo (non eccessivamente limitante): quello di poter stampare solo oggetti "stampabili". In questo ambiente di "htmlizzazione" un oggetto è "stampabile" se implementa l'interfaccia HtmlizableI, questo perché l'HtmlStream può lavorare solo con oggetti HtmlizableI.
Un oggetto HtmlStream può essere accresciuto a posteriori, si possono cioè aggiungere (si potrebbe dire "accodare") altri oggetti successivamente:
// assumete di avere ancora hStream nello scope
// aggiungo un nuovo oggetto allo stream
anotherToBePrinted = new AnotherHtmlizable();
hStream.grow(anotherToBePrinted);

// aggiungo un nuovo oggetto allo stream
hStream.grow(new AgainAnotherHtmlizableI());

// stampo la rappresentazione html
// (questa volta include i nuovi oggetti aggiunti)
System.out.println(hStream);

L'esempio precedente è la ragione dell'esistenza della classe HtmlStream, serve quando si vogliono stampare più oggetti in tempi diversi. L'uso è molto simile a quello della classe ObjectOutputStream, in effetti HtmlStream lavora concettualmente nello stesso modo. Un aiuto cognitivo nel comprendere HtmlStream è proprio la metafora della "coda di stampa" dove la fase di accodamento è separata da quella di stampa effettiva.
Stampando a video (es. con System.out.println) un HtmlStream, otterrete del testo html, cosicchè redirigendo l'output del programma su un file otterrete qualcosa di simile a questa schermata (visualizzata attraverso un browser):
 
 

In questa figura vedete un'istanza di classe MyClass "stampata" come pagina html, ogni attributo di tipo "reference" (puntatore) viene rappresentato come collegamento ipertestuale che, se "cliccato" (cioè seguìto), vi porta al punto in cui si trova la "htmlizzazione" dell'oggetto puntato (viene realizzato attraverso ancore html interne alla pagina).

 

Formato
Abbiamo usato il tag html <TABLE> per rappresentare l'istanza di un oggetto, mettendo una "tabella nella tabella" per mostrare le parti ereditate (ricordate i "cerchiogrammi"?). Il formato corrente è come segue: 

 
<hashcode dell'oggetto> <classe> 
<nome attributo>  <valore> 
<nome attributo>  <valore> 
<nome attributo>  <se l'attributo è un puntatore, allora questo è un LINK ipertestuale> 
   
<se questa classe ne estende un'altra qui viene inserita un'altra tabella (ricorsivamente) che rappresenta la classe padre> 
Questa rappresentazione dovrebbe essere abbastanza familiare a chi è abituato ai simboli usati in UML, anche se in questo caso ogni istanza si porta dietro tutti gli attributi (cioè compresi quelli ereditati) invece di avere una rappresentazione esterna (freccia che punta alla classe estesa). L'esempio riportato si riferisce a istanze create a partire dal seguente (parziale) diagramma delle classi UML:

A runtime tale schema genererà oggetti rappresentati col meccanismo delle "TABLE in TABLE" tipo questo:


 
 
 
 
 
 
 
 

Come rendere gli ogetti "htmlizzabili"? 
Come già detto in precedenza, è necessario che un oggetto sia "htmlizzabile" per poter essere stampato da HtmlStream. Per rendere "htmlizzabile" un oggetto si hanno essenzialmente due possibilità:

     
  • L'oggetto (la sua classe) implementa semplicemente l'interfaccia HtmlizableI. In questo modo è il programmatore a dover implementare i metodi public String toHtml(), public HtmlStream grow(HtmlStream hs) e public String toPureString(). In questo modo, sebbene più complicato, almeno viene risparmiata l'ereditarietà singola di java per qualche altra funzionalità. 
  • L'oggetto eredita (discende) da DefaultHtmlizable (una classe fornita insieme al package). In questo modo il vostro oggetto è automaticamente "htmlizzabile" senza alcuno sforzo implementativo, però non potete estendere (almeno in java, data l'ereditarietà singola) altre classi.
Un problema di questo meccanismo è che gli oggetti già disponibili nelle librerie del JDK non sono di per sé "htmlizzabili". Per stampare in html un oggetto di libreria bisogna costruire dei wrapper che li rendano HtmlizableI. 
 
 

Implementazione 
Nella realizzazione di questo "htmlizzatore" ci si è ispirati al meccanismo di serializzazione degli oggetti già presente in Java. Per rappresentare correttamente un'istanza bisogna essenzialmente prendere in considerazione tre aspetti: ereditarietà, attributi e link (puntatori ad altri oggetti). L'ereditarietà viene rappresentata disegnando una TABLE dentro l'altra, gli attributi vengono rappresentati completi del loro valore e i puntatori ad altri oggetti (ancora attributi, ma un po' speciali) diventano collegamenti ipertestuali.

Il problema principale che va affrontato durante la conversione in html è quando si ha a che fare con un oggetto che punta ad un altro oggetto che a loro volta punta al primo (ad es. una lista circolare). Queste sono situazioni molto frequenti nei programmi, tanto è vero che in genere si parla di grafi di oggetti, non di alberi. Se il processo di conversione viene fatto in automatico, intendendo con ciò dire che ogni reference viene seguito automaticamente, è necessario implementare un meccanismo per evitare cicli infiniti.
Il problema dei cicli infiniti è il motivo implementativo dell'esistenza di HtmlStream. L'HtmlStream è un contenitore che può venire accresciuto aggiungendo oggetti HtmlizableI attraverso un'azione (grow) che ha successo (cioè accetta l'oggetto passato) solo se è la prima volta che lo si aggiunge. In tal modo ogni oggetto può venire inserito una volta sola, interrompendo eventuali cicli infiniti.
Un altro problema che va affrontato è quello della dereferenziazione automatica dei puntatori. Volendo stampare alcuni oggetti (un grafo) si vorrebbe evitare di dover passare il puntatore di ogni singolo oggetto da stampare allo stream. Sarebbe opportuno invece poter passare soltanto un nodo (che per noi sarà in quel momento la "radice" del grafo) e fare in modo che lo stream facesse il resto del lavoro. Chi fa questo lavoro è la classe DefaultHtmlizable: le istanze di questa classe (e quindi le figlie, ovviamente) sanno elencare tutti i propri attributi e sanno quali di essi sono puntatori (attraverso il meccanismo della "riflessione") per cui possono seguirli automaticamente. Implementativamente, lo pseudo codice per l'algoritmo di accrescimento dello stream risulta: 

for every attribute of this instance
{
        if attribute is NOT primitive
        {
                if attribute is HtmlizableI
                {
                        stream.grow(attribute)
                }
                else
                {
                        wrap it, then grow stream
                }
        }
}
In realtà questa sequenza viene reiterata ad ogni livello gerarchico, dalla classe più bassa e risalendo fino a DefaultHtmlizable stessa. In questo modo si ottiene la linearizzazione iterativa del grafo di oggetti.
 
 

Concludendo...
Un meccanismo del genere è utile, sia per l'insegnamento che per l'uso quotidiano (in fondo l'html è un buon modo di rappresentare in toto un'istanza). La limitazione di dover implementare HtmlizableI per poter stampare un oggetto è però abbastanza pesante per l'uso normale, sarebbe opportuno avere un wrapper generico (ad esempio basato sul protocollo di serializzazione) in modo da poter stampare anche gli oggetti più disparati (es. quelli del JDK).

I risultati di questo lavoro sono stati presentati ad alcune conferenze dedicate all'insegnamento... data la nostra cronica mancanza di tempo, attualmente non riusciamo a sviluppare ulteriormente il package... se qualche lettore è interessato all'argomento lo invitiamo a contattarci per un'eventuale collaborazione ;-)


MokaByte rivista web su Java
MokaByte ricerca nuovi collaboratori
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it