NABIRIO BASIC PACK
Breve Glossario
Sonopro: sito web per la divulgazione e dibattito su una visione del software come “normale prodotto”
(www.nabirio.com/sonopro)
Some Open: concretizzazione in un set di regole e metodologie di sviluppo della filosofia Sonopro, con linee guida per
sviluppatori e condizioni contrattuali
Some-Open-Standard Public License: licenza d'uso scritta per software con specifiche di qualità e manutenibilità
quali: multi-piattaforma, multi-utente, multi-lingua, multi-tema, assistenza all'uso
Framework: Fornisce l’implementazione di un modello architetturale che definisce una famiglia di applicazioni. E’ lo
scheletro di un'applicazione che può essere personalizzato da uno sviluppatore . E’ un progetto riusabile di tutto o parte
di un sistema, rappresentato da un insieme di classi astratte e dal modo in cui le loro istanze interagiscono . Ereditando
dalle classi astratte si crea una applicazione riusando la maggior parte del codice e personalizzando il comportamento
con l’override.
OBBIETTIVO
Si vuole realizzare un framework/core base, utilizzabile dai programmatori per implementare facilmente sistemi
software:
• multi-piattaforma
• multi-utente
• multi-lingua
• multi-tema
secondo le linee guida del Sonopro e della Some-Open-Standard Public License.
SONOPRO - Software is a normal product
Il software è un bene o artefatto che non segue i normali processi di sviluppo degli altri prodotti fisici: una volta scritto
è riproducibile in infinite copie a costo zero, una caratteristica che nessun altro oggetto possiede. Il software è
attualmente reputato una “proprietà intellettuale” (da poco anche “proprietà industriale”), il creatore può quindi dettarne
le limitazioni di utilizzo scrivendo di proprio pugno una licenza d'uso. Chi acquista il software ed accetta la licenza
d'uso è quindi vincolato all'utilizzo rispettando le regole dettate dallo sviluppatore. Solitamente il rilascio avviene
secondo due modalità: a “codice chiuso” (o “proprietario”), o la modalità “open source” (o “codice aperto”).
Il movimento SONOPRO (acronimo di “SOftware is a NOrmal PROduct”) divulga una linea di pensiero che vede il
software come qualsiasi altro normale prodotto, che deve quindi seguire le normali regole alle quali tutti gli altri
prodotti sono assoggettati. Un software è un bene o strumento che estende e migliora le capacità dell'essere umano, ed
in quanto tale, deve essere progettato, realizzato e mantenuto tenendo conto dell'impatto che il suo impiego può avere
nel tempo.
Secondo questa filosofia, il produttore del software deve:
1.
Garantire che gli strumenti (linguaggi di programmazione, piattaforme di sviluppo, ecc) impiegati per la
progettazione e la realizzazione siano adeguati alla specifica classe di sistema che si deve realizzare
2.
Garantire che le parti del sistema, soggette a prevista manutenzione durante il ciclo di vita, siano studiabili,
aggiornabili, modificabili e ridistribuibili, in modo che venga garantita l'affidabilità, l'aggiornamento e
l'evolvibilità del software durante tutto il suo ciclo di vita
3.
Garantire all'acquirente, la possibilità, di rivolgersi ad un programmatore di fiducia per modifiche,
aggiornamenti ed espansioni, senza previa autorizzazione del produttore
4.
Garantire che gli aggiornamenti e correzioni rilasciati successivamente all'acquisto vengano concessi
gratuitamente
5.
Garantire che esista un servizio di assistenza
I sopracitati cinque punti offrono all'acquirente la garanzia di avere pressoché le stesse libertà avute con l'acquisto di
altre tipologie di prodotto. Una considerazione va però fatta: la difficoltà nella “ridistribuzione di copie” dei normali
prodotti sta proprio nella riproduzione del bene. Nel software questa difficoltà non esiste, e si lascia quindi la decisione
al produttore di concedere la libertà alla copia e ridistribuzione (anche previo compenso).
SOME-OPEN
Il Some-Open è una concretizzazione della filosofia SONOPRO, che disegna un set preciso di caratteristiche per le
categorie di software business e professionali. Some-Open è una modalità di rilascio di tipo semi-aperta.
Il software deve:
•
•
•
•
•
•
essere efficiente, aggiornabile ed espandibile, durante tutto il ciclo di vita concordato con il committente
essere facile da usare anche per i non esperti dell'ambito applicativo, e contenere quindi degli strumenti che
assistano l'utente durante l'utilizzo
prevedere funzionalità per utenti con particolari necessità
visive/motorie
avere un supporto all'internazionalizzazione (o
multilingua)
essere scritto in un linguaggio multi-piattaforma
deve garantire la sicurezza dei dati sensibili
Lo sviluppatore deve:
•
•
•
•
comprendere fino in fondo le richieste e necessità del
cliente, coinvolgendolo durante la fase di sviluppo
garantire la manutenzione e l'estensione del software
rilasciando il codice sorgente e la documentazione,
creando delle interfacce per la modifica e l'ampliamento
delle parti del programma soggette a prevista variazione
durante il ciclo di vita
assicurare l'esistenza di un servizio di assistenza sul
prodotto
offrire gratuitamente gli aggiornamenti e correzioni successivi al rilascio
Il cliente:
•
•
•
deve rendersi disponibile fornendo tutte le delucidazioni allo sviluppatore sia sull'ambito applicativo che sulle
funzionalità richieste
può rivolgersi ad un suo programmatore di fiducia per eventuali aggiornamenti/estensioni del software, senza
interpellare il produttore, purché rispetti eventuali espliciti accordi contrattuali
Non può copiare o cedere o ridistribuire il software senza prima chiedere autorizzazione al produttore, il quale
potrà richiedere un compenso per la concessione
STUDIO DI FATTIBILITA'
Denominazione Software: Nabirio Basic Pack
Tipologia: Framework
Target/Dominio: Developers
Tecnologia: Java 7
Multipiattaforma: Si (Windows/Mac/Gnu-Linux)
Librerie Esterne Utilizzabili: IText, SqLite, MigLayout
Ciclo di vita del Software: [non definita/infinita]
Completamento previsto: Settembre 2015
Definizione del Problema: il programmatore del mercato del software custom si ritrova spesso a dover fronteggiare la
stessa tipologia di problema: realizzare secondo il MVC oggetti le cui proprietà e caratteristiche devono essere
visualizzate attraverso una GUI , rappresentabili in formato di report cartaceo, ed essere resi persistenti. Spesso quindi,
la differenza concreta tra un software custom ed un altro è in realtà l'insieme degli attributi formanti gli oggetti da
creare, visualizzare e memorizzare.
Si vuole realizzare, un framework/core base, utilizzabile dai programmatori per implementare facilmente sistemi
software che prevedano funzionalità quali
• multi-piattaforma
• multi-utente
• multi-lingua
• multi-tema
e dia la possibilità di realizzare applicazioni modulari e facilmente espandibili: una sorta di CMS per applicazioni
desktop.
REQUISITI FUNZIONALI
Il framework:
•
•
•
•
•
•
•
•
•
•
•
•
Deve essere utilizzabile su qualsiasi sistema operativo
Deve avere la capacità di adattarsi a runtime all'ambiente in cui viene eseguito (es. live-usb)
Deve prevedere delle librerie per la gestione automatica delle connessioni ai database
Deve prevedere il supporto all'internazionalizzazione, in particolare deve essere possibile creare ed aggiungere
facilmente nuove traduzioni di sistema
Deve contenere delle funzionalità per la creazione e gestione di utenti con diverse credenziali e livelli di
accesso
Deve contenere delle funzionalità di switching e disconnessione degli utenti (simile ai sistemi operativi)
Deve contenere delle librerie ed oggetti grafici guida, estendibili, per la realizzazione delle gui
Deve contenere un sistema semplice per il caricamento delle immagini a runtime
Deve integrare sistemi di assistenza all'uso
Dovrebbe prevedere un sistema per la generazione di report pdf degli oggetti memorizzati
Dovrebbe riuscire a fare uso dei database in remoto
Dovrebbe prevedere appositi meccanismi per la gestione della concorrenza
REQUISITI UTENTE
L'utente (developer) deve avere a disposizione un ambiente generico semi-realizzato, dal quale con poche estensioni di
classe possa realizzare software personalizzati per il cliente. Si elencano nel dettaglio:
•
Lo sviluppatore deve poter, con poche linee di codice, realizzare oggetti secondo il Model View Controller, che
abbiano meccanismi di visualizzazione e salvataggio automatizzati
•
Deve poter disporre di oggetti di supporto e strumenti che aiutino l'implementazione e la personalizzazione del
sistema da sviluppare
•
Deve avere a disposizione un pannello grafico dal quale lanciare i moduli specializzati nelle funzioni da
realizzare, e che nel contempo dia la possibilità di gestire gli utenti, la personalizzazione del sistema grafico,
della lingua, e delle opzioni base del software
•
Deve disporre di uno strumento per creare le varie traduzioni della lingua di sistema
•
Dovrebbe disporre della documentazione necessaria all'uso
•
Dovrebbe disporre di sufficienti esempi e tutorial d'implementazione
SPECIFICA DI PROGETTO
Dallo studio di fattibilità è emerso che nell'ambito dei software custom, ciò che sostanzialmente differisce è, nella
maggior parte dei casi, il set di campi e proprietà relativi agli oggetti da visualizzare/memorizzare. Prendiamo in
esempio due tipologie di software apparentemente differenti: un software gestionale, ed un software di archiviazione e
riordino file: a parte gli algoritmi relativi all'ambito applicativo (calcolo prezzi e storno iva per il primo, e scansione
disco e generazione checksum per il secondo), quello che poi differisce è l'insieme di field relativi alle classi “Prodotto”
e “File” (barcode piuttosto che checksum, productName piuttosto che fileName). Sulla base di tale esigenza si è scelto
di progettare un “GenericObject”, un tipo di oggetto “compatibile e centrale”, conosciuto sia dalla gui che dalla base di
dati, in modo da agire solo su di esso per la personalizzazione del software, senza modificare la struttura grafica ed i
database che si adegueranno in modo automatico alle modifiche. Segue un incompleto Component Diagram del
sistema:
Il modulo base prevede le classi necessarie all'adattamento del sistema all'ambiente d'esecuzione ed al caricamento delle
librerie necessarie. I vari moduli di personalizzazione del sistema comunicheranno con il sistema di costruzione e
saranno disponibili all'interno del SystemPanel. I vari moduli di personalizzazione conoscono solo il modulo
GenericObject e tramite esso effettuano le operazioni sui database, sulla grafica e sugli altri moduli di sistema.
STRUTTURA PACKAGE
La strutturazione in pacchetti avviene secondo gli standard di modularità ed estendibilità mantenendo il più possibile
separati le componenti le quali interagiranno tra di loro tramite appositi Fàcade. I componenti relativi all'avvio, alla
configurazione, ed alla memorizzazione delle variabili del sistema base verranno inseriti nel package centrale
denominato RUN, insieme a classi di utilità, lingua, e conversione dati. I componenti grafici verranno suddivisi in base
alla funzionalità: semplici, sensibili, avanzati. Un package non contenente file *.class è dedicato ai file di traduzione di
lingua (*.properties). Il pacchetto security conterrà tutte le classi e funzionalità relative all'avvio sicuro del sistema,
crittografia, login e utilizzo sicuro.
Il package Lib conterrà le librerie di supporto (itext per la generazione di file pdf, miglayout per la strutturazione
grafica in finestre, sqlite.jar per l'interfacciamento con base di dati SQLite . Nel package principale viene inserita la
classe Main, la quale verrà invocata dallo starter esterno per inizializzare le variabili base, riconoscere le cartelle di
installazione, ed effettuare l'environmenting automatico al sistema (anche da chiavetta USB).
SCELTE PROGETTUALI ED IMPLEMENTAZIONE
Di Seguito viene effettuata descrizione degli attori del sistema, loro interazioni, e discussione delle scelte progettuali.
SISTEMA STARTER E SISTEMA BASE (RUN)
Lo starter verrà realizzato in un file *.jar esterno, il quale caricherà le librerie contenute nel package Lib, estenderà ed
aggiornerà il ClassPath di sistema ed effettuerà la chiamata al metodo main() della classe Main inviando le relative
cartelle di ambiente o installazione.
Una volta avviato il main ed istanziate le relative classi con variabili statiche verrà richiamata la finestra di Login
secondo le impostazioni di tema e lingua di sistema (che cambieranno una volta effettuato l'accesso in base all'utente).
Ad accesso effettuato verrà lanciato il pannello base del software nel quale verranno presentate all'utente le relative
funzionalità permesse (compreso switching utenti, impostazioni e moduli).
Start nel dettaglio: SystemMemory (Singleton) e DatabaseList
La chiamata alla classe SystemMemory fa partire il set-up dell'ambiente inizializzando il Mediatore di sistema,
caricando il driver per il sistema di Database (SQLite driver), carica la configurazione di sistema (se esiste su disco,
sennò ne crea una base), carica i file relativi al licenziatario, il layout grafico di sistema inizialmente e quello
personalizzato dell'utente successivamente, effettua il link al modulo di servizio per il loading delle immagini, ed avvia
il componente DatabaseList, il quale si occuperà di controllare se la struttura database su disco è corretta, ed a runtime
creerà e caricherà nel sistema eventuali nuovi database aggiunti e loro istanze.
GenericObject
(Prototype – Memento – FlyWeight – Template Method – Marker Interface)
E' l'oggetto base sul quale si fonda la struttura del software: ogni istanza rappresentante un oggetto da trattare nel
sistema è una estensione della classe GenericObject.
GenericObject è una classe astratta, che modella il generico oggetto che verrà poi effettivamente creato nelle classi
concrete eredi. E' formato da:
•
array di stringhe (stringlabels, booleanlabels, intlabels, doublelabels, longlabels) che vengono popolati nelle
classi concrete eredi per realizzare la forma dell'oggetto (Template Method). Questi array di stringhe vengono
resi STATICI per evitare accumulo di dati ridondanti alla creazione di più istanze della stessa classe erede
(principio flyweight)
•
HashMaps sono collegate agli array, e conterranno effettivamente i dati relativi ai labels
•
tre variabili di istanza che servono per specificare: il valore principale dell'oggetto, il valore per l'equals, ed il
valore di comparazione
•
I metodi di aggiunta, rimozione e salvataggio modifiche all'interno del Database avvengono tramite una
chiamata al sistema di Database che riconosce l'oggetto ed invoca il metodo all'interno della tabella corretta.
Ridondanza tra principalid e equalsid? Esiste in realtà la necessità di tenere separati questi due field: prendiamo in
esempio il caso di voler creare l'oggetto Prodotto e di volerlo gestire, visualizzare e memorizzare. Verrebbe spontaneo
settare come principalID il barcode e secondo questo gestire anche il metodo equals (per il compareTo si potrebbe usare
il campo ProductName). Questa scelta progettuale risulterebbe scorretta nel caso in cui si volesse gestire un magazzino
di prodotti resi: in un magazzino di prodotti resi, possono esistere due prodotti con lo stesso barcode ed avere altre
proprietà (come l'aspetto) diversi, quindi, avremmo a che fare con due oggetti diversi, pur avendo lo stesso codice a
barre. Per risolvere il problema si può settare il principalID con un con il barcode, e si setta l'equalsID con un hashcode
complesso (nel nostro sistema si è scelto il millisecondo di creazione dell'oggetto, essendo di fatto ogni oggetto creato
dall'utente e quindi impossibile crearne due nello stesso millisecondo, è comunque facilmente implementabile un'altra
scelta).
Utilizzo del GenericObject
Un oggetto così formato può essere utilizzato senza conoscerne a priori la forma: un pannello grafico ad esempio,
potrebbe ricevere un GenericObject, ed a runtime scansionarne le HashMaps e creare per ogni field un Jlabel ed un
JtextField, rappresentando così graficamente l'oggetto. Allo stesso modo le classi che si occupano della gestione dei
database potrebbero creare tabelle ad hoc costruite in base ai field del GenericObject e memorizzare ed estrarre i dati.
Esempio d'utilizzo: il CustomerPanel
I pannelli grafici ricevono in ingresso una specializzazione di GenericObject, scansionano i valori presenti all'interno
delle liste di attributi dell'oggetto, e creano in base al tipo di dato un oggetto grafico rappresentativo (ad esempio per un
attributo “prezzo” contenente un Double verrà creata in automatico una JFormattedTextField). Ciò permette di
automatizzare la rappresentazione di un GenericObject senza conoscerne a priori il numero di attributi dai quali è
composto.Di seguito è riportata porzione di codice che realizza ed aggiunge al pannello i labels ed i textfields necessari
alla rappresentazione di un oggetto Customer, estensione di GenericObject.
for (String strval : customer.stringlabels) {
Label senstl = new Label(strval);
TextField senstf = new TextField(customer.getStringValue(strval));
valuerappresentationpanel.add(senstl);
valuerappresentationpanel.add(senstf);
if (maskoption == CustomerPanel.VISUALIZE_MASK) {
senstf.setEditable(false);
}stringmemovalue.put(strval, senstf);}
for (String strval : customer.doublelabels) {
Label senstl = new Label(strval);
JFormattedTextField senstf = new
JformattedTextField(customer.getDoubleValue(strval),NumberFormat.getCurrency());
valuerappresentationpanel.add(senstl);
valuerappresentationpanel.add(senstf);
if (maskoption == CustomerPanel.VISUALIZE_MASK) {
senstf.setEditable(false);
}stringmemovalue.put(strval, senstf);
Criticità relative alla rappresentazione grafica del GenericObject
Quando si ha necessità di variare un componente grafico di default è necessario “sporcare” eccessivamente il codice.
Come esempio si prendono attributi d'oggetto quali la marca di un prodotto o il campo note: il primo avrà bisogno di
essere rappresentato attraverso una combo-box che preleva i dati all'interno di un apposito database, mentre il secondo
avrà la necessità di essere rappresentato con una text-area. Per rendere possibili queste specializzazioni sono necessari
appositi controlli durante la scansione degli attributi dell'oggetto. Segue un esempio relativo alla creazione degli oggetti
grafici rappresentanti gli attributi di tipo String di un Product:
for (String strval : product.stringlabels) {
if (strval.equals("Description") | strval.equals("Notes")) {
JLabel senstl = new JLabel(strval);
JTextArea jta = new JTextArea(product.getStringValue(strval));
JScrollPane tapane = new JScrollPane(jta);
jta.setLineWrap(true);
valuerappresentationpanel.add(senstl);
valuerappresentationpanel.add(tapane);
if (maskoption == ProductPanel.VISUALIZE_MASK) {
jta.setEditable(false);}
stringmemovalue.put(strval, jta);
}
else if (strval.equals("ProductBrand")
| strval.equals("Category")
| strval.equals("UnderCategory")) {
JLabel senstl = new JLabel(strval);
JComboBox jcb = new JComboBox(
combodb.searchSecondCol(strval),
transl.getString(strval + "tooltip"), tcont);
jcb.setRenderer(new TextListCellRenderer());
jcb.setSelectedItem(product.getStringValue(strval));
jcb.getEditor().getEditorComponent().addKeyListener(
new ComboKeyListener(jcb, strval, combodb));
if ((maskoption == ProductPanel.VISUALIZE_MASK)) {
jcb.setEnabled(false);}
stringmemovalue.put(strval, (JTextComponent) jcb.getEditor()
.getEditorComponent());
valuerappresentationpanel.add(senstl);
valuerappresentationpanel.add(jcb);
} else {JLabel senstl = new JLabel(strval);
JTextField senstf = new JTextField(product.getStringValue(strval));
valuerappresentationpanel.add(senstl);
valuerappresentationpanel.add(senstf);
if (maskoption == ProductPanel.VISUALIZE_MASK) {
senstf.setEditable(false);}
stringmemovalue.put(strval, senstf);}
}
Il problema è stato risolto ricorrendo ai design pattern Builder ed Abstract Factory nel sistema di costruzione. Si veda
più avanti nella relazione.
DATABASE SYSTEM
Si realizza un sistema che rende possibile l'aggiunta ed utilizzo dei Database a runtime. Lo sviluppatore non deve
assolutamente preoccuparsi di gestire la comunicazione con i Database, ne di realizzare stringhe Sql per
l'interrogazione: tutto viene svolto in automatico.
Per la memorizzazione ed archiviazione dei dati su disco si è scelto di utilizzare SQLite, un database a file singolo di
dimensione massima 3TB: per modularità, avere la possibilità di creare nuove relazioni senza modificare le strutture
dati esistenti, limitare la perdita di dati su disco e facilitare le operazioni di backup è stata effettuata la scelta progettuale
di realizzare un file per ogni tabella del database ed implementare il sistema delle relazioni in database di supporto.
GenericObjectDB:
E' una classe astratta che serve da linea generale per i databases specializzati. Contiene le specifiche per poter
scomporre e memorizzare nella base dati i vari oggetti
eredi di GenericObject.
Un esempio: il CustomerDB
Per realizzare un Database per i clienti è quindi
sufficiente realizzare una classe erede di
GenericObject che chiameremo Customer, contenenti
i campi che vorremo salvare, ed una classe
CustomerDB erede di GenericObectDB, con solo due
metodi:
• il
costruttore
che
invoca
un
super(“nomedb”,”nometabella”)
• un metodo createDB() che invocherà un
super.createDB(new
CustomerGenericObject()).
Il CustomerGenericObject verrà utilizzato dalla classe
GenericObjectDB come Prototype per eseguire tutti
gli altri metodi
(creazione database su disco,
creazione tabella adatta al CustomerGenericObject, metodi di aggiunta, rimozione ed aggiornamento).
KeyValueDB
E' una classe astratta il cui compito è delineare Database formati da due sole colonne. Una sua concretizzazione, il
ComboDB, ne chiarisce le motivazioni di progettazione: poter salvare e rendere disponibili all'occorrenza i valori di
una Combo-Box come ad esempio “Marca”, field di un GenericObject Prodotto.
GenericObjectDBStringCreator
Si occupa di generare le stringhe Sql da inviare al Database. Scansionando il GenericObject dato, la classe compone la
stringa Sql per crearne la tabella, salvare, modificare o eliminare l'oggetto dal Database.
DataBaseList
Estende una HashMap di Java e fornisce i metodi per il caricamento e creazione dei Database. Il costruttore scansiona la
cartella contenente i file *.class relativi ai GenericObjectDB, ne crea ed aggiunge le istanze. Un metodo
dirsAndFilesControlAndLoad() controlla la struttura su disco dei database ed eventualmente crea i file di database
mancanti rispetto all'elenco dei GenericObjectDB.
SISTEMA GRAFICO
Per garantire la semplicità d'uso ogni finestra del sistema sarà provvista di una barra di tooltip nella quale i componenti
grafici stampano il proprio behavior o descrizione per guidare l'utente. Per gli utenti ipovedenti o con particolari
esigenze visive c'è la possibilità di avere un proprio set di colori e font scegliendolo dal profilo utente.
SISTEMA MULTI-THEME
La classe Layout presente all'interno del pacchetto base contiene i metodi per settare i vari temi di sistema su ogni
componente grafico: sono memorizzate infatti per ogni tema vari tipi di font, di colori, di dimensioni.
COMPONENTI GRAFICI (Observer - Mediator)
Sono state progettate ed implementate le estensioni di quasi tutte le classi del framework Swing di Java, per realizzare
dei componenti grafici che perseguissero le specifiche di sistema: ogni componente grafico attinge al Layout di sistema
per autosettare il colore di sfondo, il colore del testo, la dimensione ed il font. Il pacchetto è suddiviso in componenti
“normali” e componenti “sensible”. Una interfaccia “TooltipedContainer” definisce il contratto sui metodi che un
contenitore dotato di visualizzatore di tooltip deve implementare. Così un sensible component come ad esempio il
SensibleRisaltButton potrà invocare il metodo di visualizzazione del proprio tooltip ai vari TooltipedContainer registrat
in esso. Un SensibleButton estende quindi Jbutton ed implementa l'interfaccia SensibleComponent. Il SensibleButton
quindi possiede una lista di Osservatori/Mediatori che notificano ai TooltipedContainer, al passaggio del mouse ad
esempio, di stampare la stringa di tooltip di quel determinato bottone.
Un componente grafico può inoltre implementare l'interfaccia “ModComponent” (si veda più avanti) per agganciare a
se comandi multipli da forwardare all'interno del sistema di comunicazione (si veda più avanti) al verificarsi di un
evento.
WINDOW SYSTEM E MODWINDOW (Template Method – Class Adapter)
Le finestre del sistema vengono suddivise in liste per utente. Ciò è utile per prevenire eventuali chiusure del sistema
mentre si è in “switching utente” (si veda più avanti nel sistema multiutente) e per realizzare il pattern Lazy Factory: il
SystemMemory infatti realizza questo sistema di gestione integrando i pattern singleton, la lazy initialization quando
richiesta la finestra ed appunto memorizzazione in liste delle finestre.
Tutte le finestre del sistema sono delle WindowUser (per implementare appunto il Lazy Factory), ma non tutte le
finestre di sistema hanno la capacità di mostrare il tooltip. Si utilizza quindi un Class Adapter per creare una
TooltipedWindow.
Le ModWindow sono dei Template progettati per rendere estremamente semplice la realizzazione di una finestra di
modulo. Una finestra di modulo è l'entità che raggruppa le funzionalità di modulo all'interno di essa. Serve
essenzialmente per “usare” i GenericObject realizzati. In una finestra di modulo è possibile trattare più tipi di
GenericObject diversi: ad esempio nello stesso modulo è possibile voler gestire il magazzino dei prodotti nuovi e dei
prodotti di seconda mano, è comodo quindi poter trattare i due tipi di prodotti nella stessa finestra di modulo: da qui il
concetto di “Environment di finestra”. Una finestra possiede uno o più Environments. Ogni Environment è
caratterizzato dal puntatore al suo file di traduzioni, una barra degli strumenti, ed una icona.
La finestra di modulo permette lo switching di Environment grazie alle icone di Environment poste automaticamente in
alto a destra della finestra. Nel Diagramma delle classi sopra, il commento a TaxDocumentsModWindow chiarisce
come sia immediato e semplice realizzare una finestra di modulo completa delle funzionalità sopracitate con poche
linee di codice e l'immagine ne mostra il risultato.
IL BASIC PANEL – OPTIONS PANEL
Dopo l'immissione delle credenziali d'accesso, il sistema carica tutti i files necessari e mostra il pannello dal quale è
possibile lanciare le finestre di modulo, gestire le impostazioni di sistema, ed
accedere alle funzionalità di cambia utente/disconnetti. L'OptionsPanel permette
di gestire gli utenti del sistema e loro permessi, modificare i dati relativi alla
licenza , dati per i documenti da stampare, e le impostazioni base di sistema come
la collocazione fisica dei Database, la cartella di installazione, la cartella per le
immagini aggiuntive ecc.
BUILDING SYSTEM (Builder,
Abstract Factory,
Decorator, State)
Per garantire allo sviluppatore una buona flessibilità si è realizzato un sistema di costruzione dei componenti grafici
complessi modulare e che fa uso di due Design Pattern: Builder ed Abstract Factory.
Builder è stato implementato per guidare e semplificare la realizzazione di oggetti complessi come pannelli di
visualizzazione e di ricerca dei GenericObject: un Director contiene i vari part-builders aggiunti in fase di progettazione
e prima di lanciare la costruzione del pannello, ricerca eventuali part-builders aggiunti a runtime (metodo
searchForOtherBuilders() ).
Il BuilderDirector si serve anche di un oggetto di servizio, il RepresentationStrategy, il quale memorizza al suo interno
variabili relative allo stato di esecuzione (se si sta modificando un genericobject o lo si sta ricercando) e può servire
anche come Marker Interface per eventuali controlli sui tipi.
PartBuilder è l'interfaccia che definisce le operazioni che saranno svolte dai Part-Builders ed in particolare il metodo
build().
Abstract Factory è il Design Pattern utilizzato per ovviare alle problematiche relative alla rappresentazione grafica dei
GenericObject. Come precedentemente dimostrato, sostituire un componente grafico di default per un determinato
campo può portare a sporcare eccessivamente il codice. GuiComponentAbstractFactory realizza la costruzione dei
componenti standard associati al tipo di dato del GenericObject, ed in caso servisse una personalizzazione particolare
per un determinato GenericObject basterà estendere GuiComponentsAbstractFactory sovrascrivendo ad esempio il
metodo createButton() e restituendo un componente grafico diverso da quello di default. La personalizzazione può
essere così realizzata a cascata, invocando il metodo della superclasse allorquando la personalizzazione richiesta non sia
specificata in quel determinato factory.
MEDIATION SYSTEM (Mediator, Chain of Responsability, Command,
Visitor)
Il sistema di comunicazione a comandi risulta abbastanza inusuale (o meglio...contorto!) e merita
una trattazione per parti. Si tenga di conto il design pattern Command: solitamente si ha un client
che aggiunge ad un Invocatore il comando creato passandogli l'oggetto ricevente che materialmente
eseguirà il comando. Istintivamente la progettazione avverrebbe quindi creando un command
passandogli una finestra sulla quale invocare un metodo, ad esempio il repaint().
L'implementazione qui proposta è un po diversa: un Mediator tiene una lista di componenti di
modulo “ModComponent”, un Command viene spedito al Mediator, ed il Mediator chiede al
command se interessato ad eseguire sul componente. In caso affermativo il mediatore ordina al
componente, tramite un meccanismo Visitor di registrarsi presso il command inviando se stesso
(violando quindi l'incapsulamento). Una volta registrati tutti i componenti interessati, sarà il
command stesso ad eseguire le modifiche sui target.
ModComponent e ModCommand
ModComponent è l'interfaccia che rappresenta i componenti di modulo: essi hanno la capacità di spedire comandi, di
accettarne e di fornire il mediatore al quale sono assoggettati. Sono dei ModComponent i pulsanti, i pannelli, le
ModWindow ecc. Il metodo acceptCommand() riceve un Command e si registra presso di esso inviando se stesso.
ModCommand è l'interfaccia per i comandi di modulo. Diversamente dalla normale implementazione del design pattern
Command, questo tipo di command non ha bisogno del receiver. Possiede tre meccanismi di controllo sui moduli in
registrazione che possono essere usati a piacimento dallo sviluppatore per suddividere le fasi di registrazione e
controllo: solitamente il primo “boolean isModuleInterested(ModComponent comp)” si occupa di verificare l'istanza
del modulo in arrivo. Il secondo meccanismo di controllo è confirmInterest() che controlla, attraverso i dati contenuti
nel modulo, se realmente c'è qualcosa da fare su quel ModComponent, in caso affermativo, viene mantenuto il
riferimento a quel componente ed i suoi dati.
ModComponentsMediator e SystemMediator
Un bottone essendo anch'esso un componente di modulo, possiede un riferimento ad un mediatore, ed uno o più
ModCommand. Al click il bottone invia i suoi command al mediatore (ed in questo caso il bottone fa da invoker,
potendo anche scegliere in che sequenza inviare i command al mediatore), ed il Mediator si occuperà di chiedere al
command se i moduli ad esso collegati sono interessati al comando, in caso affermativo il mediator ordinerà al
componente di registrarsi presso il comando e fornire il proprio riferimento. Attualmente sono implementati due tipi di
Mediatore: un mediatore di modulo ed uno di sistema. Ciò rende possibile la comunicazione interna tra i componeni di
uno stesso modulo, ed implementa anche la comunicazione esterna tra moduli diversi. In caso un command sia
contrassegnato da una Marker Intrface “SystemCommand”, verrà propagato con un meccanismo di Chain of
Responsability al mediatore padre (ovvero un mediatore Singleton di sistema), il quale propagherà agli altri mediatori di
modulo il comando, ed una volta terminata la propagazione invocherà il metodo run() del command che eseguirà le
operazioni previste su tutti i componenti registrati durante la propagazione.
SISTEMA MULTI-UTENTE
Il sistema prevede l'utilizzo da parte di più di un utente con livelli d'accesso e permessi diversi e con la possibilità di
effettuare lo switching fra essi, simile in
apparenza al “cambia utente/disconnetti” dei
sistemi operativi.
SISTEMA MULTI-LINGUA
Ogni utente ha la possibilità di selezionare una
propria lingua o traduzione del software (i
documenti fiscali invece verranno generati con
la lingua di default di sistema). Le traduzioni di sistema sono memorizzate in file *.properties con sistema chiave-valore
e richiamati della classe Language la quale si occupa di caricare a runtime la lingua dell'utente e quindi i moduli
necessari.
TEST DI INTEGRAZIONE D'USO DEL FRAMEWORK
Realizzazione della gestione dei clienti
A scopo di testing si realizza la gestione di un'anagrafica clienti. Come prima cosa si realizza il Model, ovvero il
GenericObject che rappresenterà il Cliente nel nostro sistema:
public class Customer extends GenericObject {
public static String[] stringlabels = { "Id", "IdentificationCode", "Name",
"Surname", "MailAddress", "Card1Number", "Card2Number", "Address",
"Cap", "City", "District", "PersonalPhoneNumber",
"OfficePhoneNumber", "Fax", "VatCode", "CF",
"DestinationWareAddress", "Other", "Notes" };
public static String[] booleanlabels = { "IsActive" };
public static String[] intlabels = {};
public static String[] doublelabels = {};
public static String[] longlabels = { "BirthDate", "CreationDate" };
public Customer() {
super("Customer", stringlabels, booleanlabels, intlabels, doublelabels,
longlabels);
}
principalid = "IdentificationCode";
compareid = "Surname";
equalsid = "IdentificationCode";
}
Si crea ora il modello del Database adatto per contenere i Customer:
public class CustomerDB extends GenericObjectDB<Customer> {
public CustomerDB() {
super("CustomerDB", "Customers");
}
public void createDB() {
try {
super.createDB(new Customer());
} catch (Exception e) {
e.printStackTrace();
}
}
Si crea la concretizzazione della AbstractFactory relativa alla personalizzazione del pannello di visualizzazione: in
pratica vogliamo che i campi Id, Name, Surname e BirthDate siano sempre non editabili, e che i campi “Other” e
“Notes” vengano rappresentati con una SensibleTextArea. In più vogliamo che ogni bottone creato per questo tipo di
GenericObject abbia aggiunto alla fine della chiave per la traduzione la personalizzazione “customerbutton” che
permetterà una traduzione più precisa. La classe da realizzare per implementare questo comportamento:
public class CustomerGuiComponentFactory<T extends GenericObject> extends
GuiComponentAbstractFactory <T>{
public CustomerGuiComponentFactory() {
super();
this.setField("panelicon", "customercard.png");
}
@Override
public JTextComponent createTextComponent(String key) {
if (key.equals("Id") | key.equals("Name") | key.equals("Surname")) {
SensibleTextField stf = (SensibleTextField) super
.createTextComponent(key);
stf.setEditable(false);
stf.setEnabled(false);
return stf;
}
// TRATTAZIONE CAMPI OTHER E NOTES (CREAZIONE TEXTAREA)
else if (key.equals("Other") | key.equals("Notes")) {
SensibleTextArea jta = new SensibleTextArea(result.getObject()
.getStringValue(key), getTranslOf(key + "tooltip"),
getModWindow());
applyStrategy(jta);
}
return jta;
// TRATTAZIONE DI ALTRI CAMPI STRINGA
else {
return super.createTextComponent(key);
}
}
@Override
public SensibleButton createButton(String key) {
return super.createButton(key + "customerbutton");
}
@Override
public JTextComponent createLongTextComponent(String key) {
if (key.equals("BirthDate")) {
JTextComponent tc = super.createLongTextComponent(key);
tc.setEditable(false);
return tc;
}
} else {
return super.createLongTextComponent(key);
}
Si specifica quali campi vorremo veder comparire nelle tabelle di ricerca dell'oggetto, che dimensione dovrà avere la
colonna, e che tipo di dato stiamo rappresentando. Per far questo creiamo la classe:
public class CustomerTableModelFactory extends TableModelAbstractFactory<Customer>{
static String[] headers = { "Name", "Surname", "City", "BirthDate",
"IsActive", "PersonalPhoneNumber" };
static String[] types = { "String", "String", "String", "Long", "Boolean",
"String" };
static int [] colDimensions = {100,100,100,50,10,100};
public CustomerTableModelFactory() {
super(types, headers,colDimensions);
}
}
Si crea infine la ModWindow relativa alla gestione dei clienti:
public class CustomersFrame extends ModWindow {
static ResourceBundle[] translSet = {
SystemMemory.getSystemCommonLangMod(),
SystemMemory.getSystemLangMod("customer") };
static String[] envIconNames = {};
public CustomersFrame(Mediator mediator) {
super("CustomersGestionWindow", mediator, translSet, envIconNames);
super.dockList = new ModWindowDockBar[1];
super.dockList[0] = new CustomersDock(this);
setIconImage(SystemMemory.imgSet.getImage("customers.png"));
this.windowIcon = SystemMemory.imgSet.getImageIcon("customers.png");
welcomeCentralPanel = new ScrollPanel();
super.addDecorations();
}
}
Integrazione nel sistema del Gioco del Sudoku
Per dimostrare la possibile integrazione con componenti software realizzati indipendentemente dal sistema base si
inserisce nel sistema un risolutore di Sudoku. E' interessante notare la flessibilità del GenericObject a runtime anche
nelle sue componenti atomiche: per il sudoku infatti si è scelto di memorizzare le 81 caselle come campi distinti del
sudoku. Una normale implementazione del GenericObject prevederebbe la scrittura a mano di 81 elementi all'interno
dell'array di interi, con 81 nomi diversi, e 81 chiave-valore nelle traduzioni di lingua. Un piccolo trucchetto può però
essere messo a punto:
public class SudokuGenericObject extends GenericObject {
public
public
public
public
public
static
static
static
static
static
String[]
String[]
String[]
String[]
String[]
stringlabels = { "IdentificationCode", "Name" };
booleanlabels = {};
intlabels = getIntLabels();
doublelabels = {};
longlabels = { "CreationDate" };
public SudokuGenericObject() {
super("Sudoku", stringlabels, booleanlabels, getIntLabels(), doublelabels,
longlabels);
principalid = "IdentificationCode";
compareid = "Name";
equalsid = "IdentificationCode";
}
public static String[] getIntLabels() {
String[] labArray = new String[81];
}
int indexPosition=0;
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
String position= new Integer(i).toString()+new
Integer(j).toString();
String label = "cell"+position;
labArray[indexPosition] = label;
indexPosition++;
}
}
return labArray;
}
Come detto prima, il sudoku è un componente indipendente, che si inserisce all'interno del sistema senza essere
progettato per esso: avrà quindi un suo pannello non compatibile con la costruzione automatica dei
GenericObjectPanelBuilder e di conseguenza dovrà prevedere un Builder per il pannello personalizzato:
public class SudokuPanelBuilder extends PanelPartBuilder{
public SudokuPanelBuilder(AbstractPanelBuilderDirector bd) {
super(bd);
}
@Override
public void buildPart() {
this.director.getResult().setSudokuPanel(new SudokuPanel(
new MigLayout(),
this.director.getResult().getWindow(),
this.director.getResult().getTransl(),
(SudokuGenericObject) this.director.getResult().getObject(),
this.director.getResult().getObjectList(),
this.director.getResult().getRepresentationStrategy(),
this.director.getResult().getGuiFactory()
));
}
}
Come per il builder anche un Director:
public class GamePanelBuilderDirector <T extends GenericObject> extends
AbstractPanelBuilderDirector{
GamePanel <T> result;
public void setResult(GamePanel<T> result) {
this.result = result;
}
public GamePanelBuilderDirector (SudokuGenericObject obj,ArrayList<T>objList,
ModWindow window, ResourceBundle transl,
TableModelAbstractFactory<T> guiFactory) {
AddNewStrategy strategy = new AddNewStrategy();
this.result = new GamePanel<T>(new MigLayout(),
window,
transl,
obj,
objList,
strategy,
guiFactory);
}
@Override
public void build() {
this.result.getGuiFactory().setResult(this.result);
new TitlePanelPartBuilder(this).buildPart();;
new SudokuPanelBuilder(this).buildPart();
new ModDelSaveButtonsPartBuilder(this,
new VisualizeObjectCommand(this.result),
new ModifyObjectCommand(this.result),
new DeleteObjectCommand(
result.getObject()
)
).buildPart();
}
@Override
public ModPanel<T> getResult() {
}
return this.result;
}
La barra degli strumenti per il modulo Sudoku, che sarà contenuto nella ModWindow del Sudoku e che istanzierà
all'occorrenza i BuilderDirector per la creazione dei pannelli:
public class SudokuDock extends ModWindowDockBar {
SensibleButton addsudokubutton;
SensibleButton searchsudokubutton;
public SudokuDock(ModWindow modWindow) {
super(modWindow);
addsudokubutton = new SensibleNormalButton(
transl.getString("addsudokubutton"),
SystemMemory.imgSet.getImageIcon("add.png"),
transl.getString("addsudokubuttontooltip"), window);
addsudokubutton.addActionListener(this);
searchsudokubutton = new SensibleNormalButton(
transl.getString("searchsudokubutton"),
SystemMemory.imgSet.getImageIcon("find.png"),
transl.getString("searchsudokubuttontooltip"), window);
searchsudokubutton.addActionListener(this);
this.add(addsudokubutton);
this.add(searchsudokubutton);
setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == addsudokubutton) {
SudokuGenericObject obj = new SudokuGenericObject ();
obj.setStringValue("IdentificationCode", new Long(new
GregorianCalendar().getTimeInMillis()).toString());
GamePanelBuilderDirector<SudokuGenericObject> dir = new GamePanelBuilderDirector (
obj,null, this.window, SystemMemory.getSystemLangMod("sudoku"),
new SudokuTableModelFactory()) ; dir.build();
ModPanel<SudokuGenericObject> p = dir.getResult();
window.addTabToCentralPanel(dir.getResult().getTransl().getString("playsudoku"), p);
}
else if (e.getSource() == searchsudokubutton) {
new SearchSudokuWindow(window);
}
}
}
ed infine la finestra per il modulo Sudoku:
public class SudokuFrame extends ModWindow {
static ResourceBundle[] translSet = {
SystemMemory.getSystemCommonLangMod(),
SystemMemory.getSystemLangMod("sudoku") };
static String[] envIconNames = {};
public SudokuFrame(Mediator mediator) {
super("SudokuWindow", mediator, translSet, envIconNames);
super.dockList = new ModWindowDockBar[1];
super.dockList[0] = new SudokuDock(this);
setIconImage(SystemMemory.imgSet.getImage("sudoku.png"));
this.windowIcon = SystemMemory.imgSet.getImageIcon("sudoku.png");
welcomeCentralPanel = new ScrollPanel();
addDecorations();
}
}
ALTRE FEATURES (Executor Framework, Facade Pdf Renderer)
E' prevista la personalizzazione della posizione di installazione della cartella contenente i databases. Integrata
funzionalità di autocorrezione di sintassi nell'inserimento dati (correzione di maiuscole dopo il punto, spazi dopo la
virgola, controllo capslock). Possibilità di generare documenti in pdf.
Prevista la realizzazione dei moduli “Registrazione in Linea” e “Chat” che permetteranno la registrazione del prodotto
tramite invocazione remota del metodo “productValidation()” e mettono a disposizione le funzionalità di chat tra utenti
(ad esempio due software gestionali installati su due postazioni diverse permetterebbero ad operatori di reparti diversi di
comunicare in modo veloce ed utilizzando solo la rete Lan interna).