La
configurazione CLDC
L l
mese scorso, nelle pagine di Mokabyte, abbiamo incominciato
ad occuparci di J2ME introducendo i concetti fondamentali
dell'architettura, e i suoi elementi costitutivi: configurazioni
e profili. In questo numero ci occuperemo della configurazione
CLDC (Connected Limited Device Configuration), studiata
appositamente per dispositivi dalle risorse hardware
limitate (capacità computazionale, dotazioni
di memoria, tipologia e dimensioni dei display, connessioni
a reti di TLC); si tratta tipicamente di dispositivi
portatili (molto spesso tascabili) dalle ridotte dimensioni
come telefoni cellulari, smart-phone, PDA (Personal
Digital Assistant), Pager comunicator, ecc., non a caso,
infatti, i profili previsti per questo tipo di configurazione
sono il PDAP (Personal Digital Assistant Profile) e
il MIDP (Mobile Information Device Profile), dedicati
al mondo palmare, alla telefonia cellulare e a dispositivi
wireless in generale. Individuato il target dei dispositivi
alla quale la CDLC mira, vediamo quali sono i requisiti
minimi richiesti. La CLDC può essere utilizzata
in apparati con:
-
almeno 160 Kbyte di memoria: 128 Kbyte di memoria
non volatile (per la macchina virtuale e le librerie
CLDC )
- non
meno di 32 Kbyte di memoria volatile (per il sistema
runtime e l'allocazione degli oggetti Java)
-
processori RISC/CISC a 16-32 bit;
- connettività
a reti di telecomunicazioni, sovente di tipo wireless
con larghezza di banda anche inferiore a 9600 bps;
- un
sistema operativo che gestisca l'hardware sottostante
e possieda una scheduler per mandare in esecuzione
la macchina virtuale. Tale SO può essere, quindi,
anche molto semplice, ed essere privo di funzionalità
avanzate come il multitasking o un file system di
tipo gerarchico (molti dei dispositivi ai quali la
CLDC è indirizzata hanno SO privi del concetto
di file system).
Come
previsto dalle specifiche J2ME, la configurazione CLDC
si occupa di problematiche del tutto generali a prescindere
dal dispositivo sul quale viene utilizzata, lasciando
ai profili la gestione di tutti quegli aspetti "più
strettamente legati alla piattaforma" (intesa come
tipo di dispositivo es. Cellulare o PDA, non come marca
- modello dell'apparato!).
In
particolare la CLDC si occupa di:
-
definire le caratteristiche del linguaggio Java e
della Virtual Machine supportate;
- fornire
un set minimo di librerie di base;
- gestire
gli stream di I/O;
- sicurezza;
- networking;
- internazionalizzazione;
lasciando
ai profili:
- la
gestione del ciclo di vita delle applicazioni (installazione,
lancio, cancellazione);
- l'implementazione
di interfacce utenti;
- la
"cattura" e la gestione degli eventi;
- l'interazione
tra l'utente e l'applicazione;
Vediamo in dettaglio le caratteristiche (e le differenze
rispetto alla Standard Edition) della configurazione
CLDC.
Il package java.lang e le caratteristiche
del linguaggio supportate
Il
package java.lang, contiene il "DNA del linguaggio
Java" quindi la sua presenza nella CLDC non può
certo stupire, ma è del tutto ovvia. Tuttavia
esso presenta alcune importanti differenze rispetto
all'omonimo package della Standard Edition. La quasi
totalità di tali differenze é dovuta alle
caratteristiche dei dispositivi sui quali la CLDC dovrà
funzionare, che avendo risorse limitate, hanno richiesto
una "rivisitazione" dei package della Standard
Edition ed un loro adattamento a questo tipo di piattaforme.
Confrontando i package java.lang della CLDC e della
Standard Edition una delle prime cose che colpisce è
l'assenza nella CLDC delle classi Float e Double che
costituiscono la "rappresentazione oggetto"
dei tipi semplici float e double. La loro mancanza é
dovuta al fatto che la maggior parte dei dispositivi,
ai quali la CLDC è rivolta non ha il supporto
hardware per i floating point e anche una loro "emulazione
software" sarebbe stata troppo costosa in termini
di risorse richieste ragion per cui è stata decisa
la loro eliminazione dal set di base delle librerie
CLDC. In realtà esistono package di terze parti
(es. MathFP) che permettono l'utilizzo di tali tipi
semplici (float e double) estendendo, inoltre, il set
di metodi della classe Math. Quest'ultima, infatti,
è particolarmente "impoverita", proprio
a causa della mancato supporto dei numeri in virgola
mobile e, si limita a fornire metodi per ottenere il
massimo (max) o il minimo (min) tra due numeri (interi
o long) e il valore assoluto (abs).
La CLDC, inoltre, non supporta daemon thread e gruppi
di thread: poiché il multithreading è
comunque garantito è possibile utilizzare gruppi
di thread ma tutte le operazioni su di essi (start,
stop, sleep, ecc.) devono essere eseguite in modo indipendente.
Per ragioni di sicurezza, ma anche a causa delle risorse
richieste (troppo elevate), non è previsto il
supporto del codice nativo (Java Native Interface).
Le librerie native risultano quindi accessibili solo
alla macchina virtuale, questo impedisce alle applicazioni
qualunque tipo di accesso "esterno", garantendo
quindi la loro esecuzione in un ambiente chiuso e protetto
("Sandbox Model"). (In realtà esistono
macchine virtuali di terze parti, che consentono l'utilizzo
del codice nativo, ad esempio J9 di IBM). Sempre per
ragioni legate alla sicurezza e alle restrizioni imposte
dal "Sandbox model" adottato, di cui parleremo
più avanti, la CLDC non prevede la possibilità
di utilizzare caricatori di classi definiti dall'utente.
Il caricamento delle classi è gestito unicamente
dalla virtual machine il cui class loader non può
essere ridefinito a livello di applicazione.
Il ridimensionamento delle "classi di errore"
è un'altra delle caratteristiche della CLDC,
che emerge dal confronto con la Standard Edition. Le
condizioni di errore che la CLDC si limita a rilevare
riguardano mal funzionamenti della macchina virtuale
(VirtualMachineError) o l'impossibilità di allocare
memoria (OutOfMemoryError), nulla di più. Questo
é dovuto al fatto che un'implementazione completa
della casistica di errori che ritroviamo nella Standard
Edition, (come indicato dalle specifiche del linguaggio
java - Java Language Specification), richiederebbe risorse
troppo elevate. Occorre poi tener presente che le condizioni
di errore sono associate ad eventi solitamente irrecuperabili
e, negli apparati embedded e nell'elettronica di consumo
sono altamente legati al tipo di dispositivo, da qui
la decisione di limitare le classi "error"
ai due soli casi che abbiamo citato.
In ultimo (ma non per importanza) va detto che la CLDC
non possiede quelle caratteristiche di "introspezione"
proprie del linguaggio java, che ritroviamo nella Standard
Edition. La CLDC, infatti, non supporta la Java Reflection,
cioè la possibilità data alle applicazioni
Java di "guardarsi dentro", ed ottenere informazioni
sulle proprie classi, su costruttori, su metodi e attributi,
nonché la possibilità di invocare tali
metodi o modificare gli attributi in fase di runtime
senza possedere alcuna informazione in fase di compilazione.
Un'immediata conseguenza di tutto ciò è
la mancanza della serializzazione (non troverete mai
"implements Serializable" in un programma
che utilizza la CLDC) che a sua volta comporta l'impossibilità
di supportare RMI (Remote Method Invocation).
Classi di utilità: java.util
Anche
le classi presenti nel package java.util hanno subito
un notevole ridimensionamento rispetto alle corrispondenti
classi di utilità presenti nell'edizione standard.
Naturalmente, anche questa limitazione, é motivata
dal fatto che nella configurazione CLDC è stato
inserito solo "lo stretto necessario" per
poter mantenere il "core" il più piccolo
possibile e renderlo adatto a dispositivi con pochi
kilobyte di memoria. Ecco quindi che in java.util troviamo
davvero poco: l'interfaccia Enumeration, Hashtable,
Vector e Stack per quel che riguarda le "collezioni
di oggetti", Calendar, Date e TimeZone per la gestione
e la manipolazione di data e ora. La classe Random per
la generazione di numeri pseudocasuali completa il set
delle classi di utilità di cui possiamo disporre.
Tra gli "strumenti" mancanti quelli di cui
si avverte più la mancanza riguardano la manipolazione
e il trattamento delle stringhe (lo StringTokenizer
ad esempio) e un maggior supporto per l'internazionalizzazione:
l'inserimento delle classi Locale, DateFormat e ResourceBoundle
potevano rendere più completo il package senza
peraltro aumentarne eccessivamente le dimensioni.
Gestione degli stream : java.io
L'input
e l'output di Java si basano sul concetto di stream,
cioè un generico flusso di byte "in transito"
da una sorgente verso una destinazione indipendentemente
dal tipo di sorgente, di destinazione e di percorso.
Il package java.io non poteva quindi mancare nella CLDC,
esso costituisce un sottoinsieme dell'omonimo della
Standard Edition. In particolare nella CLDC sono state
eliminate tutte le classi riguardanti le operazioni
su file, questo perché la maggior parte dei dispositivi
che utilizzeranno la CLDC, come già abbiamo detto,
non possiedono un vero e proprio file system (i palmari
palmOS ne sono un esempio, ma anche i cellulari e gli
smart-phone). Le operazioni di I/O da/su file sono comunque
garantite dal "Generic Connection Framework"
di cui parleremo tra breve. Le due classi, InputStreamReader
e OutputStreamWriter, rappresentano l'unico supporto
all'internazionalizzazione di cui la CLDC dispone e
permettono la conversione di sequenze di byte in caratteri
Unicode e vicerversa:
InputStreamReader(InputStream
is, String encoding);
OutputStreamWriter(OutputStream os, String encoding);
La
CLDC supporta di default la codifica ISO-8859_1 come
è possibile vedere "interrogando" le
proprietà di sistema (System.getProperty("microedition.encoding"))
altri tipi di codifica possono, però essere presenti
in implementazioni particolari. Il mancato supporto
per una codifica richiesta lancerà, ovviamente,
una UnsupportedEncodingException.
Il "Generic Connection Framework"
Come
è noto i package java.io e java.net nella Standard
Edition forniscono un insieme completo di strumenti
per la gestione dell'Input - Output sia per quanto riguarda
l'I/O da file sia per quello legato a connessioni di
rete. Sfortunatamente però hanno dimensioni tali
(circa 200 Kbyte) da impedirne "la completa importazione"
nella CLDC, inoltre molte delle funzionalità
che garantiscono sono poco (o per nienete) utili in
dispositivi embedded e nell'elettronica di consumo in
generale. Inoltre molti di questi apparati dispongono
di metodi di comunicazione "inusuali" e molto
specifici (infrarossi, bluetooth), alcuni hanno un vero
e proprio file-system altri hanno particolari "meccanismi"
per il salvataggio persistente dei dati.
Occorreva quindi estrapolare dalla Standard Edition
(java.io, java.net) le funzionalità principali
e adattarle a questo contesto garantendo, al contempo,
un elevato grado di estensibilità e flessibilità
per garantire il supporto delle differenti forme di
comunicazione e di nuovi protocolli.
E' stato quindi pensato quello che viene definito "Generic
Connection Framework", implementato in un insieme
di interfacce che rappresentano vari livelli di astrazione
di metodologie di connessione. La decisione di non implementare
direttamente, a livello di configurazione, i vari protocolli,
ma di lasciare questo compito al livello applicazione
(o profilo), rientra nella logica di un approccio generico
al problema. In questo modo la configurazione fornisce
gli "strumenti generici di base" permettendo
il supporto di molti protocolli e l'utilizzo sui più
disparati dispositivi (ad esempio una connessione di
tipo datagram può avvenire attraverso l'IP ma
anche tramite beaming su porta infrarossi).
Alla radice della gerarchia, l'interfaccia Connection,
rappresenta una generica connessione che può
essere aperta e chiusa attraverso i metodi open() (che
in realtà non è public ma viene richiamato
dal metodo statico open() presente nella - unica - classe
Connector) e close().
InputConnection e OutputConnection rappresentano un
dispositivo dal quale i dati possono essere rispettivamente
letti o scritti, attraverso gli opportuni stream (openInputStream-openDataInputStream
/ openOutputStream-openDataOutputStream), StreamConnection
è la combinazione di Input/Output-Connection
e costituisce normalmente il punto di partenza per le
classi che implementano le interfacce di comunicazione.
La sua estensione ContentConnection dispone di alcuni
metodi (getEncoding, getType, getLength) che consentono
di ottenere informazioni sui dati trasmessi.
Come è possibile vedere nella figura 1 sono presenti
altre due interfacce discendenti direttamente da Connection:
StreamConnectioNotifier e DatagramConnection.
La prima rappresenta una connessione server-side di
tipo socket, il metodo acceptAndOpen() crea una conessione
con il client e ritorna una StreamConnection (questo
tipo di connessione è simile ai server socket
della Standard Edition). DatagramConnection può
essere utilizzata per implementare connessioni di tipo
datagram. I datagram possono essere creati(newDatagram(...)),
inviati (send(Datagram datagram) ) e ricevuti (receive(Datagram
datagram)). Una connessione di tipo datagram può
essere aperta in modalità "server"
oppure "client": nel primo caso il dispositivo
resta nell'attesa di ricevere datagram nel secondo caso
è lui ad iniziare la trasmissione (per aprire
una connessione "datagram-server" occorre
omettere il nome dell'host nella stringa di connessione
di cui parleremo tra breve).
Figura 1 - Gerarchia delle interfaccie del "Generic
Connection Framework".
Qualunque
tipo connessione viene aperta utilizzando il metodo
open() della classe Connector:
Connector.open(string_connection);
dove
string_connection ha il seguente formato:
<protocol>:<address>;<parameters>
protocol
indica il tipo connessione che si intende utilizzare
(es. http, datagram, scrittura su file, ecc.), address
permette di individuare la destinazione (può
essere un indirizzo IP ma anche il nome di un file)
, parameters contiene una serie di informazioni aggiuntive
che possono essere funzionali per un dato tipo di connessione
(ad es. baudrate per comunicazioni su porta seriale).
La figura 2 riporta alcuni esempi di stringhe di connessione,
ribadiamo che nessuna di queste è implementata
a livello di configurazione, utilizzando quindi un profilo
che si basa su CLDC potrete trovare implementate solo
alcune di queste modalità di connessione e/o
altre che non compaiono nell'esempio.
Figura 2 - Esempi di stringhe di connessione
A titolo di esempio riportiamo una porzione di codice
che realizza una connessione di tipo Http (utilizzando
il profilo MIDP - Mobile Information Device Profile
- che implementa il protocollo Http):
......
......
// La stringa di connessione con protocol-address
String connectionString = "http://193.0.0.1/servlets/MyServlet";
// Apre la connessione di tipo Http.
// Può lanciare alcune eccezioni: IllegalArgumentException
// se la stringa di connessione non è valida
// ConnectionNotFoundException nel caso la connessione
non
// possa essere stabilita o il protocollo specificato
non è supportato
// IOException - se si verifica un qualunque errore
di I/O
HttpConnection
http = (HttpConnection) Connector.open(connectionString);
//Apre
gli stream di input/output sui quali è possible
leggere/scrivere
InputStream is = http.openInputStream();
OutputStream os = http.openOutputStream();
...
...
//Chiude
gli stream di I/O e la connessione Http
is.close();
os.close();
http.close();
...
Questa
parte di codice invece stabilisce una connessione su
porta seriale (utilizzando l'attuale profilo per palmari
PalmOS, che si basa sulla configurazione CLDC):
//
Apre una connessione su porta seriale.
// Il numero 0 individua la porta
// l'unica presente sui dispositivi PalmOS
// vengono poi settate la velocità di trasmissione
e i controlli di flusso
StreamConnection serialConnection = (StreamConnection)
Connector.open("
comm:0;
baudrate=57600;
autocts=on;
autorts=on");
//Apre gli stream di input/output sui quali è
possible leggere/scrivere
InputStream is = serialConnection.openInputStream();
OutputStream os = serialConnection.openOutputStream();
//Chiude
gli stream di I/O e la connessione seriale
is.close();
os.close();
serialConnection.close();
Come
è possibile osservare si tratta di un framework
molto potente e versatile, che può essere adattato
alle più diverse esigenze applicative e alle
caratteristiche dei vari dispositivi.
Il
problema della sicurezza e la verifica dei file class
I
meccanismi di sicurezza di Java 2 Standard Edition richiedono
quantitativi di memoria troppo elevati per essere impiegati
nella CLDC. Per poter mantenere comunque alto il livello
di sicurezza offerto, anche su tali dispositivi, sono
state introdotte alcune modifiche e anche qualche limitazione
(alcune delle quali sono già emerse). Ma andiamo
con ordine.
I meccanismi di verifica e di sicurezza della CLDC agiscono
a due livelli distinti:
-
a livello di Virtual Machine
- a
livello applicazione
In
una virtual machine standard il primo punto è
espletato dal sistema runtime di Java che, tramite il
verificatore (dei file class) si preoccupa di stabilire
se ciò che è contenuto all'interno dei
file class é bytecode java valido oppure no.
In particolare il verificatore controlla che il bytecode
non contenga riferimenti a locazioni di memoria non
valide o comunque al di fuori della memoria riservata
dalla macchina virtuale agli oggetti java, che non vengano
effettuate conversioni illegali di tipo, che i parametri
dei metodi siano appropriati e che le istruzioni alle
quali vengono applicati siano correte, che le operazioni
dello stack non producano situazioni di overflow, che
le operazioni con i registri non portino a condizioni
di errore. Tutto ciò doveva essere garantito
e mantenuto anche nella CLDC della J2ME. E così
è stato seppure con alcune modifiche e accorgimenti.
Il processo di verifica standard di java utilizza algoritmi
troppo complessi che richiedono sia ingenti quantitativi
di memoria sia elevate capacità di calcolo (perlomeno
paragonandole a quelle dei dispositivi ai quali CLDC
si rivolge - cellulari, palmari, smart-phone) basti
pensare che il verificatore di una virtual machine stanadard
occupa 50 Kbyte e richiede dai 30 ai 100 Kbyte di memoria
a runtime.
Quantitativi troppo elevati per dispositivi che possono
avere memoria dinamica di 32 Kbyte.
Per superare questo problema è stata introdotta
nella CLDC una fase supplementare: la pre-verifica.
In pratica il processo di verifica avviene in due fasi
distinte:
-
al di fuori del dispositivo (off-device pre-verification)
- nel
dispositivo (in-device verification);
Il
primo passo (pre-verifica) avviene sulla macchina di
sviluppo o sul server dalla quale viene scaricata l'applicazione
e, viene eseguito da un tool di pre-verifica che, si
occupa di inserire nei file class speciali attributi,
chiamati Stack Map, che permettono al sistema runtime
di effettuare la verifica con prestazioni molto superiori
(rispetto al sistema di verifica standard). Inoltre,
la verifica con il meccanismo dello Stack Map riduce
notevolmente la complessità degli algoritmi di
controllo la cui dimensione richiede circa 100 byte
(byte!) di memoria a runtime, questo perché l'inserimento
di tali attributi consente una scansione lineare del
bytecode senza processi "ricorsivi". Una volta
superata la fase di pre-verifica, l'applicazione java
può essere installata sul dispositivo finale,
dove al momento dell'esecuzione (runtime) subirà
come abbiamo detto un secondo controllo (la verifica).
La figura 3 mostra come avviene il processo di verifica.
Come si può notare il pre-verificatore accetta
in input il file class generato dalla compilazione e
produce anch'esso un file class. Quest'ultimo contiene
gli attributi aggiuntivi degli Stack Map, infatti dopo
la pre-verifica le dimensioni dei file class risultano
aumentate di circa il 5 % rispetto alla dimensione originale.
Figura 3 - Il processo di verifica
E'
importante sottolineare che un file class sottoposto
a pre-verifica continua ad essere eseguibile anche in
una macchina virtuale standard, la quale semplicemente
ignora gli attributi dello Stack Map, il bytecode rimane
quindi compatibile con le altre versioni di Java (Standard
ed Enterprise Edition).
Le
operazioni di pre-verifica e verifica di fatto assicurano
la validità di un'applicazione Java cioè
la sua conformità alle regole del linguaggio.
Questo però, non è di per se sufficiente
a garantire un elevato grado sicurezza. Ad esempio la
verifica non si occupa di gestire l'accesso alle risorse
del sistema (file-system, porte di comunicazione, reti,
ecc.). Nella Standard Edition, l'accesso alle risorse
esterne è controllato nella attraverso il concetto
di "security manager", che viene richiamato
ogniqualvolta un'applicazione o il sistema runtime stesso
richiede l'accesso ad una risorsa protetta.
A causa delle risorse che richiede, questo approccio
però risulta poco adatto alla CLDC, è
stato pensato quindi un semplice modello "Sendbox"
che rappresentasse un compromesso tra sicurezza e funzionalità.
Secondo le regole imposte da tale modello ogni applicazione
Java si trova ad essere eseguita in un ambiente chiuso
e ben circoscritto, in particolare:
-
ogni applicazione deve aver super il processo di verifica;
- ogni
applicazione a accesso ad un ben determinato set di
API (quelle previste dalla CLDC, dal profilo utilizzato
e eventuali altri classi);
-
non è prevista la possibilità di ridefinire
caricatori di classe a livello applicazione. L'unico
class loader ammesso è quello standard della
virtual machine;
- il
codice nativo non è accessibile alle applicazioni,
quindi non è ammesso codice che utilizzi metodi
nativi o che in qualche modo acceda a funzioni native
che non rientrano in quelle previste dalla CLDC o
dal profilo utilizzato;
Nei
prossimi numeri, parleremo dei profili PDAP e MIDP che
si basano sulla configurazione CLDC, avremo quindi modo
di vedere l'utilizzo pratico delle librerie di cui abbiamo
parlato in questo articolo. Con l'ausilio di semplici
esempi mostreremo come creare applicazioni reali partendo
dai file java fino ad arrivare all'eseguibile vero e
proprio (in pratica il "percorso" di figura
3).
Appendice:
classi
ed interfacce CLDC
Classi
di sistema
java.lang.Object
java.lang.Class
java.lang.Runtime
java.lang.System
java.lang.Thread
java.lang.Runnable (interfaccia)
java.lang.String
java.lang.StringBuffer
java.lang.Throwable
Classi
di tipi di dati:
java.lang.Boolean
java.lang.Byte
java.lang.Short
java.lang.Integer
java.lang.Long
java.lang.Character
Classi
di utilità (collezioni di oggetti)
java.util.Enumeration
(interfaccia)
java.util.Vector
java.util.Stack
java.util.Hashtable
Classi
di utilità (manipolazione di data e ora)
java.util.Calendar
java.util.Date
java.util.TimeZone
Altre
classi di utilità
java.util.Random
java.util.Math
Classi
di gestione dell'I/O
java.io.InputStream
java.io.OutputStream
java.io.ByteArrayInputStream
java.io.ByteArrayOutputStream
java.io.DataInput (interfaccia)
java.io.DataOutput
(interfaccia)
java.io.DataInputStream
java.io.DataOutputStream
java.io.Reader
java.io.Writer
java.io.InputStreamReader
java.io.OutputStreamReader
java.io.PrintStream
Classi
del Generic Connection Framework
javax.microedition.io.Connection
(interfaccia)
javax.microedition.io.ContentConnection
(interfaccia)
javax.microedition.io.Datagram
(interfaccia)
javax.microedition.io.DatagramConnection
(interfaccia)
javax.microedition.io.InputConnection
(interfaccia)
javax.microedition.io.OutputConnection
(interfaccia)
javax.microedition.io.StreamConnection
(interfaccia)
javax.microedition.io.StreamConnectionNotifier
(interfaccia)
javax.microedition.io.Connector
Classi
relative ad eccezioni ed errori
java.lang.Exception
java.lang.ClassNotFoundException
java.lang.IllegalAccessException
java.lang.InstantiationException
java.lang.InterruptedException
java.lang.RuntimeException
java.lang.ArithmeticException
java.lang.ArrayStoreException
java.lang.ClassCastException
java.lang.IllegalArgumentException
java.lang.IllegalThreadStateException
java.lang.NumberFormatException
java.lang.IllegalMonitorStateException
java.lang.IndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException
java.lang.NullPointerException
java.lang.SecurityException
java.util.EmptyStackException
java.util.NoSuchElementException
java.io.EOFException
java.io.IOException
java.io.InterruptedIOException
java.io.UnsupportedEncodingException
java.io.UTFDataFormatException
javax.microedition.io.ConnectionNotFoundException
java.lang.Error
java.lang.VirtualMachineError
java.lang.OutOfMemoryError
Riferimenti
http://www.embedded.oti.com
IBM J9 VM
http://home.rochester.rr.com/ohommes/MathFP/index.html
MathFP
http://java.sun.com/j2me/docs/zip/cldcapi.zip
Il link per scaricare la documentazione realtiva alle
API CLDC;
http://java.sun.com/aboutJava/communityprocess/final/jsr030/index.html
Contiene le specifiche CLDC;
http://java.sun.com/products/cldc/
Sito dal quale è possibile effettuare il download
della CLDC. Contiene alcuni interessanti link a FAQ,
mailing-list e documentazione varia.
|