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).