Departement Industriële Wetenschappen
Master in de industriële wetenschappen: Elektronica-ICT afstudeerrichting ICT
Ontwikkeling van een Java API voor een kennisbanksysteem
Masterproef voorgedragen tot het behalen van de beroepstitel van industrieel ingenieur.
Academiejaar 2010-2011
Door:
Calus Nick
Promotor hogeschool:
Dr. Vennekens Joost
Promotor bedrijf:
Dr. Vennekens Joost
Departement Industriële Wetenschappen
Master in de industriële wetenschappen: Elektronica-ICT afstudeerrichting ICT
Ontwikkeling van een Java API voor een kennisbanksysteem
Masterproef voorgedragen tot het behalen van de beroepstitel van industrieel ingenieur.
Academiejaar 2010-2011
Door:
Calus Nick
Promotor hogeschool:
Dr. Vennekens Joost
Promotor bedrijf:
Dr. Vennekens Joost
Voorwoord Bij het maken van deze masterproef heb ik hulp en steun gekregen van een aantal personen. Ik zou hen daarom bij deze hiervoor willen bedanken. In de eerste plaats wil ik mijn promotor dr. Joost Vennekens bedanken voor het inhoudelijk verbeteren van deze tekst en om mij op weg te helpen wanneer ik niet goed wist waar te beginnen.
Ook wil ik mijn ouders, broers en vriendin bedanken voor de steun
en aanmoediging op momenten dat ik de moed even verloren had. Mijn vriendin wil ik daarenboven ook bedanken voor het grammaticaal verbeteren van deze tekst. Indien ik iemand ben vergeten te vermelden die mij ook gesteund heeft, wil ik die alsnog bedanken. Er is hard gewerkt aan deze tekst en de software die ontwikkeld is in deze masterproef. Ik lever het ontwikkelde systeem dan ook met trots af en ben ervan overtuigd dat dit nuttig zal zijn. Veel leesplezier.
ix
Abstract Het Inductive Denition Programming kennisbanksysteem is een expertsysteem dat logische problemen kan oplossen aan de hand van regels en denities. Een bepaalde soort van deze problemen zijn conguratieproblemen. Bij conguratieproblemen zal de gebruiker van het expertsysteem een aantal parameters van het probleem een waarde geven. Met andere woorden, de gebruiker zal het probleem aan de hand van een aantal parameters congureren. Door midel van deze conguratie zal het kennisbanksysteem bepalen of er geen, één of meerdere geldige oplossingen zijn voor het probleem. Een voorbeeld van een dergelijk probleem is een applicatie waarmee een ets kan samengesteld worden. De gebruiker kiest het soort ets, het kader, velgen, enz. Afhankelijk van de keuze van een onderdeel zullen andere onderdelen wel of niet beschikbaar zijn. De conguratie van de parameters van het probleem kan gezien worden als het inclusief of exclusief selecteren van een aantal waarden. Een gebruiksvriendelijke manier om waarden te selecteren, is door middel van een grasche gebruikersinterface of afgekort GUI. Dit laat toe om stap voor stap verschillende waarden toe te kennen aan de parameters van het probleem. De GUI kan gemaakt worden met behulp van verschillende programmeertalen, waarvan Java een goede keuze is. Dit is omwille van de Create once, deploy anywhere losoe van Java. Bij andere gecompileerde programmeertalen bestaat het risico dat, bij het gebruik op meerdere platformen, de applicaties niet meteen werken zonder aanpassingen. Spijtig genoeg is er een grote hoeveelheid Java-code nodig om een GUI in elkaar te knutselen en een aantrekkelijke layout te geven. Er is nog meer werk en code nodig om deze GUI op een correcte manier te koppelen met een achterliggend systeem, zodat deze GUI dat systeem kan aanpassen en de staat van het systeem wordt weergegeven door de GUI. Bijkomend bestaat er geen werkende kant en klare oplossing om het IDP kennisbanksysteem te gebruiken vanuit de Java programmeertaal. Om die problemen op te lossen zal er in deze masterproef een Application Programming Interface ontwikkeld worden in de Java programmeertaal.
In de eerste plaats zal met
behulp van deze API het IDP kennisbanksysteem gebruikt kunnen worden vanuit de Java programmeertaal.
Daarenboven is het mogelijk om hiemee op een snelle, gemakkelijke
en exibele manier GUI applicaties automatisch te koppelen aan een IDP kennisbank, zodat deze GUI de kennisbank kan aanpassen en de staat van deze kennisbank wordt weergegeven door de GUI.
Abstract The Inductive Denition Programming knowledge base system is an expert system that can solve logical problems by using rules and denitions. A specic kind of these problems are the conguration problems. When solving a conguration problem, the user of the expert system will provide values for some of the parameters of the problem. In other words, the user will congure the problem by assigning values to some parameters. Based on this conguration, the knowledge base system will determine if there are one or more solutions, or no solution at all for that problem. An example of such a problem is an application with which a bicycle can be congured. The user chooses the kind of bike, the frame, rims, etc. Depending on the choices for a component, other components will or will not be available. Conguring parameters of the problem can be seen as inclusively or exclusively selecting some values. A user friendly way of selecting these values, is by the use of a GUI, short for Graphical User Interface. This allows a user to assign values to the parameters of the problem in a step by step fashion. The GUI can be created in one of many programming languages, although Java would be an excellent choice. This is so because of the Create once, deploy anywhere philosophy of Java. In other compiled programming languages, there is always a risk that the application will not work on multiple platforms without modications of the source code. Sadly, there is a large amount of Java code needed to put together a professional looking GUI. Even more eort and code is needed to make this GUI interact in a correct way with a backing system. Interacting correctly means that the GUI can alter the backing system and it displays the state of that system. Above and beyond this fact, there is currently no working of the shelf solution to use the IDP knowledge base system in conjunction with the Java programming language. To solve all these problems, an API, short for Application Programming Interface, for the Java programming language will be developed in this masters thesis. In the rst place, this API will enable an application, written Java, to use the IDP knowledge base system. On top of that, this API allows the programmer to create the GUI and connect it in a fast, easy and exible way to the IDP knowledge base system, in order for the GUI to be able to update the knowledge base and display the state of the knowledge base.
Short Summary The goal of this master's thesis is to develop a method or system that enables a Java programmer to easily and rapidly build a GUI which is backed by the IDP knowledge base system. The rst part, which is building a GUI using the Java language, can be easily done using a GUI builder. Several dierent GUI builders exist at the time of writing. Some of them are open source and free, and for others a license can be purchased. There are free GUI builders available for the three most widely known and used IDEs in the Java world: Eclipse (through plug-ins), Netbeans and IntelliJ. Creating a GUI with those builders is not covered by this thesis, because it is very simple and there are enough resources available on the internet to get you started. The second part of the goal is to back the GUI by the IDP knowledge base system. There are a couple of possibilities to accomplish this goal, but some of them have proven to be less desirable than others. The rst possibility is to create a plug-in for one of the known IDEs. This plug-in would generate connection code by dragging and dropping parts of the knowledge base on a graphical component in the GUI. There are however some drawbacks to this method. One IDE has to be chosen for which the plug-in will be made, letting down all programmers which use another IDE. The code generated by the plug-in would probably be dicult to maintain. The plug-in would need to be updated or maybe recreated whenever a new version of the IDE becomes available, which isn't ideal for a master's thesis product since there are no maintainers and the product would become obsolete. Another possible solution would be to use Java Annotations. These metadata classes can be used to tag parts of the GUI. Tagged objects could be parsed and thus automaticaly connected to the IDP system. The advantage of this method is that annotations are part of the Java language standard, so this solution will remain functional as long as the Java language doesn't change drasticaly. The last possible solution involves the Java Enterprise Edition framework. This is a web based framework where the GUI is actually a web page.
For this solution to work, a
Java Application Server is needed, which means you must have a server machine with the application server installed. This can be a costly solution if just a desktop application is required. Another drawback is the complexity of the framework which has to accounted for in the implementation. It is already clear that the solution which uses annotations will have the greatest chance of success. Therefore this solution will be implemented. It should be noted that every
proposed solution would require or at least benet from the development of an API, so this is in any case the rst and most important task at hand. To succesfully develop an API, a thorough design will have to be made. In the design of the API created in this thesis there are three top level modules. The most important module is the knowledge base abstraction module. This module will add a layer on top of the IDP system to hide its aws and to make it easy to use from within a Java application. The second module is the conguration module. This module will provide the means to congure every kind of supported GUI component and attach it to the knowledge base. Support for new GUI components can be added to the API by making use of the provided Service Provider Interface. This is a Java technology that can be used to dynamicaly load new implementations of a certain interface into a running application. In fact, this module doesn't support any GUI component by itself.
A few basic components are supported
because a default implementation of the Service Provider Interface is provided. The third and last module is the analysis module.
This module is able to analyse the
written or generated GUI code by looking for annotated elds. It is this module that will automatically connect the GUI to the IDP system, or at least semi-automatic, since the programmer needs to annotate the elds himself. Luckily, this is an easy and small task. Annotating the required elds will take at most the same amount of time as needed to create the GUI with a GUI builder. To connect the annotated components to the IDP system, the other two modules of the API are used by this module. The API is designed and implemented in such a way that it is easy for a programmer to extend its functionality according to the needs of the application.
To accomodate this
extensibility, a lot of interfaces and abstract base classes are available in the API.
Inhoudsopgave 1 Inleiding 1.1
Probleemstelling 1.1.1
1.1.2 1.2
1.3
1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
IDP-taal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1.1.1.1
Vocabularium . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.1.1.2
Theorie
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.1.1.3
Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
Gebruik van het IDP-systeem
. . . . . . . . . . . . . . . . . . . . .
5
Doelstellingen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
1.2.1
congNow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
1.2.2
Fietsconguratiedemo
. . . . . . . . . . . . . . . . . . . . . . . . .
7
Organisatie van deze tekst . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
2 Literatuurstudie
11
2.0.1
Eclipse-Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
2.1
Annotaties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
2.2
JavaServer Pages
12
2.2.1
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
JavaServer Faces
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
2.3
Internationalisatie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
2.4
Service Provider Interface
. . . . . . . . . . . . . . . . . . . . . . . . . . .
14
2.5
Besluit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
3 Windows programma's
17
3.1
ISO C en C++ standaarden . . . . . . . . . . . . . . . . . . . . . . . . . .
17
3.2
GidL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
3.3
Approx . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
3.4
Besluit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
4 Ontwerp
23
4.1
Overzicht
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
4.2
Kennisbankabstractie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
4.2.1
Model
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
4.2.2
Toestand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
28
4.2.3
Gegevensverwerking
. . . . . . . . . . . . . . . . . . . . . . . . . .
28
4.2.4
Solution
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
4.2.5
Event listeners
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
4.3
Conguratie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31
4.4
Analyse
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
33
4.5
Besluit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
34
5 Implementatie
37
5.1
UndoStack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
37
5.2
Processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
38
5.3
Solution
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
38
5.3.1
changeState . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
39
5.3.2
undoChangeSteps . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
5.4
Solution in een andere Thread . . . . . . . . . . . . . . . . . . . . . . . . .
45
5.5
Congurator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
46
5.5.1
ndBestForObject
. . . . . . . . . . . . . . . . . . . . . . . . . . .
46
5.5.2
ndToolkit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
47
5.5.3
getJars . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
48
Besluit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
50
5.6
6 Gebruik en uitbreiding van de API
51
6.1
Translator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
6.2
Annotatie parser
53
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.2.1
Snelle methode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.2.2
Flexibele methode
55
. . . . . . . . . . . . . . . . . . . . . . . . . . .
56
6.3
Congurator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
6.4
Kennisbankabstractie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
60
6.5
Interfaces en abstracte klassen implementeren
. . . . . . . . . . . . . . . .
60
6.6
Maken van een Toolkit . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
INHOUDSOPGAVE
6.7
xv
6.6.1
Adapter klasse
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
6.6.2
Factory klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
66
6.6.3
Toolkit klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
66
6.6.4
JAR samenstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67
Besluit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
69
Besluit
69
Bibliograe
72
Verklarende lijst van afkortingen en symbolen API
Application Programming Interface. Een verzameling interfaces en klassen die een geheel vormen waarmee een bepaald probleem kan opgelost worden.
GUI
Graphical User Interface. Een gebruikersinterface is het onderdeel van een applicatie waarmee de gebruiker kan interageren met de applicatie.
Men spreekt van een
grasche gebruikersinterface wanneer er gebruik wordt gemaakt van vensters, iconen, menus, knoppen en een aanwijsapparaat.
HTML
HyperText Markup Language. Een opmaaktaal om de structuur van webpagina's
te deniëren.
IDE
Integrated Development Environment. Een applicatie om software te onwikkelen, waarin alle nodige tools in geïntegreerd zijn.
IDP
Inductive Denition Programming.
Een modeleertaal en een systeem om NP-
problemen mee op te lossen.
JAR
Java Archive. Een op ZIP gebaseerd bestandstype dat gebruikt wordt om een Java applicatie en resources in op te slaan.
JRE
Java Runtime Environment. De combinatie van een JVM en de standaard Javabibliotheek. Dit geheel maakt het mogelijk om Java-applicaties uit te voeren.
JVM
Java Virtual Machine. De virtuele machine waar Java applicaties op worden uit-
gevoerd.
JSF
JavaServer Faces. Een op JSP gebaseerde technologie om op een gemakkelijke manier in een webpagina formulieren te maken die op een GUI gelijken.
JSP
JavaServer Pages. Een scripting technologie om op een gemakkelijke manier HTML opmaak te mengen met Java code.
SPI
Service Provider Interface.
Een standaard manier in Java om dynamisch Jar bi-
bliotheken in te laden in een reeds uitvoerende applicatie. SPI mogelijkheden zijn geïntegreerd in de specicatie van het Jar bestandsformaat.
URL
Unied Resource Locator. Een tekenreeks die de locatie van een bron of bestand aanduidt. Deze tekenreeks moet aan bepaalde syntaxvoorwaarden voldoen.
ZIP
Een bestandsformaat dat toelaat om bestanden in te bundelen en deze te comprimeren. De naam ZIP is afkomstig van de inpakhandeling, het dichtritsen van een archief.
L¼st van guren 1.1
De architectuur van congNow. (Abhinav, 2010) . . . . . . . . . . . . . . .
7
1.2
Originele etsconguratiedemo.
8
1.3
Nieuwe versie van de etsconguratie demo.
4.1
Overzicht van de architectuur van de API
4.2
Overzicht van kennisbank abstractie module
4.3
Klassediagram van de model klassen
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
. . . . . . . . . . . . . . . . . .
24
. . . . . . . . . . . . . . . . .
26
. . . . . . . . . . . . . . . . . . . . .
27
4.4
Principe van het veranderen van de toestand in een stap. . . . . . . . . . .
29
4.5
Overzicht van de conguratiemodule
. . . . . . . . . . . . . . . . . . . . .
32
4.6
Overzicht van de analyse module.
. . . . . . . . . . . . . . . . . . . . . . .
34
5.1
Implementatie van het veranderen van de toestand in een stap. . . . . . . .
40
6.1
Oproepen van de Export Wizard.
. . . . . . . . . . . . . . . . . . . . . . .
68
6.2
De Export Wizard
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
70
Lijst van codefragmenten 1.1
Vocabularium blok
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.2
Theorie blok . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.3
Data blok
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
2.1
Denitie van de IDPPredicate-annotatie. . . . . . . . . . . . . . . . . . . .
12
2.2
Gebruik van JSF tags.
14
3.1
Code zonder sequence points . . . . . . . . . . . . . . . . . . . . . . . . . .
19
3.2
Declaratie van de index operator van de vector klasse . . . . . . . . . . . .
20
5.1
De changeState methode, deel 1. . . . . . . . . . . . . . . . . . . . . . . . .
41
5.2
De changeState methode, deel 2. . . . . . . . . . . . . . . . . . . . . . . . .
41
5.3
De changeState methode, deel 3. . . . . . . . . . . . . . . . . . . . . . . . .
42
5.4
De changeState methode, deel 4. . . . . . . . . . . . . . . . . . . . . . . . .
43
5.5
De changeState methode, deel 5. . . . . . . . . . . . . . . . . . . . . . . . .
43
5.6
De undoChangeSteps methode, deel 1.
. . . . . . . . . . . . . . . . . . . .
44
5.7
De undoChangeSteps methode, deel 2.
. . . . . . . . . . . . . . . . . . . .
45
5.8
De ndBestForObject methode, deel 1. . . . . . . . . . . . . . . . . . . . .
46
5.9
De ndBestForObject methode, deel 2. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.10 De ndToolkit methode, deel 1.
. . . . . . . . . . . . . . . . . . . . . . . .
5.11 De ndToolkit methode, deel 2.
47 48
. . . . . . . . . . . . . . . . . . . . . . . .
48
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
49
6.1
Deel van een properties bestand met de vertaling van symbolen en tuples. .
52
6.2
Declaratie van geannoteerde velden. . . . . . . . . . . . . . . . . . . . . . .
53
6.3
Code voor de koppeling van geannoteerde velden, de snelle manier.
55
6.4
Code voor de koppeling van geannoteerde velden met meer exibiliteit.
6.5
Koppeling van componenten aan de kennisbank met behulp van Adapters.
6.6
De attachAdapter-methodes voor het koppelen van de adapters aan de
5.12 De getJars methode.
kennisbank.
. . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56 58 59
6.7
Directe wijziging van de staat van de kennisbank.
. . . . . . . . . . . . . .
60
6.8
Klassedenitie, velden en constructor van de ColorJCheckboxAdapter. . . .
62
6.9
De geneste CheckBoxListener klasse.
. . . . . . . . . . . . . . . . . . . . .
63
6.10 De onStateChange methode. . . . . . . . . . . . . . . . . . . . . . . . . . .
64
6.11 De onProcessingStarted en onProcessingEnded methode.
. . . . . . . . . .
65
. . . . . . . . . . . . . . . . . . .
66
6.13 De ColorCheckBoxFactory klasse. . . . . . . . . . . . . . . . . . . . . . . .
67
6.12 De setName en setDescription methode.
6.14 De ColorToolkit klasse. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
68
6.15 Het jidp.conguration.spi.Toolkit bestand.
68
. . . . . . . . . . . . . . . . .
Hoofdstuk 1 Inleiding 1.1 Probleemstelling In deze masterthesis ga ik op zoek naar een methode en implementatie voor het interactief oplossen van logische problemen met behulp van het IDP-raamwerk. IDP kan gezien worden als een kennisbanksysteem, in die zin dat dit systeem in staat is ingewikkelde logische problemen op te lossen door kennis aan te bieden in de vorm van een wiskundig model. De vernoemde ingewikkelde logische problemen zijn meerbepaald NP-problemen ofwel niet-deterministische polynomiale tijd problemen. Dit zijn k beslissingsproblemen die in polynomiale tijd O(n ) opgelost kunnen worden door een niet-deterministische Turing-machine. (Mariën et al., 2006) Een expert in een bepaald kennisdomein kan zijn kennis beschikbaar stellen door een wiskundig model op te stellen van een probleem.
Het domein waarin deze problemen
voorkomen kan sterk variëren. Enkele voorbeelden zijn: planning (van uurroosters), problemen in de grafentheorie, opstellen en oplossen van puzzels, enz. Door het model van een dergelijk probleem in te geven in het IDP-systeem kan in de eerste plaats geverieerd worden of een instantie al dan niet geldig is. In de tweede plaats is het ook mogelijk om geldige instanties te genereren. Het ontwikkelen van een API in de Java-programmeertaal moet het IDP-systeem toegankelijker te maken voor het ontwikkelen van applicaties. Meer speciek ga ik mij richten op de ontwikkeling van conguratie applicaties. Dit zijn applicaties waarbij de eindgebruiker van de applicatie door middel van selecties een oplossing gaat zoeken voor een probleem. De gebruiker gaat progressief selecties maken, waarbij het IDP-systeem deze selecties zal veriëren.
Indien het binnen aanvaardbare uitvoeringstijd mogelijk is, zal
het IDP-systeem ook combinaties uitsluiten die aan de hand van de huidige selecties niet meer tot een geldige oplossing kunnen leiden.
1.1.1 IDP-taal Het IDP-raamwerk bestaat uit een modeleertaal en een systeem om problemen die beschreven zijn met de modeleertaal op te lossen.
Er wordt gesproken van een modeleertaal in plaats van een programmeertaal. programmeertaal wordt de methode beschreven om een probleem op te lossen.
In een Deze
methode wordt een algoritme genoemd en kan omgezet worden naar machinecode, waarna dit uitgevoerd kan worden. In de IDP-taal wordt er geen methode beschreven maar eerder een model.
Dit model bevat voorwaarden en verbanden waaraan een oplossing moet
voldoen om een geldige oplossing te zijn voor een bepaald probleem. Een IDP-model bestaat uit drie delen:
•
Vocabularium
•
Theorie
•
Data
Om deze drie delen nader toe te lichten, zal ik een voorbeeld gebruiken. Dit voorbeeld bevat het model van een puzzelspel, Hitori genaamd. Hitori is een gelijkaardig spel als het meer bekende Sudoku. Het spel bestaat uit een vierkant rooster van een bepaalde grootte. het rooster staat een getal.
In elk vakje van
In elke rij en elke kolom van het rooster mag slechts één
wit vakje met een bepaald getal staan. Als er meerdere vakjes in dezelfde rij of kolom hetzelfde getal bevatten moeten elk van die vakjes behalve één zwart gekleurd worden. Een bijkomende beperking is dat er geen twee zwarte vakjes vlak naast elkaar mogen staan. Tenslotte moeten alle witte vakjes één aaneensluitend gebied vormen. Er mogen dus geen afgezonderde eilandjes zijn van witte vakjes. De bedoeling van het spel is om de juiste vakjes zwart te kleuren zodanig dat aan alle voorgaande voorwaarden voldaan zijn.
1.1.1.1 Vocabularium In het vocabularium gedeelte (codefragment 1.1) zijn de symbolen gedeclareerd die gebruikt worden als in- en uitvoer. Er kunnen ook intern gebruikte symbolen gedeclareerd worden in dit gedeelte maar het is evengoed mogelijk om deze symbolen in het theoriegedeelte te declareren. De lijst van symbolen die als invoer dienen, wordt voorafgegaan door `Given:'. Declaraties in IDP bestaan uit types, predicaten en functies. De declaratie van types gebeurt ofwel in een type blok of door `type' voor de declaratie te plaatsen. Een type moet gedeclareerd zijn voordat het kan gebruikt worden. Om een predicaat of een functie te declareren wordt de signatuur van dit predicaat of functie opgegeven. Voor een predicaat is dit eerst de naam van het predicaat en daarachter komen tussen ronde haakjes de argumenten van het predicaat, gescheiden door een komma. Bij een functie is dit nagenoeg hetzelfde, enkel wordt er bijkomend een dubbel punt en het terugkeertype geplaatst na het sluiten van de ronde haakjes. De lijst van symbolen die als uitvoer dienen voorafgegaan door `Find:'.
Declaraties in
deze lijst gebeuren op dezelfde manier als in de lijst voor de invoer. In tegenstelling tot de invoer is het hier niet mogelijk om types te declareren, aangezien alle types gekend moeten zijn voordat ze kunnen gebruikt worden.
Inleiding 1 2 3 4 5 6 7 8 9 10 11 12 13
3
Given : type { int Xpos int Ypos } type int Number S t a t e ( Xpos , Ypos , Number ) Find : Black ( Xpos , Ypos ) Declare : Reachable ( Xpos , Ypos ) Codefragment 1.1: Vocabularium blok
Het gedeelte voor interne symbolen wordt voorafgegaan door `Declare:'.
1.1.1.2 Theorie De theorie (codefragment 1.2), voorgesteld door regels, wordt voorafgegaan door `Satisfying:'.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Satisfying : d e c l a r e NextTo ( Xpos , Ypos , Xpos , Ypos ) . { }
! x1 y1 x2 y2 : NextTo ( x1 , y1 , x2 , y2 ) => ~( Black ( x1 , y1 ) & Black ( x2 , y2 ) ) . ! x1 x2 y n : ) => x1 = ! x y1 y2 n : ) => y1 = {
15 16 17 18 19 20
NextTo ( x1 , y1 , x2 , y2 ) <− abs ( x1 − x2 ) + abs ( y1 − y2 ) = 1 .
}
S t a t e ( x1 , y , n ) & S t a t e ( x2 , y , n ) & ~Black ( x1 , y ) & ~Black ( x2 , y x2 . S t a t e ( x , y1 , n ) & S t a t e ( x , y2 , n ) & ~Black ( x , y1 ) & ~Black ( x , y2 y2 .
Reachable ( 1 , 1 ) <− ~Black ( 1 , 1 ) . Reachable ( 1 , 2 ) <− Black ( 1 , 1 ) . Reachable ( x , y ) <− NextTo ( x , y , rx , ry ) & ~Black ( x , y ) & Reachable ( rx , ry ) .
! x y : Xpos ( x ) & Ypos ( y ) & ~Black ( x , y ) => Reachable ( x , y ) . Codefragment 1.2: Theorie blok
In dit gedeelte wordt de denitie gegeven van de gedeclareerde predicaten en functies. Dit zijn de verbanden en beperkingen die opgelegd worden in het model. Om deze ver-
Tabel 1.1: IDP-operatoren
logisch
∧ ∨ ¬ ⇒ ≡ ∀ ∃
IDP
verklaring
&
en
|
of
∼
niet
=>
impliceert
<=>
equivalent
!
voor alle
?
er bestaat
banden en beperkingen uit te drukken in IDP zijn er een aantal operatoren voorzien die overeenstemmen met wiskundige operatoren (tabel 1.1). De betekenis van een uitdrukking !
(x2,y2)).
x1 y1 x2 y2 : NextTo(x1,y1,x2,y2)=> ~(Black(x1,y1)& Black
is dan: voor elke x1, y1, x2 en y2, als punt x1,y1 aangrenzend is met punt x2,y2
mogen beide punten niet tegelijkertijd zwart zijn. In het voorbeeld is ook te zien dat elke uitdrukking moet eindigen met een punt. Wanneer één of meerdere uitdrukkingen omsloten zijn door gekrulde haakjes, dan is dit een expliciete denitie. Elke uitdrukking in een denitie moet beginnen met het predicaat dat gedenieerd wordt, gevolgd door `<−' en de uitdrukking waaraan het predicaat gelijk moet zijn. Een denitie kan ook recursief zijn.
1.1.1.3 Data In dit gedeelte staat de invoer die speciek is voor elke instantie van het probleem (codefragment 1.3). Als dit de invoer is voor een type, predicaat of functie in het `Given:' blok, dan wordt deze invoer voorafgegaan met `Data:'. Voor een type en predicaat wordt er een verzameling van mogelijke waarden gegeven en voor een functie wordt er een verband gegeven tussen de argumenten en de terugkeerwaarde.
De mogelijke waarden worden
gescheiden door een punt-komma `;'. Wanneer een waarde bestaat uit verschillende symbolen, zoals bij een predicaat of functie met meerdere argumenten, dan worden deze symbolen gescheiden door een komma `,'. Bij een functie wordt er tussen de argumenten en de terugkeerwaarde een pijl `->' gezet die het verband aangeeft. De lijst van waarden voor een type, predicaat of functie wordt omsloten door gekrulde haakjes en voorafgegaan door de naam van het type,predicaat of functie en een isgelijkteken `='. Voor predicaten of functies in het `Find:' blok wordt de invoer voorafgegaan met `Partial:'. Dit is handig wanneer er reeds een gedeelte van de oplossing gekend is. Dit blok wordt op dezelfde manier ingegeven als het `Data:' blok, maar met twee lijsten van waarden achter elkaar per predicaat of functie. De eerste lijst bevat de waarden die zeker wel deel zijn van de oplossing en de tweede lijst bevat de waarden die zeker niet deel zijn van de oplossing.
Inleiding 1 2 3 4 5
6 7 8
5
Data : Xpos = { 1 ; 2 ; 3 ; 4 ; 5} Ypos = { 1 ; 2 ; 3 ; 4 ; 5} Number = { 1 ; 2 ; 3 ; 4 ; 5} State = {1 ,1 ,1; 1 ,2 ,4; 1 ,3 ,1; 1 ,4 ,2; 1 ,5 ,4; 2 ,1 ,2; 2 ,2 ,4; 2 ,3 ,3; 2 ,4 ,4; 2 ,5 ,1; 3 ,1 ,4; 3 ,2 ,1; 3 ,3 ,4; 3 ,4 ,3; 3 ,5 ,3; 4 ,1 ,3; 4 ,2 ,1; 4 ,3 ,4; 4 ,4 ,5; 4 ,5 ,2; 5 ,1 ,5; 5 ,2 ,2; 5 ,3 ,1; 5 ,4 ,4; 5 ,5 ,3} Partial : Black = { 5 , 4 ; 4 , 1 } { 1 , 1 } Codefragment 1.3: Data blok
1.1.2 Gebruik van het IDP-systeem Bij het uitvoeren van het IDP-systeem wordt eerst het model met bijhorende data ingegeven, waarna het systeem één of meerdere oplossingen zal zoeken en weergeven. Deze vorm van uitvoeren is bekend als een batch proces en is eenvoudig en eciënt, omdat alle variabelen op voorhand gekend zijn. Helaas is het moeilijk om op deze manier interactieve applicaties te bouwen, aangezien die interactie een (quasi) continu proces is. Daarom is er een aangepaste versie van het IDP-systeem gemaakt (Approx). Deze aangepaste versie zal als resultaat niet een oplossing aanbieden maar wel beperkingen in de verzameling van oplossingen. De beperkingen kunnen dan samen met een nieuwe beperking, gemaakt door de gebruiker, terug ingevoerd worden om zo opnieuw naar bijkomende beperkingen zoeken. De bedoeling is dan om stapsgewijs de gewenste oplossing als enige oplossing te behouden.
1.2 Doelstellingen Het doel van deze masterproef is het ontwikkelen van een methode of systeem waarmee een Java-programmeur snel en gemakkelijk een GUI kan maken, die aangestuurd wordt door het IDP-kennisbanksysteem. De vereisten kunnen opgesplitst worden in twee delen: het snel en gemakkelijk maken van een GUI en het snel en gemakkelijk koppelen van deze GUI met het IDP-systeem. Het eerste deel van de vereisten kan opgelost worden met een GUI-builder. Er zijn verschillene GUI-builders beschikbaar.
Sommigen zijn vrij te gebruiken en voor anderen
moet een licentie aangekocht worden. Voor elk van de drie meest gebruikte Java-IDE's is er op zijn minst één vrij te gebruiken builder beschikbaar. Bij Netbeans en IntelliJ is deze inbegrepen en bij Eclipse kan deze als plug-in toegevoegd worden. Een GUI ontwikkelen met een GUI-builder is zeer eenvoudig. Men sleept componenten op de gewenste plaats en zorgt er voor dat de componenten juiste uitgelijnd zijn.
De
procedure om dit te doen zal niet behandeld worden in deze masterproef, aangezien dit een eenvoudige procedure is en er verschillende handleidingen beschikbaar zijn op het internet.
Het tweede deel van de vereisten, de GUI koppelen met het IDP-systeem, is niet zo voor de hand liggend. Er bestaat namelijk geen kant en klare manier om dit systeem te gebruiken vanuit Java. Het IDP-kennisbanksysteem wordt uitgevoerd in een apart proces op de computer. De in- en uitvoer van en naar IDP gebeurt ofwel via tekstbestanden of via de in- en uitvoerstromen van dit proces. Om IDP te kunnen aanspreken vanuit Java, moet eerst het IDP-proces gestart worden, waarna hier invoer naar wordt weggeschreven en uitvoer terug wordt opgehaald. De invoer en uitvoer van het IDP-proces is vrijwel onbruikbaar in ruwe tekstvorm voor een Java-applicatie. Daarom moet de uitvoer worden omgezet in een gestructureerde voorstelling, die gemakkelijk kan gebruikt worden in een Java-applicatie. Andersom moet die gestructureerde voorstelling terug omgezet kunnen worden in een vorm die het IDP-systeem verstaat, zodat dit als invoer kan aangeboden worden. Met behulp van deze gestructureerde voorstelling is het mogelijk om een GUI te koppelen aan het IDP-systeem. Er moet wel een gemakkelijke en snelle manier gevonden worden om die koppeling tot stand te brengen. Er is reeds een kleine Java-bibliotheek, congNow genaamd, beschikbaar die een gestructureerde voorstelling kan opbouwen van de uitvoer van het IDP-systeem.
Als de
GUI voldoet aan een aantal voorwaarden, is deze bibliotheek in staat de GUI automatisch te koppelen aan IDP. Samengevat is het doel van deze masterproef de functionaliteit van congNow, die beschreven is in sectie 1.2.1 en sectie 1.2.2, te implementeren en te verbeteren op vlak van correctheid, exibiliteit en gebruiksvriendelijkheid voor de programmeur en gebruiker.
1.2.1 congNow De bibliotheek congNow is in staat een GUI, gemaakt met de Swing toolkit, te koppelen met IDP. In guur 1.1 staan de onderdelen van congNow afgebeeld. Hierin zijn Manager en Support Classes de onderdelen die terug te vinden zijn in die bibliotheek. De andere onderdelen zijn Approx en GidL. Dit zijn de programma's (Zie hoofdstuk 3) die onderliggend gebruikt worden.
De onderdelen Cong.
File, Partial Int.
File en
IDP File zijn de bestanden die het probleem beschrijven en die gebruikt worden voor de in- en uitvoer van IDP. Het onderdeel aan de linkerkant van de guur is de gebruikersinterface. Dit is het onderdeel dat gebruik maakt van congNow om de kennisbank aan te spreken. Zoals de ontwerper zelf aangeeft in guur 1.1, is de Manager klasse de belangrijkste klasse in de bibliotheek en dienen de andere klassen als ondersteuning van die klasse. Het was oorspronkelijk de bedoeling dat ik congNow zou uitbreiden en aanpassen, omdat de basisfunctionaliteit hier reeds aanwezig is. Dit was echter een onhaalbare opdracht om verschillende redenen. Als eerste is er nauwelijks een ontwerp of architectuur te herkennen in de bibliotheek. Dit is vooral te wijten aan de enkele Manager klasse, waarvan ook de naam ongelukkig gekozen is. uitzonderingen.
De hulpklassen zijn vooral data klassen, met enkele
Bijkomend is de code niet gedocumenteerd en is de naamgeving van
methodes en variabelen obscuur en weinig betekenisvol.
Inleiding
7
configNow Support Classes
GIDL User Interface
Manager Approx
Config. File
IDP File
Partial Int. File
Figuur 1.1: De architectuur van congNow. (Abhinav, 2010)
De bibliotheek is ook minder geschikt om gebruikt te worden in een omgeving met meerdere gebruikers, aangezien de in- en uitvoer hier gebeurt via een bestand (Partial Int. File in guur 1.1).
Op een systeem waar deze bibliotheek door meerdere gebruikers
tegelijkertijd gebruikt wordt, moet voor elke gebruiker het in- en uitvoerbestand gekopieerd worden en hieraan een naam toegekend worden. Het gebruik van dit bestand zou vermeden moeten worden. In de bibliotheek zijn ook onvolkomenheden aanwezig die reeds duidelijk worden bij de meegeleverde demoapplicatie.
Deze zouden ook verholpen moeten worden, indien de
bibliotheek gebruikt zou worden.
1.2.2 Fietsconguratiedemo De functionaliteit die congNow (sectie 1.2.1) aanbiedt, wordt getoond met behulp van een voorbeeldapplicatie. De applicatie in guur 1.2 laat de originele versie van een etscongurator zien.
In deze etscongurator kan een ets samengesteld worden door de
gebruiker. Deze versie maakt gebruik van de congNow bibliotheek. Met behulp van kleurcodes wordt er bij de keuzevakjes aangegeven welke onderdelen verplicht zijn en welke verboden zijn. Bij de keuzelijsten worden de verboden mogelijkheden niet meer weergegeven in de lijst. Wanneer een keuzevakje aangevinkt wordt of een element uit een keuzelijst geselecteerd wordt, zal IDP opgeroepen worden om de geldigheid na te kijken.
Figuur 1.2: Originele etsconguratiedemo.
Inleiding
9
Om de gewenste functionaliteit aan te tonen, zijn een aantal schermafdrukken genomen en weergegeven in guur 1.3. Aan de hand van deze afdrukken zullen de vereisten van de te ontwikkelen API in deze masterproef duidelijk gemaakt worden. In guur 1.3(a) wordt aangetoond dat er nog meerdere keuzemogelijkheden zijn voor het frametype. Hier is ook enkel aangeduid dat een damesets gewenst is. Als dit vergeleken wordt met guur 1.3(c), zien we dat daar maar één mogelijkheid meer is en dat deze automatisch geselecteerd is. Hetzelfde kan gezegd worden over het etstype. Dit gebeurt niet in het huidige systeem congNow, aangezien IDP hier maar één keer wordt opgeroepen en sommige gevolgen van een wijziging door IDP niet direct gevonden worden. De nieuwe API moet dus, indien door de gebruiker gewenst, blijven zoeken zolang er in de vorige zoekopdracht nieuwe wijzigingen werden gevonden. In guur 1.3(b) is te zien dat alle gekoppelde componenten worden uitgeschakeld wanneer een bewerking wordt uitgevoerd. Dit maakt aan de gebruiker duidelijk dat hij even moet wachten. De versie die gebruik maakt van congNow zal in dit geval niet meer reageren, waardoor de gebruiker mogelijk denkt dat de applicatie is vastgelopen. Een vereiste van de API is dus de mogelijkheid om aan de gebruiker aan te geven dat een bewerking in uitvoering is. Figuur 1.3(d) laat dezelfde toestand zien als guur 1.3(c), maar dan aan de hand van kleurencodes. Het moet dus met de API mogelijk zijn om gemakkelijk en snel het gedrag van de componenten te veranderen. Met congNow is er maar één soort gedrag dat de componenten kunnen aannemen. Er worden ook maar twee componenten ondersteund:
javax.swing.JCheckBox
en
javax.swing.JCombobox.
In de nieuwe API kunnen er vir-
tueel oneindig veel componenten ondersteund worden door nieuwe Toolkits toe te voegen. Verder is er in guur 1.3 te zien dat de labels van de componenten vertaald zijn en dat er een undo-, redo-, check-, clear- en resetknop aanwezig is. De API moet dus voor de gekoppelde componenten een tekst kunnen weergeven die aangepast is aan de lokale taal. Ook moet de staat terug naar de begintoestand gebracht kunnen worden en moet er door de verschillende aangebrachte wijzigingen heen en weer gelopen kunnen worden. Enkel de check- en resetfunctionaliteit is beschikbaar in congNow.
1.3 Organisatie van deze tekst Het vervolg van deze masterthesis wordt opgesplitst in een aantal hoofdstukken. Om te beginnen worden de oplossingsmogelijkheden toelichten in hoofdstuk 2. Daarna worden in hoofdstuk 3 de opgeloste problemen met benodigde programma's beschreven. Hoofdstuk 4 gaat dieper in op het ontwerp van de API en verklaart en verantwoordt de onderdelen. De implementatiedetails zullen verwerkt worden in hoofdstuk 5. Informatie over het gebruik van de API en hoe deze uit te breiden wordt beschreven in hoofdstuk 6.
(a) Damesets is geselecteerd. Er zijn nog ver- (b) Alle componenten zijn uitgeschakeld tijdens schillende mogelijkheden voor het frametype. een bewerking.
(c) Een framegrootte van 190 tot 200 cm is gese- (d) Hetzelfde als (c), maar dan met gekleurde lecteerd. Het etstype en frametype kan niet checkbox componenten. meer vrij gekozen worden.
Figuur 1.3: Nieuwe versie van de etsconguratie demo.
Hoofdstuk 2 Literatuurstudie In dit hoofdstuk zullen een aantal mogelijke manieren besproken worden om de doelstelling van de masterproef te verwezenlijken. Ook wordt er gekeken naar een paar technologieën die gebruikt kunnen worden bij het implementeren van de oplossing.
2.0.1 Eclipse-Plugin Eclipse is een platform dat volledig gebaseerd is op plugins.
Dit maakt de Eclipse-
applicatie eenvoudig, maar het platform uitgebreid en complex. Aan de basis van Eclipse-plugins bevinden zich extensions en extension points. (Clayberg and Rubel, 2008) Dit is te vergelijken met een stekker en een stopcontact.
Eclipse en
ook plugins bieden extension points aan. Deze extensionpoints hebben een schema dat beschrijft aan welke voorwaarden een extension moet voldoen om te kunnen koppelen met het extensionpoint. Een plugin heeft één of meerdere extensions waarmee ze functionaliteit aanbied aan het platform. De extensions en extension points van een plugin worden verzameld in het plugin.xml bestand. Eclipse zal dit bestand voor elke geïnstalleerde plugin verwerken. Hoewel het zeer interessant zou zijn om een plugin te ontwikkelen, is dit geen ideale oplossing. Er zou gekozen moeten worden voor een specieke IDE, wat er toe leidt dat programmeurs die gebruik maken van een andere IDE hierdoor in de kou blijven staan. Bijkomend zou de onwikkelde plugin elke keer aangepast moeten worden wanneer een nieuwe versie van Eclipse uitkomt die niet helemaal compatibel is.
2.1 Annotaties Java Annotations zijn tags die kunnen toegevoegd worden aan klassen, constructoren, velden, methodes, parameters, lokale variabelen en zelfs andere annotaties. Tijdens het compileren van java code worden deze annotaties als metadata aan de klassebestanden toegevoegd.
Met behulp van de ingebouwde
Retention
annotatie kan ingesteld worden dat deze an-
notaties mee ingeladen moeten worden in de Java Virtuele Machine. Annotaties die mee in de JVM worden geladen, kunnen opgevraagd worden met behulp van de Reection API. Om dit te doen moet er eerst een beschrijving van een klasse worden verkregen. De beschrijving is een instantie van de
Class
klasse en bevat informatie over de velden en
methodes van de klasse waartoe deze beschrijving behoort. Bij een veld hoort informatie zoals de naam, het type en de zichtbaarheid van het veld, maar ook de annotaties die toegevoegd zijn aan dat veld.
Een methode bevat bijkomend nog informatie over het
aantal parameters en de types van deze parameters. Voor elke methode of veld kan gevraagd worden of ze een specieke annotatie bevatten of alle annotaties kunnen opgesomd worden. (Horstmann and Cornell, 2008) 1 2 3 4 5 6 7
@Documented @Retention ( R e t e n t i o n P o l i c y .RUNTIME) @Target ({ ElementType . FIELD}) public @ i n t e r f a c e IDPPredicate { String value ( ) ; boolean i n v e r t e d ( ) default f a l s e ; } Codefragment 2.1: Denitie van de IDPPredicate-annotatie.
Een voorbeeld van een denitie van een nuttige annotatie is gegeven in codefragment 2.1. Deze annotatie kan gebruikt worden om een veld te markeren, zodat deze gekoppeld wordt met het IDP-systeem. Een voorbeeld hiervan kan gevonden worden in codefragment 6.2. Annotaties kunnen in deze masterproef gebruikt worden om hoeveelheid te schrijven code te beperken en zo die code overzichtelijker houden.
Hierdoor zal de code beter onder-
houdbaar zijn.
2.2 JavaServer Pages JavaServer Pages technologie maakt het gemakkelijk om Java code te combineren met XML of HTML. JSP maakt gebruik van tags om pagina's op te bouwen.
Deze tags
kunnen gemengd worden met HTML tags en platte tekst. Om rechtstreeks Java code in de JSP pagina te schrijven, wordt er gebruik gemaakt van de <% en %> tags of een variant hiervan. Hoewel op deze manier om het even welk script kan geschreven worden, is dit niet onderhoudbaar en netjes voor grotere applicaties. Daarom is er een alternatief, namelijk de JavaServer Pages Standard Tag Library of JSTL in combinatie met de Unied Expression Language. Met de tags die beschikbaar zijn in de JSTL kunnen eenvoudige dynamische webpagina's gemaakt worden. De Unied Expression Language wordt gebruikt om Java objecten in te voegen in deze tags. Voor meer complexe applicaties volstaan de standaard tags niet. Gelukkig is het mogelijk om zelf nieuwe tags aan te maken. Nieuwe tags kunnen volledig in een JSP document geschreven worden, maar dat is nog steeds niet overzichtelijk. Om dit te verhelpen moet er een Tag Library Descriptor ge-
Literatuurstudie
13
schreven worden. Hierin staat de beschrijving van de tags in de bibliotheek en de Java klassen die uitgevoerd moeten worden wanneer zo een tag voorkomt in een JSP pagina. De klassen die als Tag Handler gebruikt worden, moeten de
Tag interface implementeren.
JSP tags kunnen zelf nieuwe variabelen aanmaken of bestaande variabelen in de pagina gebruiken en aanpassen. Een tag heeft ook toegang tot de tags waarin deze zich bevindt. Op deze manier kunnen verschillende tags gecombineerd worden om afhankelijk van de situatie de gewenste functionaliteit te verkrijgen.
2.2.1 JavaServer Faces Het JavaServer Faces framework is een JSP tag library waarmee een webapplicatie met een gebruikersinterface kan gemaakt worden die sterk gelijkt op die van een standaard Java applicatie. Aangezien het HTTP-protocol geen toestand bijhoudt, moet die op een andere manier bewaard worden. De applicatieserver zal voor een JSF-applicatie de toestand van de gebruikersinterface automatisch bijhouden en terug opvragen wanneer een volgende HTTP-aanvraag binnenkomt. Achterliggend worden er aan de JSF componenten convertors, validators en eventlisteners gekoppeld. De eventlisteners zijn gelijkaardig aan die van een gewone applicatie. Ze worden uitgevoerd wanneer een knop wordt ingedrukt of een waarde veranderd. De convertors en validators zijn speciek voor JSF, aangezien de invoer vanuit een HTTP-request moet omgezet worden. Een JSF-component bestaat uit meerdere JSP-tags. Omsluitend is er de component tag, die bepaald welke component er in de pagina wordt geplaatst. In die component worden verschillende tags geplaatst, waardoor de component zijn functionaliteit verkrijgt. Om de koppeling te maken met de kennisbank zou het volstaan om zelf een JSP Tag Library te maken met tags die in een JSF-component geplaatst kunnen worden. Door één van die tags dan in een JSF-component te plaatsen wordt de component met de kennisbank gekoppeld wanneer de Tag Processor de JSP-pagina omzet naar een
Servlet.
Een voorbeeld van een applicatie die gebruik zou maken van de tags is aangegeven in codefragment 2.2.
2.3 Internationalisatie Om een applicatie toegankelijk te maken voor een breed publiek, is het noodzakelijk om na te denken over internationalisatie en localisatie. Internationalisatie is het aanpassen van een applicatie zodat die kan gebruikt worden in verschillende talen. Localisatie is het aanpassen van een applicatie aan verschillende regio's. Deze aanpassingen zijn bijvoorbeeld de weergave van data, valuta of andere getallen. De standaardmanier om een Java applicatie te internationaliseren is volgens Oracle (2010b) door het gebruik van resource bundles. Dit zijn instanties van de
ResourceBundle klasse.
Een resource bundle wordt ofwel ingeladen met een .properties bestand ofwel met een subklasse van de
ListResourceBundle
klasse.
In beide gevallen wordt er een lijst van
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
< j s f : combobox>
j s f : combobox> < j s f : checkbox/> j s f : checkbox> < j s f : checkbox/> j s f : checkbox> < j s f : checkbox/> j s f : checkbox> Codefragment 2.2: Gebruik van JSF tags.
sleutel-waarde paren voorzien, waarbij de sleutel zorgt voor de identicatie van een stuk tekst en waarbij de waarde een stuk tekst voorstelt in de gewenste taal.
2.4 Service Provider Interface Om het gedrag van de gekoppelde componenten van de GUI gemakkelijk te veranderen, kan er gebruik gemaakt worden van bibliotheken die dynamisch geladen kunnen worden. Ook kunnen er door een bibliotheek toe te voegen nieuwe componenten ondersteund worden. Een mogelijkheid in Java om dynamisch te laden bibliotheken te maken is door het maken van een SPI. `Service Provider' is de term voor een dergelijke dynamische bibliotheek. De service die wordt aangeboden is een implementatie van de SPI. Een SPI bestaat uit één of meerdere interface klassen. Deze klassen zijn gekend door de applicatie die gebruik wenst te maken van de service en door alle service providers.
Als de versies van de interface klassen
verschillen kunnen er conicten optreden bij het gebruik van een service. Daarom is het belangrijk dat de interface klassen bij alle partijen identiek gedenieerd zijn. Een `Service Provider' zal concrete klassen bevatten die de klassen van de SPI implementeren.
Op deze manier kan dynamisch nieuwe functionaliteit aangeboden worden.
Om
de `Service Provider' te kunnen inladen en te kunnen beslissen of een `Service Provider' de gewenste service kan leveren, moet er een providerklasse voorzien worden. De providerklasse moet aan een paar voorwaarden voldoen. De belangrijkste voorwaarde is dat de klasse een constructor moet hebben zonder argumenten, zodat die kan geïnstantieerd worden. De providerklasse moet snel kunnen ingeladen worden door een Java Class Loader omdat bij het zoeken naar `Service Providers' alle gevonden providerklassen ingeladen worden. Er moet een methode gedenieerd zijn in de providerklasse zodat kan bepaald
Literatuurstudie
15
worden of een implementatie van die klasse de gevraagde implementatie is. Verder is de specicatie van een providerklasse vrij. Een `Service Provider' wordt ingepakt in een JAR-bestand.
In de specicatie van het
JAR-formaat is een directory vastgelegd waar services worden gedenieerd. De directory is META-INF/services. Indien er een SPI met een providerklasse
Provider
bestaat
in een pakket be.mijnProject.mijnPakket, dan moet er een tekstbestand met de naam be.mijnProject.mijnPakket.Provider in die directory geplaatst worden om een service aan te bieden die die SPI implementeert.
Veronderstel dat we een implementatie aan-
bieden met de naam ProviderImplementatie in het pakket com.dummy, dan moet in het tekstbestand de regel com.dummy.ProviderImplementatie toegevoegd worden. Als aan de
ServiceLoader wordt gevraagd om een implementatie te zoeken van de Provider-
klasse zal die in alle JAR-bestanden gaan zoeken naar de META-INF/services directory. Indien in die directory een bestand gevonden wordt met als naam de canonieke naam van de providerklasse, be.mijnProject.mijnPakket.Provider, dan zal er in dat tekstbestand een opsomming staan van alle implementerende klassen in het omsluitende JAR-bestand. Het volstaat om die klassen te instantiëren. (Oracle, 2010c)
2.5 Besluit Van een aantal mogelijkheden om een koppeling te maken van een GUI naar het IDPsysteem, zijn annotaties het eenvoudigst te implementeren. Deze technologie zit standaard in Java ingebakken, wat maakt dat het in elke Java applicatie gebruikt kan worden. Er is ook een standaardmanier om de elementen van de GUI in de moedertaal van de gebruiker om te zetten. Tot slot is een SPI een gemakkelijke manier om dynamisch ondersteuning voor nieuwe componenten toe te voegen aan een applicatie.
Ook kan hiermee het gedrag van de
gebruikte componenten veranderd worden zonder dat de applicatie opnieuw moet gecompileerd worden.
Hoofdstuk 3 Operationeel maken van de benodigde Windows programma's De API die in deze masterproef gemaakt wordt, heeft als doel het toegankelijk maken van het IDP-systeem.
Dit systeem is opgebouwd uit verschillende programma's.
Deze
programma's zijn voor het grootste deel in C++ geschreven, aangevuld met gegenereerde C code. Dit zijn native talen wat wil zeggen dat de code, geschreven in deze talen, voor elk platform opnieuw gecompileerd moet worden. Hierbij wordt mogelijk een verschillende compiler en C++ runtime library implementatie gebruikt. De programma's waren ontwikkeld en getest op het GNU/Linux platform en gecompileerd met de GNU C++ Compiler. Het systeem dat ik gebruik bij de ontwikkeling van de API is voorzien van het Microsoft Windows 7 besturingssysteem. Daarom zal ik in dit hoofdstuk beschrijven welke aanpassingen er nodig waren aan deze programma's, zodat ze ook met behulp van de Microsoft C++ Compiler gecompileerd konden worden op het Microsoft Windows platform.
3.1 ISO C en C++ standaarden De C standaard [ISO/IEC 9899:1999] en C++ standaard [ISO/IEC 14882:2003] speciëren welke taalconstructies geldig zijn en welke tot onbepaald gedrag leiden. Het is niet altijd even gemakkelijk om de situaties af te leiden die tot onbepaald gedrag leiden, aangezien de code die daarvoor verantwoordelijk is niet altijd gegroepeerd is of omdat hiervoor een grondige kennis van de standaard vereist is.
Om een deel van de fouten op te lossen
zijn er richtlijnen opgesteld. Deze richtlijnen geven aan welke naamgevingen de voorkeur hebben, welke structuur en afmeting optimaal is voor functies, enzovoort.
Niemand is
verplicht om die richtlijnen te volgen maar het is wel sterk aanbevolen. Die richtlijnen kunnen ook verschillen aangezien ze niet opgelegd zijn en onder invloed van persoonlijke voorkeuren zijn. Sommige richtlijnen worden beschreven als Design Patterns.
Een voorbeeld van een
belangrijke richtlijn en design pattern in C++ is RAII. Resource Acquisition Is Initialisation wordt gebruikt om te voorkomen dat er geheugen weglekt en dat andere bronnen
in een inconsistente toestand achtergelaten worden.
Een implementatie van RAII om
geheugenlekken te voorkomen is de Smart Pointer.
Deze pointers, verwijzingen naar
geheugenadressen, zijn slim omdat ze automatisch de gegevens, waar ze naar verwijzen, vrijgeven wanneer ze verwijderd worden. Door het gebruik van de richtlijnen wordt de code duidelijker, beter te onderhouden en dus robuuster.
3.2 GidL Een programma dat gebruikt wordt door de API is de grounder, GidL. Dit programma zet de IDP-taal om in een interne voorstelling die gemakkelijker en sneller te verwerken is. Dat is standaard ook het enige dat de grounder kan. Voor deze masterproef is er een aangepaste versie beschikbaar gesteld. Door een instelling van de grounder te veranderen, gaat deze niet langer de invoer omzetten maar hierover informatie teruggeven. De mogelijkheid om die instelling te veranderen werd toegevoegd aan de aangepaste versie. De informatie die wordt teruggegeven zijn de symbolen die gebruikt worden in het IDPbestand. Het is mogelijk om alle symbolen op te vragen door de optie mode=returnvoc te gebruiken. Dit zijn alle gebruikte types, predikaten en functies. Maar ook kunnen enkel de symbolen opgevraagd worden die gebruikt worden als invoer of uitvoer door de opties mode=returngiven of mode=returnnd te gebruiken. Bij het compileren van GidL zijn er een aantal fouten opgedoken. Als eerste was er in verschillende functies het return statement vergeten. Dit was ook het geval wanneer er een belangrijke waarde werd teruggegeven.
Ook heb ik een functie gevonden waarvan
twee argumenten dezelfde naam hadden. Na het oplossen van deze fouten werkte GidL en verkreeg ik het te verwachten resultaat.
3.3 Approx Het andere programma dat gebruikt wordt door de API is Approx. Dit is het belangrijkste programma omdat het de oplossing van een probleem berekent of toch benadert.
Om
approx uit te voeren wordt het IDP-probleem ingevoerd samen met een deel van de oplossing die reeds bekend is.
Approx zal dan bepalen of het ingevoerde deel van de
oplossing geldig is en zal ook de oplossing aanvullen. Ook bij dit programma waren er fouten bij het compileren. Zoals bij GidL ontbraken er in verschillende functies het return statement. In een functie waren er een aantal lokale variabelen dubbel gedeclareerd. Hierdoor werden niet alle bewerkingen opgeslagen. Verschillende lokale en object variabelen waren ook niet geïnitialiseerd, wat onvermijdelijk tot onbepaald gedrag leidt.
Als het programma als debug-versie gecompileerd wordt,
zijn deze fouten niet zichtbaar omdat in deze versie al het geheugen dat gealloceerd wordt zal geïnitialiseerd worden met de waarde nul. Wanneer een geoptimaliseerde versie gecompileerd wordt, zal die impliciete initialisatie weggelaten worden waardoor uit het
Windows programma's
19
niets fouten verschijnen. Dit veroorzaakte elke keer een segmentatiefout omdat pointers naar ongeldige geheugenadressen verwijzen en indices van arrays naar elementen verwijzen die niet bestaan. De laatste moeilijk te vinden fout heeft te maken met sequence points. Een sequence point is een moment of plaats in de uitvoering van code waar alle neveneffecten van voorgaande code volledig afgewerkt zijn en nog geen enkel neveneect van nakomende code begonnen is.[ISO/IEC 14882:2003] In C++ is een punt komma `;' één van de sequence points en eveneens het symbool dat een statement beëindigt.
Dat wil zeggen dat alle
neveneffecten van een statement uitgevoerd moeten zijn voordat de uitvoering van een volgend statement kan starten. De genoemde neveneffecten kunnen zo eenvoudig zijn als het wegschrijven van een berekende waarde. Fouten doen zich voor wanneer een sequence point ontbreekt tussen twee operaties die naar dezelfde variabele of hetzelfde geheugenadres schrijven. Het is onzeker welke operatie eerst zal gebeuren, maar ook of de geheugenlocatie nog geldig is na de eerste schrijfoperatie. In codefragment 3.1 ontbreken een aantal sequence points waardoor in sommige gevallen een segmentatiefout wordt veroorzaakt.
Dit is één van de functies die een crash van
getBDD en disj zijn functies en kernel, low en high zijn objecten std::vector. Het gebrek aan sequence points is een samenloop van
Approx veroorzaakte. van het type
omstandigheden.
1
r = getBDD( k e r n e l [ b1 ] , d i s j ( low [ b1 ] , low [ b2 ] ) , d i s j ( high [ b1 ] , high [ b2 ] ) ) ; Codefragment 3.1: Code zonder sequence points
Ten eerste zijn de komma's tussen de argumenten van
getBDD
en
disj
geen sequence
points. Het is dus niet gedenieerd welk argument eerst zal geëvalueerd worden. De enige zekerheid is dat alle argumenten geëvalueerd zijn voordat de functie wordt opgeroepen waarin de argumenten gebruikt worden.
Het is dus mogelijk dat een argument eerst
gedeeltelijk wordt geëvalueerd, waarna een ander argument wordt geëvalueerd en tot slot de evaluatie van het eerste argument verder wordt afgewerkt. In dit geval werd de code zodanig gecompileerd dat eerst beide waarden uit waarden uit
high
en daarna werd de functie
mag dit doen omdat de argumenten van
disj
disj
low
werden opgehaald, daarna beide
twee maal uitgevoerd.
De compiler
geëvalueerd zijn voordat de functie wordt
opgeroepen en bovendien zorgt dit voor een betere prestatie omdat hierdoor de code van de
disj functie na de eerste uitvoering zich nog in de processorcache bevindt en dus geen
tweede keer moet ingeladen worden. Ten tweede zijn
low
en
high
geen lokale variabelen. Daardoor kan
die rechtstreeks of onrechtstreeks wordt opgeroepen door
disj
disj
of een functie
de variabelen aanpassen.
Op zichzelf is dit niet erg, zolang de aanpassingen niet interfereren met elkaar. Maar in
disj
worden de vectoren
low
en
high
aangepast door hier nieuwe elementen aan toe te
voegen. De derde en laatste meespelende factor is de
std::vector
is een containerklasse met veranderlijke grootte.
klasse.
Deze klasse
Wanneer er niet genoeg ruimte is in
een object van die klasse dan zal het object een groter stuk geheugen alloceren.
De
specicatie van
std::vector stelt dat de elementen in de container opgeslagen zijn
in een aaneensluitend geheugenblok.
Als de vector nieuw geheugen moet alloceren en
er is geen ruimte meer vrij vlak achter het gebruikte blok, dan zal een volledig nieuw blok aangemaakt worden en alle bestaande elementen zullen verplaatst worden naar het nieuwe, grotere blok. Het geheugen van het oude blok wordt daarna vrijgegeven. In de documentatie is vermeld dat als dit gebeurt, dan alle externe referenties naar elementen in de vector ongeldig worden. Dit is logisch aangezien die referenties verwijzen naar het vrijgegeven geheugenblok. 1 2 3
namespace s t d { class vector { c o n s t _ r e f e r e n c e o p e r a t o r [ ] ( s i z e _ t y p e n ) const ;
4 5
};
6
/
∗
code
weggelaten
∗/
7 8 9
};
/
∗
code
weggelaten
∗/
Codefragment 3.2: Declaratie van de index operator van de vector klasse
De index operator van de vector klasse is gedeclareerd zoals te zien is in codefragment 3.2. Dit wil zeggen dat het een functie is die als argument een getal aanvaardt van het type
size_type.
De terugkeerwaarde is een constante referentie naar het gevraagde element
in de vector en de functie is een constante functie, wat wil zeggen dat de functie geen veranderingen zal aanbrengen aan het object waarop het opgeroepen is. Het belangrijke gedeelte is de terugkeerwaarde. Ze is constant, wat wil zeggen dat ze niet mag aangepast worden door de code die de operator heeft opgeroepen. De terugkeerwaarde is ook een referentie. Dit betekent dat het een verwijzing is naar het adres waar het element van de vector zich bevindt. Als de drie voorgaande factoren in rekening worden gebracht is het duidelijk dat de code in codefragment 3.1 voor ongedenieerd gedrag kan zorgen. Namelijk, de adressen van de elementen worden eerst opgehaald, aangezien de compiler dit als meest optimale volgorde ziet.
De functie
disj
wordt een eerste keer opgeroepen.
Deze functie gaat onder
bepaalde voorwaarden een element toevoegen aan één van de vectoren. Als er in de vector te weinig geheugen beschikbaar is, zal deze nieuw geheugen alloceren waardoor de bestaande elementen een nieuw adres toegewezen kunnen krijgen. Bij de tweede oproep van
disj
worden de oude adressen gebruikt om de argumenten door te geven. De oude
adressen zijn op dat moment ongeldig en zullen afhankelijk van externe factoren ofwel willekeurige gegevens bevatten ofwel niet meer tot de adresruimte van het proces behoren. Het programma zal daardoor crashen met een segmentatiefout of verder werken met een willekeurige waarde, wat in ieder geval geen betrouwbaar resultaat oplevert. Deze fout kan opgelost worden door één van de drie factoren te elimineren. De implementatie van de vector klasse kan niet veranderd worden. Dat hoeft ook niet want met die klasse op zich is niets verkeerd. Het is zeer moeilijk voor mij om het algoritme zodanig te veranderen dat de vectoren niet meer aangepast worden in de
disj
functie, aangezien
het algoritme voor mij een black box is. Gelukkig is de eerste factor gemakkelijk aan te
Windows programma's pakken. Door de argumenten van de
21 getBDD functie toe te wijzen aan tijdelijke variabelen,
waardoor de code opgesplitst wordt in verschillende statements, zullen de juiste sequence points toegevoegd worden. Nadat alle vernoemde fouten uit de broncode van Approx verwijderd zijn, kan het programma snel en stabiel uitgevoerd worden. Voorheen functioneerde dit programma enkel wanneer het gecompileerd was met debug instellingen en zonder optimisaties. Dat maakte het programma ongeveer vijf keer trager. Hierdoor was het testen van de API lastig en langdurig.
3.4 Besluit De GNU C++ Compiler, die gebruikt werd door de ontwikkelaars van GidL en Approx, genereerde werkende machinecode voor de C++ code met ongedeniëerd gedrag. Hierdoor hebben de ontwikkelaars de in dit hoofdstuk vernoemde fouten waarschijnlijk niet opgemerkt en bleven deze fouten onopgelost. Ik heb de ontwikkelaars achteraf gecontacteerd om de oplossingen voor de gevonden fouten aan hen door te geven. Door deze fouten op te lossen, kon de API sneller getest worden. Dit heeft de productiviteit sterk verhoogd.
Hoofdstuk 4 Ontwerp In dit hoofdstuk zal ik de architectuur van de API toelichten, vertrekkende van een overzicht en gaande naar details over de onderdelen van de verschillende modules. Om de API op een degelijke manier te kunnen implementeren heb ik eerst een ontwerp gemaakt. Hierdoor is de API van in het begin logisch en gestructureerd opgebouwd. Dit draagt bij tot de exibiliteit en uitbreidbaarheid van de API. Ontwerp- en implementatieproblemen worden op deze manier ook sneller ontdekt, waardoor de implementatietijd verkort wordt. De API is opgebouwd uit verschillende modules. Deze modules bestaan elk uit verschillende onderdelen, die er samen voor zorgen dat een module zijn taak kan vervullen. Door het opsplitsen in modules kunnen eventuele programmeerfouten sneller ontdekt en opgelost worden. Het is ook gemakkelijker om aparte modules te testen in plaats van het volledige systeem. De mogelijkheid bestaat immers om een module af te zonderen door alle andere hiermee verbonden modules te vervangen door stubs. Een stub is een stuk code die uitwendig dezelfde functionaliteit heeft en dezelfde resultaten teruggeeft als de module die vervangen wordt.
De stub moet zo eenvoudig mogelijk
gemaakt worden, zodat kan verzekerd worden dat deze zelf geen fouten bevat. Door ervoor te zorgen dat er enkel in de geteste module eventuele fouten kunnen optreden, is het mogelijk om deze module individueel te testen. Dit is een groot voordeel van een modulaire opbouw. Opmerking: De guren die in dit hoofdstuk bij elke module gebruikt zijn voor de verdui-
delijking van de inhoud van de module, volgen een bepaalde conventie. De onderdelen die deel uitmaken van de betreende module zijn voorgesteld door witte rechthoeken of, indien ze zich in een ander onderdeel bevinden, door gekleurde rechthoeken. Deze rechthoeken hebben puntige hoeken. De onderdelen die niet tot de module behoren zijn voorgesteld door gekleurde rechthoeken met afgeronde hoeken. De verbindingspijlen tussen onderdelen van de module zullen de onderdelen raken.
De
verbindingspijlen tussen een onderdeel van de module en een externe module, waarmee deze communiceert, raken de onderdelen niet.
Gebruikersinterface Keuze 1 2 Keuze 1 2 3
Opties Optie 1 Optie 2 Optie 3
Verzend
Opnieuw
API Analyse
Configuratie
Kennisbank abstractie
Kennisbanksysteem Approx
Kennisbanken
GidL
Figuur 4.1: Overzicht van de architectuur van de API
Ontwerp
25
4.1 Overzicht De API moet de verbinding verzorgen tussen een kennisbank en een applicatie die gebruik maakt van de API. Op het hoogste niveau bestaat de API uit drie modules (guur 4.1): de kennisbank abstractie module, de conguratie module en de analyse module. De kennisbank abstractie module zorgt voor een gemakkelijk in Java te gebruiken voorstelling van de kennisbank. Wijzigingen aan de staat van de kennisbank zullen via deze module uitgevoerd worden en de applicatie zal een melding krijgen wanneer de staat van de kennisbank is gewijzigd. De conguratie module zal adapters aanmaken om componenten van de GUI te koppelen met het kennisbanksysteem. Hierdoor hoeft niet voor elke soort van grasche component een koppelingsklasse geschreven te worden door de applicatieprogrammeur zelf. De analyse module zal aan de hand van annotaties aan de juiste GUI componenten een adapter uit de conguratie module koppelen. In het eenvoudigste geval bestaat de applicatie enkel uit een gebruikersinterface.
Er
wordt dan volledig vertrouwd op de functionaliteit van de API zelf om de applicatie te laten werken. Het is louter voor het opstarten en initieel opbouwen van de interface dat de applicatie zelf logica zal bevatten.
De analyse module is dan de enige module die
aangesproken zal worden door de applicatie. Deze module is dan ook de eenvoudigste en minst exibele module van de drie. Voor meer geavanceerde applicaties zal er vanuit de applicatiecode meer interactie zijn met de twee andere modules van de API. Wanneer er bijvoorbeeld dynamisch nieuwe grasche componenten aangemaakt en gekoppeld moeten worden, dan is de conguratie module nodig.
De conguratie module levert immers adapters voor de verschillende grasche
componenten. Die adapters verbinden volledig automatisch de gewenste component met de kennisbank. Indien een zo groot mogelijke controle over de API gewenst is, moet de kennisbankabstractielaag rechtstreeks aangesproken worden. Hiermee kunnen direct veranderingen worden aangebracht aan het op te lossen probleem. Daarenboven is het ook mogelijk om te wachten op berichten over aanpassingen aan het op te lossen probleem.
4.2 Kennisbankabstractie De grootste en belangrijkste module is de kennisbankabstractiemodule (guur 4.2). Het hoofddoel van deze module is het doorspelen van gestructureerde gegevens tussen de applicatie en de kennisbank.
Dit gebeurt op een zodanige manier dat de applicatie geen
kennis hoeft te hebben over de manier waarop de kennisbank de gegevens verwerkt. Bijkomend zorgt deze module ook voor het verbergen of oplossen van tekortkomingen aan de kennisbank zelf, waarvan een aantal in hoofdstuk 5 zullen behandeld worden.
Progress listener Processing listener
Conflict listener
Conflict listener Conflict listener Conflict listener
Completion listener
Processing listener Processing listener Processing listener
Applicatie
Completion listener Completion listener Completion listener
Solution
- Undo - Redo - Reset
Change State writer
Model Stateless
State listener Change listener Change listener
Stateful
State processor
Solution state
Symbol
Symbol
State
Symbol
Symbol
State
Undo stack
Kennisbanksysteem
Model reader
Figuur 4.2: Overzicht van kennisbank abstractie module
State reader
Ontwerp
27 Model Symbol <>
Instance
*
1
*
Type
StatefulSymbol <>
Predicate
Function
Figuur 4.3: Klassediagram van de model klassen
4.2.1 Model Zoals eerder vermeld is de oplossingstoestand opgebouwd uit de toestanden van toestandsvolle symbolen. Deze symbolen, samen met de toestandsloze symbolen, vormen het model van een probleem. De symbolen in het model zijn alle symbolen die beschikbaar zijn via de API voor een bepaald probleem.
Het probleem bevat mogelijk nog meer symbolen
maar die kunnen enkel intern in de kennisbank gebruikt worden. Die symbolen zullen dus niet terug te vinden zijn in het model aangezien ze niet gebruikt kunnen worden. Door het model in een apart onderdeel te plaatsen is het mogelijk om dit model te delen tussen verschillende oplossingen voor hetzelfde probleem. Eens geladen wordt het model niet meer aangepast. Dit is interessant in situaties waar meerdere instanties van dezelfde applicatie uitgevoerd worden, bijvoorbeeld op een server. Er is op de server op elk moment maar één model object nodig per probleem, ongeacht het aantal oplossingen dat gezocht wordt voor dat probleem. In guur 4.3 is de structuur van de modelklassen weergegeven. Elke entiteit die uit het IDP-systeem wordt opgehaald is een
Symbol. Een Symbol heeft een naam en kan hiermee Symbol zijn: Type, Instance en StatefulSymbol.
opgevraagd worden. De subklassen van
Type-klasse stelt de Instance-objecten voor De
datatypes voor uit de kennisbank. elk
Type
en deze
Instance-objecten
Er bestaan verschillende hebben dat
Type
als da-
tatype. Een
StatefulSymbol
is een
Symbol
waarvoor een toestand kan bijgehouden worden. Dit
gebeurt door middel van parameters die elk van een bepaald van deze parameters een specieke
Instance
`tuple'. Dit is één bepaalde toestand van het De
Predicate-klasse
Type zijn.
Wanneer voor elk
wordt gekozen, wordt er gesproken van een
StatefulSymbol.
is een symbool dat een toestand kan bevatten aan de hand van
verschillende parameters. De
Function-klasse
geeft een verband tussen een waarde van
de parameters en de resulterende waarde die hiervoor wordt teruggegeven. Deze klasse is echter niet volledig geïmplementeerd in de API, omdat er geen praktisch voorbeeld was
dat een functie gebruikte om de kennisbank te congureren. Indien een zou gebruikt worden in de kennisbank, kan deze ingekapseld worden met
Function toch een Predicate.
4.2.2 Toestand Eén van de problemen met de IDP-kennisbank is dat deze zelf geen staat kan bijhouden. De kennisbank wordt opgeroepen waardoor deze de nieuwe staat zal berekenen. Zodra dit gebeurd is, wordt als antwoord de nieuwe toestand teruggegeven en beëindigt de kennisbank zichzelf. Daarom is het nodig dat de API de staat van het probleem beheert. De staat van een oplossing wordt bijgehouden in het`Solution state' onderdeel, of oplossingstoestand, dat geïmplementeerd is door de gelijknamige
SolutionState
klasse. Van
dit onderdeel wordt er één exemplaar als huidige toestand bijgehouden in het `Solution' onderdeel. De oplossingstoestand is opgebouwd uit de toestanden van elk toestandsvol symbool. Wijzigingen aan deze toestanden gebeuren via de oplossingstoestand door middel van `Change'-objecten. `Change'-objecten die elk een wijziging voorstellen, worden samengenomen in stappen. Een stap is een verzameling van logisch samenhangende wijzigingen. De toestand wordt enkel als consistent beschouwd wanneer een gehele stap is uitgevoerd. Het is daarom niet zinvol en ook niet mogelijk om individuele wijzigingen ongedaan te maken en opnieuw uit te voeren, dus worden deze bewerkingen altijd uitgevoerd op stappen. Een stap wordt aangemaakt als reactie op een wijziging door de applicatie. De kennisbank zal de net aangemaakte stap verder aanvullen met wijzigingen om de toestand consistent te houden. Dit is dus een kwestie van actie en reactie. De applicatie of gebruiker initieert altijd een wijziging en de kennisbank reageert hierop. Dit gebeurt in één bewerking. De oplossingstoestand zal ook antwoorden met change objecten. Op deze manier wordt aangegeven welke wijzigingen er zijn gebeurd door een operatie die uitgevoerd is op de oplossingstoestand. Dit komt voor bij eender welke bewerking die meerdere wijzigingen als gevolg kan hebben, bijvoorbeeld het herinitialiseren van de toestand of ongedaan maken van een stap. In guur 4.4 wordt getoond hoe een aanpassing aan de toestand in zijn werk gaat. De applicatie maakt in een stap met behulp van `Change'-objecten een tijdelijke toestand. Deze toestand zal door de `Change'-objecten aangepast zijn. Daarna zal het kennisbanksysteem op de tijdelijke toestand wijzigingen uitvoeren om de nieuwe toestand consistent te maken. Alle wijzigingen die in deze stap zijn uitgevoerd, worden door middel van de StateListeners gemeld aan de applicatie.
4.2.3 Gegevensverwerking Om het model in te laden in een applicatie wordt er van een maakt. Deze
ModelReader
gebruik ge-
ModelReader is speciaal ontworpen voor een bepaald kennisbanksysteem, in
dit geval het IDP-kennisbanksysteem. Het is mogelijk om voor een ander kennisbanksys-
Ontwerp
29
Applicatie
Change
State listener Change listener Change listener
Change
Solution
SolutionState
SolutionState
SolutionState
Stap
Change
Kennisbanksysteem
Figuur 4.4: Principe van het veranderen van de toestand in een stap.
teem eenModelReader te implementeren zolang de modelvoorstelling van die kennisbank compatibel is met de modelvoorstelling van de API. Gelijkaardig aan de model reader bestaat er ook een
StateReader.
de toestand inlezen en verwerken, afkomstig van de kennisbank.
StateProcessor StateWrite.
de kennisbank opgeroepen door de geschreven door middel van een De
StateReader, StateWrite
en
StateProcessor
De state reader zal Hiervoor werd eerst
en de toestand naar de kennisbank
zijn ook speciaal ontworpen voor de
IDP-kennisbank. Ook bij deze onderdelen is het mogelijk ze te implementeren voor een andere kennisbank. Uiteraard is dit enkel het geval als de modelvoorstelling compatibel is. Het valt op te merken dat de verwerking van de toestand opgesplitst is in drie onderdelen.
Dit is niet strikt noodzakelijk maar bevordert de testbaarheid en exibiliteit.
De
verklaring hiervoor zal verder toegelicht worden in sectie 5.2.
4.2.4 Solution Het Solution onderdeel (in guur 4.2) komt overeen met de Solution klasse in de API. Deze klasse houdt de kennisbankabstractiemodule bij elkaar. Het houdt de huidige toestand, de verschillende Listeners en de processor met zijn reader en writer bij. Deze klasse zorgt er ook voor dat al deze componenten als een mooi geheel samenwerken. Als wijzigingen binnenkomen van de applicatie zal Solution zorgen dat deze wijzigingen worden toegepast op de toestand en dat het kennisbanksysteem deze wijzigingen controleert en bijstuurt. Ook zal deze component de Listeners oproepen om de wijzigingen te melden.
4.2.5 Event listeners Om de resultaten van een bewerking te verkrijgen wordt er van
EventListeners
gebruik
gemaakt. Er zijn verschillende soorten eventlisteners beschikbaar in de API, elk om een bepaald type van gebeurtenissen op te vangen. Om een gebeurtenis te kunnen opvangen moet de desbetreende event listener geregistreerd worden op het Solution onderdeel. Een eerste soort eventlistener is de
StateListener.
Deze soort wordt enkel geregistreerd
op de symbolen waarvoor dat gevraagd wordt en zal dus ook enkel opgeroepen worden wanneer de toestand van één van die symbolen gewijzigd wordt.
Omwille van perfor-
mantieredenen heb ik deze ontwerpbeslissing genomen. Het is namelijk zo dat regelmatig meerdere symbolen gewijzigd worden in één bewerking. Indien bij elke wijziging alle state listeners zouden opgeroepen worden, waarbij elke state listener voor zichzelf moet opzoeken of het van toepassing is op het gewijzigde symbool, zou dit leiden tot een grotere systeembelasting. Dit is zeker het geval wanneer het aantal listeners toeneemt. De andere soorten eventlisteners worden altijd opgeroepen.
De impact hiervan is veel
kleiner aangezien de methodes van deze listeners hoogstens één maal worden opgeroepen
Ontwerp
31
per bewerking. Bijkomend is er ook geen rechtstreeks verband tussen een instantie van één van deze listeners en een bepaalde wijziging. Door het gebruik van eventlisteners moet er niet gewacht worden op de resultaten van een bewerking. De bewerking wordt gestart en de applicatie die de methode van de API heeft opgeroepen kan onmiddellijk verder werken. Wanneer de resultaten beschikbaar zijn, zal de applicatie verwittigd worden door middel van de event listeners die het voorheen geregistreerd heeft. Dit wordt ook wel asynchrone uitvoering genoemd. Het tegenovergestelde van asynchrone uivoering is synchrone uitvoering en deze techniek maakt gebruik van de terugkeerwaarde. Een bewerking wordt ook hier gestart door een methode op te roepen maar de applicatie kan pas verder werken wanneer de bewerking klaar is en de methode terugkeert met het resultaat als terugkeerwaarde. De applicatie heeft zolang de bewerking wordt uitgevoerd geen mogelijkheid om verder te werken. De doelomgeving van de API zijn applicaties met een grasche gebruikersinterface. Een GUI is ook gebaseerd op gebeurtenissen. Wanneer de gebruiker interageert met de applicatie, zullen er events gegenereerd worden. Deze events worden in een wachtrij gezet om één voor één afgehandeld te worden in de context van de event thread.
Alle event
listeners die hierop geregistreerd zijn worden eveneens uitgevoerd in de context van de event thread. Het is niet onbegrijpelijk dat het een slechte zaak is indien één van die event listeners een synchrone bewerking zou starten waarvan niet geweten is wanneer die zal eindigen. Zolang de bewerking in uitvoering is, kunnen alle events in de wachtrij van de GUI niet verder verwerkt worden. Dit heeft als gevolg dat de GUI niet meer zal reageren op interacties van de gebruiker, waardoor de gebruiker de indruk krijgt dat de applicatie is vastgelopen. Een vuistregel voor grasche gebruikersinterfaces is dat de reactietijd onder één tiende van een seconde moet blijven om een vloeiende gebruikerservaring te behouden. Indien dit niet mogelijk is, moet volgens Nielsen (1994) de wachttijd door middel van een visuele hint kenbaar gemaakt worden. Uit tests met de kennisbank heb ik gemerkt dat deze voor meer complexe problemen een uitvoeringstijd heeft in de ordegrootte van een seconde of meer. Om te verhinderen dat de gebruikerservaring geschaad wordt, is het noodzakelijk de bewerkingen op de kennisbank asynchroon uit te voeren.
Dit is, bovenop de exibiliteit, een belangrijke reden om te
kiezen voor event listeners.
4.3 Conguratie De conguratiemodule (guur 4.5) zal adapters aan de API leveren om GUI componenten te koppelen met de kennisbankabstractiemodule. Op zichzelf is dit een kleine module. Ze bestaat uit de
Configurator-klasse en een aantal klassen die samen een SPI vormen.
De
congurator zal de juiste `Service Provider' uitzoeken, om hiervan de aangeboden `Service' in te laden. Hoe dit gebeurt, zal toegelicht worden in sectie 5.5. De providerklasse van de SPI die beschikbaar wordt gesteld in deze module, is de klasse. Elke implementatie van de
Toolkit-klasse
Toolkit-
heeft een naam, zodat de congurator
Gebruikersinterface Keuze 1 2 Keuze
Kennisbank abstractie
Applicatie
1 2 3
Opties Optie 1 Optie 2 Optie 3
Verzend
Opnieuw
Configurator Service Provider Interface Service Providers Service Exception handler
Exception handler factory
Adapter Radio button
Adapter factory Radio button
Adapter Checkbox
Adapter factory Checkbox
Adapter Dropdown list
Adapter factory Dropdown list
Toolkit
Figuur 4.5: Overzicht van de conguratiemodule
Ontwerp
33
op basis hiervan de gewenste versie kan uitkiezen. ries aanleveren: de
Een toolkit kan twee soorten facto-
ProcessingExceptionHandlerFactory
en de
AdapterFactory.
Van
beide soorten factories kunnen er verschillende implementaties gemaakt worden. Elke implementatie kan adapters produceren die bevestigd kunnen worden op een bepaald soort van objecten. Een
AdapterFactory produceert Adapters voor één soort van de grasche componenten.
Voor elke door de `Service Provider' ondersteunde soort van grasche componenten bestaat er een factory. Wanneer een symbool of een tuple gekoppeld moet worden met een grasche component kan aan de juiste factory gevraagd worden om hiervoor een adapter te maken. De aangemaakte adapter zal er voor zorgen dat de toestand van het symbool of het tuple wordt weergegeven door de grasche component en dat de invoer vanuit de grasche component de toestand van het symbool of tuple zal wijzigen. Hierdoor is het niet meer nodig dat een programmeur deze objecten manueel verbindt. Dit vermindert sterk de hoeveelheid conguratiecode. De
ProcessingExceptionHandlerFactory
produceert
ProcessingExceptionHandlers.
Hoewel deze klasse niet de term `adapter' bevat, kunnen de instanties ervan toch bevestigd worden aan een soort van objecten. In de kennisbankabstractiemodule kunnen verschillende fouten of
Exceptionss
den waarvoor die module geen eenduidige oplossing kan bieden.
Daarom worden die
Exceptionss
optre-
doorgegeven naar de code die de kennisbankabstractiemodule oproept. Zo-
als vermeld in sectie 4.2.5 kan de API asynchroon gebruikt worden.
Als de methode
die een bewerking gestart heeft reeds is teruggekeerd, is het onmogelijk om via deze weg nog een
Exception
door te geven. Net daarom kan een
ProcessingExceptionHandler
gebruikt worden. Doordat deze handlers als adapter kunnen toegevoegd worden aan bijvoorbeeld een onderdeel van de GUI, is het mogelijk om foutboodschappen in deze GUI te integreren. In een serveromgeving kan het ook interessant zijn om een handler te voorzien die kan bevestigd worden aan een logboek of een database. De reden hiervoor is dat de
Exceptionss die door deze handlers opgevangen worden, kunnen duiden op een verkeerde conguratie van de server of een programmeerfout. Er zijn verschillende grasche toolkits beschikbaar voor Java.
Daarom is het moeilijk
om voor elke grasche toolkit een implementatie te maken van de adapters.
Door het
gebruik van de SPI wordt het gemakkelijk om ondersteuning voor bijkomende grasche toolkits toe te voegen. Eens een serviceprovider voor een bepaalde toolkit ontwikkeld en verpakt is in een JAR, kan dit archief gewoon in de juiste directory geplaatst worden. De congurator zal dan op zoek gaan naar een implementatie met de gegeven naam.
4.4 Analyse De analysemodule (guur 4.6) bouwt verder op de automatisering van de conguratiemodule. Daar waar de conguratiemodule de gevraagde symbolen gaat verbinden met een grasche component, zal de analysemodule in de gebruikersinterface zoeken naar grasche componenten die verbonden moeten worden met een symbool.
De
Parser
zal met be-
public class Klasse { @IDPAdapterAction("clear") private Button btnClear;
Gebruikersinterface
@IDPAdapterAction("undo") @IDPAdapterVariant("undo") private Button btnUndo; @IDPPredicate("predikaat1") @IDPTuple("keuze1") @IDPAdapterVariant("color") private Checkbox pred1k1;
Keuze 1 2 Keuze
Java Compiler
Kennisbank abstractie
Injecteren 1 2 3
Opties
@IDPPredicate("predikaat1") @IDPTuple("keuze2") @IDPAdapterVariant("color") private Checkbox pred1k2;
Optie 1 Optie 2 Optie 3
@IDPPredicate("predikaat2") private Dropdown pred2; Verzend
/* layout code */
Opnieuw
/* program code */ }
Parser Annotaties @IDPPredicate @IDPTuple Java Reflection API
Configuratie
Parse
Adapter
@IDPAdapterVariant Translator @IDPAdapterAction
Figuur 4.6: Overzicht van de analyse module.
hulp van annotaties bepalen welke grasche componenten aan welke symbolen gekoppeld moeten worden. De annotaties die voorzien zijn in de analysemodule worden samen met de programmacode ingeladen in de JVM. Door middel van reectie worden alle componenten in een klasse overlopen. Als een dergelijke component één van de annotaties van deze module bevat dan wordt de annotatie met zijn parameters opgevraagd. Deze parameters vertellen de parser aan welk symbool de component gekoppeld moet worden. Een symbool kan een cryptische naam hebben in de IDP-taal.
Om deze naam om te
zetten in een naam en beschrijving die ook door een leek verstaanbaar is, wordt er gebruik gemaakt van een
Translator.
Niet alleen wordt dit omgezet in leesbare vorm, het is ook
mogelijk om dit aan te passen aan de taal waarin de applicatie gebruikt wordt. Hierdoor kan de applicatie ook in vreemde talen gebruikt worden.
4.5 Besluit In dit hoofdstuk werden de globale architectuur en haar modules en onderdelen opgesomd en toegelicht. Een goed ontwerp zorgt voor minder onverwachte problemen bij de implementatie en voor meer gestructureerde code.
Ontwerp
35
Het werkelijke ontwerpproces is echter niet altijd helemaal rechtlijnig geweest.
Bij de
implementatie zijn er hier en daar toch nog bedenkingen geweest die ertoe hebben geleid dat ik het ontwerp moest herzien. Deze bedenkingen hebben er voor gezorgd dat in de meeste gevallen de API op het gebied van exibiliteit of gebruiksvriendelijkheid verbeterd is. Bij het ontwerpen van de API werd er gebruik gemaakt van verschillende `Design Patterns'. `Design patterns' zijn ontwerpvormen die dikwijls terugkomen bij het maken van software.
Deze ontwerpvormen zijn volgens Holzner (2006) bedoeld om het ontwerp te
verbeteren en meer exibel te maken alsook de code onderhoudbaar te maken. Enkele voorbeelden van design patterns die gebruikt zijn bij het onwerp zijn `Decorator Pattern', `Factory Pattern' en `Adapter Pattern'.
Hoofdstuk 5 Implementatie In dit hoofdstuk worden de implementatiedetails besproken van een aantal onderdelen van de API. Sommige klassen en methodes hebben een niet voor de hand liggende implementatie. Door deze implementaties te beschrijven wordt er een duidelijker beeld gecreëerd van de werking van de API.
5.1 UndoStack Om de Stappen bij te houden in de
SolutionState-klasse
heb ik gebruik gemaakt van
twee stacks: een stack die de stappen bijhoudt om ongedaan te maken en een andere die de stappen bijhoudt om opnieuw uit te voeren. Bij het gebruik van de API zal bij elke wijziging door de gebruiker een nieuwe stap aangemaakt worden. Als het op te lossen probleem omvangrijk is of als de gebruiker veel bewerkingen uitvoert, zullen de opgestapelde stappen meer en meer geheugen innemen. Aangezien geheugen beperkt is en de applicatie zal falen wanneer dit geheugen op is, moet voorkomen worden dat alle ooit gemaakte stappen worden bijgehouden. Om dit probleem op te lossen heb ik de van
Deque,
UndoStack-klasse
gemaakt.
Deze klasse is een implementatie
een interface uit de JRE-bibliotheek. In de
UndoStack-klasse
kan opgegeven
worden hoeveel stappen er worden bijgehouden. De stappen die worden bijgehouden zijn natuurlijk de meest recent toegevoegde stappen. Wanneer de maximale grootte van de stack bereikt is, zal bij het toevoegen van een nieuw element het oudste element verwijderd worden. Zoals de naam van de
UndoStack-klasse aangeeft is dit een ideale implementatie voor een
stack met bewerkingen die ongedaan gemaakt kunnen worden. Deze klasse wordt in de API ook enkel gebruikt voor de stack met stappen die ongedaan gemaakt kunnen worden. Het is niet nodig om deze klasse ook te gebruiken voor de stack met stappen die opnieuw uitgevoerd kunnen worden aangezien deze stack nooit meer stappen kan bevatten dan het aantal in de undostack. Een stap moet immers eerst ongedaan gemaakt worden voordat deze terug kan uitgevoerd worden. Door de grootte van de undostack te beperken wordt dus automatisch de grootte van de redostack beperkt.
5.2 Processing Het processing gedeelde van de kennisbankabstractiemodule dat instaat voor de verwerking van de toestand wordt geïmplementeerd in drie klassen: een reader, een writer en een processor. Om de
Solution-klasse
te kunnen voorzien van instanties van de process-
orklasse wordt er gebruik gemaakt van een factoryklasse. Voor elk kennisbanksysteem wordt er een processor, een reader, een writer en een factory geïmplementeerd.
De reader, writer en processor worden standaard door de factory
gebruikt. Het is mogelijk om de reader en writer te vervangen door een andere implementatie. Voor deze klassen kan er een decorator gemaakt worden die extra functionaliteit toevoegt. (Holzner, 2006) Een mogelijke decorator kan de gegevens die door de writer naar de processor wordt geschreven onderscheppen en in een logboek wegschrijven. Hetzelfde is mogelijk voor een reader. Dit kan handig zijn wanneer een nieuwe kennisbanksysteem is ontwikkeld en er onverwacht gedrag optreedt. Er kan gekeken worden naar de in- en uitvoer van het kennisbanksysteem om te weten te komen waar het onverwacht gedrag vandaan komt. Het is mogelijk dat er een fout zit in de implementatie van het kennisbanksysteem, de reader of de writer. Door de gegevens te onderscheppen met een decorator moet geen van de drie voorgaande onderdelen gewijzigd worden en kunnen de gegevens toch verkregen worden.
5.3 Solution Het `Solution' onderdeel bestaat uit twee klassen. De eerste klasse is de
Solution-klasse.
Dit is een abstracte klasse die alle methodes denieert die nodig zijn voor de implementatie van het `Solution'-onderdeel (zie sectie 4.2.4 voor de denitie van het solutiononderdeel). Een aantal triviale methodes zijn al geïmplementeerd in deze basisklasse, zoals de methodes om listeners toe te voegen en te verwijderen.
Door gebruik te maken van deze
abstracte klasse is het gemakkelijk om achteraf, door middel van het `Decorator Pattern', fuctionaliteit toe te voegen zonder dat de gebruiker van de klasse hier iets van merkt. Om de
Solution
klasse te kunnen gebruiken moet er eerst een concrete subklasse ge-
maakt worden die alle abstracte methodes van de solution klasse implementeert.
SolutionImpl-klasse voorzien, die door Solution-klasse kan geïnstantieerd worden.
een dergelijke implementerende statische methode in de
In deze klasse wordt de toestand bijgehouden door middel van
Er is
middel van een
SolutionState-objecten.
Er is slechts één huidige toestand in de Solution klasse maar deze wordt voorgesteld door twee
SolutionState-objecten.
Eén van die objecten is de
inputState welke de toestand
voorstelt die als gevolg van acties door de gebruiker tot stand is gekomen. De andere is de
solutionState
die de volledige toestand bevat. Dit zijn de acties die door de gebruiker
uitgevoerd zijn en de reacties van het kennisbanksysteem op die acties. Oorspronkelijk werd enkel deze laatste toestand bijgehouden, wat wilde zeggen dat deze twee klassen zeer eenvoudig waren. Ze zorgden er enkel voor dat de kennisbankabstractiemodule netjes bij elkaar werd gehouden en delegeerden de handelingen naar de klassen die daarvoor dienen. Bij tests bleek dit niet te werken zoals gewenst was. Wanneer er
Implementatie
39
geprobeerd werd om een gekend symbool terug te verwijderen, werd dit elke keer terug toegevoegd door het kennisbanksysteem. Dit is logisch aangezien het kennisbanksysteem op zoek gaat naar een toestand met zo weinig mogelijk vrijheden. Om dit probleem te verduidelijken volgt er een voorbeeld uit de etsconguratiedemo (sectie 1.2.2). Wanneer een damesets met framegrootte 190 - 200 cm aangeduid is, zal enkel nog het etstype Grandma bike en het kadertype Centurion Boulevard tot een geldige oplossing leiden. Als de gebruiker wenste de keuze van een damesets terug ongedaan te maken, zou deze door het kennisbanksysteem terug toegevoegd worden. Volgens de regels van de etsconguratiekennisbank kan er immers enkel een damesets gemaakt worden van het etstype Grandma bike en het kadertype Centurion Boulevard met framegrootte 190 - 200 cm. Het is dus noodzakelijk om de beperkingen die afkomstig zijn van de applicatie afzonderlijk bij te houden en de toestand van de oplossing hierop te baseren. Het kennisbanksysteem mag dus geen beperkingen toevoegen aan de toestand afkomstig van de gebruiker maar omgekeerd moet die toestand wel invloed hebben op de beperkingen die zullen toegevoegd worden door het kennisbanksysteem.
5.3.1 changeState De
changeState-methode
van de
SolutionImpl-klasse
zal, op vraag van de applicatie,
de toestand van de oplossing voor het conguratieprobleem aanpassen met de gewenste
SymbolChange-objecten.
Daarna wordt het kennisbanksysteem uitgevoerd om die te
controleren en hierop verdere wijzigingen toe te passen.
Dit principe werd reeds kort
toegelicht in sectie 4.2.2.
changeState-algoritme in zijn werk gaat. Eerst worden twee kopies gemaakt van de permanente inputState. De permanente states zijn de toestandsobjecten die worden bijgehouden door de Solution-klasse als oplossingstoeIn guur 5.1 wordt weergegeven hoe het
stand. De twee kopies worden gebruikt om wijzigingen op toe te passen in de loop van het
changeState-algoritme.
Als er iets misgaat, zoals een conict dat optreedt, worden de
kopies weggegooid en is er niets veranderd. Het voordeel is ook dat de hele operatie wordt gezien als één stap. Elke wijziging op de tijdelijke toestanden wordt uitgevoerd in een aparte stap. Dit is niet anders mogelijk omdat dit in sequentie moet gebeuren. Door achteraf alle wijzigingen in die toestand op te vragen en in één keer uit te voeren op de permantente toestanden, zullen deze als één stap worden gezien. De applicatie wordt ook van deze wijzigingen op de hoogte gebracht. Merk op dat in de loop van het algoritme de voor de gegeven
inputState.
solutionState niet de juiste oplossing bevat
Dit wordt door het uitvoeren van het kennisbanksysteem
opgelost. De gedetailleerde werking van het algoritme zal uitgelegd worden aan de hand van de implementatiecode.
inputState
solutionState
Stap
+ dupliceren
+ dupliceren
inputState
solutionState
Stap Change Applicatie
inputState
State listener
solutionState
Stap Change
inputState
Kennisbanksysteem
solutionState
Change
Change
inputState
solutionState
Figuur 5.1: Implementatie van het veranderen van de toestand in een stap.
Implementatie 1 2 3 4 5 6
41
public void changeState ( C o l l e c t i o n <SymbolChange> symbolChanges ) throws ParserException , IOException , ReaderException , WriterException { onProcessingStarted ( ) ; try { StateProcessor processor = getFactory ( ) . getStateProcessor ( ) ; Codefragment 5.1: De changeState methode, deel 1.
changeState-methode is gegeven in codefragmethode zal de ProcessingListeners verwittigen dat
De declaratie en het eerste deel van de ment 5.1. De eerste regel van de een bewerking gestart is.
Hierna wordt een try blok geopend waarin de rest van de
methode staat. In het nally blok dat hierop volgt worden de
ProcessingListeners ver-
wittigen dat een bewerking geëindigd is. Indien dit niet zo wordt gedaan, zouden bij het optreden van een
Exception
de
ProcessingListeners
niet verwittigd worden. De API
garandeert dat de start en stop verwittigingen in paren voorkomen, dus is het try nally blok noodzakelijk. In het try blok wordt eerst een de
ProcessingFactory.
StateProcessor object opgevraagd uit
Deze processor dient om het kennisbanksysteem op te roepen en
wordt later in deze methode gebruikt.
S o l u t i o n S t a t e newInputState = this . i n p u t S t a t e . c r e a t e D e r i v e d S t a t e ( Arrays . a s L i s t ( symbolChange ) , S o l u t i o n S t a t e . INFINITE_SIZE ) ; S o l u t i o n S t a t e n e w S o l u t i o n S t a t e = this . s o l u t i o n S t a t e ; for ( SymbolChange change : symbolChanges ) { i f ( change . g e t A c t i o n ( ) . e q u a l s ( ChangeAction . Remove ) ) { n e w S o l u t i o n S t a t e = this . i n p u t S t a t e ; } } newSolutionState = newSolutionState . createDerivedState ( symbolChanges , S o l u t i o n S t a t e . INFINITE_SIZE ) ; newSolutionState = newSolutionState . createDerivedState ( Arrays . a s L i s t ( symbolChange ) , S o l u t i o n S t a t e . INFINITE_SIZE ) ;
1 2 3 4 5 6 7 8 9 10 11 12
Codefragment 5.2: De changeState methode, deel 2.
In codefragment 5.2 wordt eerst een tijdelijk stateobject aangemaakt dat gebaseerd is
inputState. Dit tijdelijk object wordt dan aangepast met de wijziging die als argument met de changeState methode werd meegegeven. Als laatste wordt een tijdelijk
op de
stateobject aangemaakt dat afhankelijk van de actie van de wijzigingen gebaseerd is op ofwel de
inputState
ofwel de
solutionState.
Zoals eerder vermeld wil het kennisbanksysteem een gekend symbool niet meer verwijde-
inputState als Remove-actie voorkomt
Remove-actie
ren. Daarom wordt er vertrokken van de
er een
in de wijzigingen. Zelfs als er geen
in de wijzigingen kan ook ver-
trokken worden van de
voorkomt
inputState, alhoewel het kennisbanksysteem dan meer werk moet
verrichten. Gezien het kennisbanksysteem de beperkende factor is op vlak van performantie, moet deze zo weinig mogelijk onnodig belast worden. Om die reden wordt uitsluitend bij een
Remove-actie
de
inputState
gebruikt.
wordt aangepast met de gevraagde wijziging.
Ook het tijdelijk
solutionState-object
try { u p d a t e S o l u t i o n S t a t e ( newSolutionState , p r o c e s s o r , i t e r a t i o n P o l i c y ) ; } catch ( C o n f l i c t i n g S t a t e E x c e p t i o n e ) { onConflictFound ( e ) ; C o n f l i c t S t a t u s s t a t u s = null ; i f ( g e t C o n f l i c t H a n d l e r ( ) != null ) { s t a t u s = g e t C o n f l i c t H a n d l e r ( ) . h a n d l e C o n f l i c t ( this , newInputState , newSolutionState , p r o c e s s o r ) ; onConflictHandled ( status ) ; } i f ( s t a t u s != C o n f l i c t S t a t u s . Resolved ) { return ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Codefragment 5.3: De changeState methode, deel 3.
In codefragment 5.3 wordt met de methode
updateSolutionState het kennisbanksysteem
opgeroepen. Dit is een interne private methode van deze klasse. Het eerste argument van deze methode is het tijdelijke state object dat zal aangepast worden met de wijzigingen die het kennisbanksysteem aangeeft. Het tweede argument is de processor die in codefragment 5.2 is aangemaakt en waarmee het kennisbanksysteem zal opgeroepen worden. Als laatste argument wordt er een
iterationPolicy
doorgegeven aan de methode.
Approx, het programma dat gebruikt wordt als kennisbanksysteem, zal nieuwe beperkingen zoeken en de nodige wijzigingen hiervoor teruggeven wanneer het opgeroepen wordt. Helaas worden niet altijd alle beperkingen gevonden. De reden hiervoor is dat het kennisbanksysteem anders nog trager zou zijn. Approx kan door middel van een parameter ingesteld worden om langer of minder lang te zoeken, hoewel dit bij een aantal tests weinig invloed leek te hebben op het resultaat. De
updateSolutionState-methode
zal herhaaldelijk het kennisbanksysteem oproepen,
zolang er bij de voorgaande oproep nieuwe wijzigingen worden teruggegeven. Om niet in een oneindige lus te belanden en om een compromis te vinden tussen uitvoeringstijd en nauwkeurigheid wordt de
IterationPolicy-interface
gebruikt.
Een implementatie van
deze interface kan bepalen of de lus moet afgebroken worden, zelfs als niet alle wijzigingen zijn gevonden. Bij het zoeken naar beperkingen door het kennisbanksysteem kunnen conicten optre-
ConflictingStateException gooien. opgevangen en zal een ConflictHandler
den. Indien dit gebeurt, zal de StateProcessor een Zoals te zien is in codefragment 5.3 wordt dit
opgeroepen worden als die beschikbaar is. Als de conicthandler niet in staat is om het conict op te lossen of als er geen handler voorhanden is, wordt de bewerking afgebroken en zal de methode terugkeren. In dit geval zal de toestand van het solutionobject niet gewijzigd zijn, aangezien de wijzigingen op tijdelijke
SolutionState-objecten uitgevoerd
werden. Indien er geen conict is opgetreden of het conict is afgehandeld, zal de methode verder uitgevoerd worden in codefragment 5.4. De code in dit fragment zal de wijzigingen permanent maken door ze op te slaan in de twee stateobjecten van de
Solution-klasse.
Implementatie
43
int s t e p s = newInputState . countUndoSteps ( ) ; newInputState . undoChangeSteps ( s t e p s ) ; C o l l e c t i o n <SymbolChange> inputChanges = newInputState . redoChangeSteps ( s t e p s ) ; this . i n p u t S t a t e . createChangeStep ( true ) ; this . s o l u t i o n S t a t e . createChangeStep ( true ) ; for ( SymbolChange change : inputChanges ) { this . i n p u t S t a t e . changeState ( change ) ; this . s o l u t i o n S t a t e . changeState ( change ) ; } C o l l e c t i o n <SymbolChange> s o l u t i o n C h a n g e s = S o l u t i o n S t a t e . getChangeList ( this . s o l u t i o n S t a t e , n e w S o l u t i o n S t a t e ) ; for ( SymbolChange change : s o l u t i o n C h a n g e s ) { this . s o l u t i o n S t a t e . changeState ( change ) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Codefragment 5.4: De changeState methode, deel 4.
Eerst wordt er bepaald welke aanpassingen er moeten gebeuren aan de wordt gedaan door eerst in de tijdelijke
inputState
inputState.
Dit
alle wijzigingen ongedaan te maken.
Daarna worden die wijzigingen opnieuw gemaakt. Het resultaat van die methode is een verzameling van wijzigingen die gebeurd zijn bij het opnieuw uitvoeren. Na het verzamelen van de wijzigingen wordt er eerst voor gezorgd dat in beide permanente states met een nieuwe, lege stap gewerkt wordt. de nieuwe stap van de permanente
Hierna wordt de verzameling van wijzigingen op
inputState en solutionState toegepast. Vervolgens solutionState. Dit
wordt het verschil opgevraagd tussen de permanente en de tijdelijke
geeft als resultaat een verzameling van wijzigingen die nodig zijn om de toestand van de permanente state identiek te maken aan de toestand van de tijdelijke state. wijzigingen worden ook toegepast op de permanente 1 2 3 4 5 6 7 8 9 10 11
}
Deze
solutionState.
i f ( this . i n p u t S t a t e . hasCurrentChanges ( ) | | this . s o l u t i o n S t a t e . hasCurrentChanges ( ) ) { this . s o l u t i o n S t a t e . createChangeStep ( f a l s e ) ; this . i n p u t S t a t e . createChangeStep ( f a l s e ) ; } n o t i f y S t a t e L i s t e n e r s ( inputChanges , solutionChanges , f a l s e ) ; } finally { onProcessingEnded ( ) ; } return ;
Codefragment 5.5: De changeState methode, deel 5.
Het laatste stuk code van de
changeState
methode is die van codefragment 5.5. In het
eerste stuk van dit fragment wordt getest of de permanente toestand gewijzigd is. dit het geval is, wordt in beide stateobjecten een nieuwe stap aangemaakt. Door
Als
false
als argument door te geven, gebeurt dit zelfs als de huidige stap van het stateobject geen wijzigingen bevat.
De reden hiervoor is het gesynchroniseerd houden van beide
stateobjecten. Indien geen van beide stateobjecten een wijziging bevat dan zal voor geen
van beiden een stap gemaakt worden. Wanneer beide stateobjecten wel wijzigingen bevat dan zal dit hetzelfde eect hebben als wanneer de methode met
true
opgeroepen zou
worden. Het is dus enkel wanneer één van de twee state objecten geen wijzigingen bevat dat voor dit object een lege stap zal toegevoegd worden aan de undostack. Als dit niet zou gebeuren dan zou bij het ongedaan maken één van de twee state objecten één stap achter lopen op de andere. Dit zou tot ongewenst gedrag van de API leiden. Het laatste statement in het try blok zal zorgen dat eerst de wijzigingen van de invoer en dan die van het kennisbanksysteem doorgegeven worden aan de statelisteners. Daarna volgt het nally blok dat de processinglisteners op de hoogte zal brengen van het einde van de bewerking. De methode zal hierna eindigen.
5.3.2 undoChangeSteps De tweede methode die ik zal bespreken is de
undoChangeSteps methode.
Deze methode
zal één of meerdere stappen ongedaan maken. Oorspronkelijk was deze methode zeer eenvoudig. Deze delegeerde de oproep door naar de
solutionState
en verwittigde de applicatie hiervan.
Door het probleem dat zich voordeed bij het verwijderen van een keuze (begin sectie 5.3), moest een tweede toestand, de
inputState,
bijgehouden worden. Hierdoor is ook deze
methode een stuk complexer geworden. De details van het algoritme worden toegelicht aan de hand van implementatiecode. 1 2 3 4 5 6 7 8 9 10 11 12 13 14
public void undoChangeSteps ( int numberOfSteps ) { i f ( numberOfSteps > countUndoSteps ( ) ) { throw new I l l e g a l A r g u m e n t E x c e p t i o n ( " numberOfSteps " ) ; } onProcessingStarted ( ) ; try { for ( int i = numberOfSteps ; i > 0 ; −− i ) { S o l u t i o n S t a t e i n p u t S t a t e = this . i n p u t S t a t e . c r e a t e D e r i v e d S t a t e ( null , S o l u t i o n S t a t e . INFINITE_SIZE ) ; S o l u t i o n S t a t e s o l u t i o n S t a t e = this . s o l u t i o n S t a t e . c r e a t e D e r i v e d S t a t e ( null , S o l u t i o n S t a t e . INFINITE_SIZE ) ; C o l l e c t i o n <SymbolChange> inputChanges = this . i n p u t S t a t e . undoChangeSteps ( 1 ) ; this . s o l u t i o n S t a t e . undoChangeSteps ( 1 ) ; Codefragment 5.6: De undoChangeSteps methode, deel 1.
Het eerste deel van de methode is terug te vinden in codefragment 5.6. Zoals te zien is in dit fragment zal er een lus uitgevoerd worden die voor elke stap die ongedaan gemaakt moet worden een aantal bewerkingen zal uitvoeren.
inputState en de solutionState gemaakt. Daarna wordt bij beide toestanden één stap ongedaan gemaakt, waarvan enkel bij de inputState de wijzigingen worden bijgehouden. Deze wijzigingen worden aan het einde van de methode gebruikt om de StateListeners te verwittigen.
Om een stap ongedaan te maken wordt eerst een tijdelijke kopie van de
Implementatie C o l l e c t i o n <SymbolChange> r e v e r s e I n p u t C h a n g e s = S o l u t i o n S t a t e . getChangeList ( this . i n p u t S t a t e , i n p u t S t a t e ) ; S o l u t i o n S t a t e n e w S o l u t i o n S t a t e = this . s o l u t i o n S t a t e . c r e a t e D e r i v e d S t a t e ( reverseInputChanges , S o l u t i o n S t a t e . INFINITE_SIZE ) ; C o l l e c t i o n <SymbolChange> s o l u t i o n C h a n g e s = S o l u t i o n S t a t e . getChangeList ( s o l u t i o n S t a t e , n e w S o l u t i o n S t a t e ) ; n o t i f y S t a t e L i s t e n e r s ( inputChanges , solutionChanges , true ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13
45
}
} } finally { onProcessingEnded ( ) ; }
Codefragment 5.7: De undoChangeSteps methode, deel 2.
In het tweede deel van de methode, dat te zien is in codefragment 5.7, wordt er gezocht naar de wijzigingen die gebeurd zijn. Hiervoor wordt eerst het verschil berekend van de permanente en de tijdelijke
inputState.
Let op, de permanente toestand is het eerste argument, dus zijn dit de wijzigingen om van de permanente toestand over te gaan naar de tijdelijke. Dit lijkt misschien vreemd aangezien de tijdelijke toestand een kopie is van de permanente toestand wanneer deze nog niet aangepast was en de permanente toestand nu reeds aangepast is. Deze omgekeerde wijzigingen, vandaar de naam
reverseInputChanges,
zijn nodig om de wijzigingen die
door het kennisbanksysteem gemaakt zijn te kunnen extraheren. Bij de volgende operatie wordt er een tweede tijdelijke solutionstate gemaakt. Deze toestand wordt aangepast met de reverseInputChanges welke eigenlijk de voorwaartse wijzi-
inputChanges reverseInputChanges de
gingen zijn. Dit is immers de undomethode. De
zijn dus de wijzigingen
om de stap ongedaan te maken en de
wijzigingen om de stap
opnieuw uit te voeren. De
getChangeList-methode
over te gaan.
geeft de wijzigingen om van de eerste naar de tweede state
Daarom wordt de tweede toestand eerst geplaatst en de eerste toestand
tweedes geplaatst bij de
getChangeList-methode
zodat de wijzigingen verkregen wor-
inputChanges en solutionChanges worden uiteindelijk gebruikt om de StateListeners te verwittigen. Door true als laatste argument van de notifyStateListeners methode te gebruiken, worden eerst de solutionChanges gemeld en dan pas de inputChanges. De wijzigingen den om achteruit te gaan en dus de stap ongedaan te maken.
De
worden dus als het ware achterstevoren afgespeeld. Gelijkaardig als in de
changeState
methode worden ook hier de
ProcessingListeners
verwittigd dat de bewerking beëindigd is.
5.4 Solution in een andere Thread Zoals in sectie 4.2.5 vermeld is, is het noodzakelijk om het kennisbanksysteem in een andere thread te laten uitvoeren dan die van de gebruikersinterface. Deze functionaliteit
SolutionImpl-klasse zou deze klasse te groot en complex maken. Daarom werd de ThreadedSolution-klasse ontwikkeld die deze funtionaliteit toevoegt aan een Solution-object door gebruik te maken het `Decorator Pattern'. Zo is het mogelijk om ook andere implementaties te gebruiken in samenwerking met de ThreadedSolutioninbouwen in de
klasse. Deze klasse maakt gebruik van een die de
ExecutorService-interface
ExecutorService
klasse. Dat is één van de klassen
implementeert en komt uit de JRE-bibliotheek. Door
gebruik te maken van deze klasse wordt het eenvoudig om taken asynchroon uit te voeren. Belangrijk om te weten bij de
SolutionImpl-klasse is dat deze niet voorzien is om tegelijk
in verschillende threads gebruikt te worden. Als dit wel gebeurt dan is het mogelijk dat deze klasse in een inconsistente toestand gebracht wordt. Daarom zal de klasse het
Solution-object
ThreadedSolution-
dat hierin gebruikt wordt aangespreken via één thread. Alle
bewerkingen zullen in een wachtrij geplaatst worden en die thread zal één voor één de bewerkingen uitvoeren die in de wachtrij staan. De
ExecutorService-interface maakt het mogelijk om te wachten op het resultaat van een ThreadedSolution-klasse gebruik
opdracht. Hier wordt door sommige methodes van de
van gemaakt. Dit zijn de methodes die belangrijke resultaten teruggeven en bedoeld zijn om synchroon uit te voeren.
5.5 Congurator De
Configurator klasse zorgt ervoor dat wanneer een object wordt aangeboden, hiervoor ConfiguratorImpl subklas-
een geschikte adapter wordt teruggegeven. Bijkomend kan de
se aan de hand van de naam van een toolkit deze inladen vanop het bestandssysteem. De naam wordt gekozen door de ontwikkelaar die de
Toolkit-interface
implementeert. Deze
naam moet duidelijk maken voor welke GUI-toolkit deze implementatie gemaakt is en wat de implementatie doet. De standaard implementatie die meegeleverd is met de API draagt bijvoorbeeld de naam Swing - default.
5.5.1 ndBestForObject 1 2 3 4 5 6 7 8
public AdapterFactory f i n d B e s t F o r O b j e c t ( Object o b j e c t , S t r i n g v a r i a n t ) { AdapterFactory f a c t o r y = null ; i f ( o b j e c t != null ) { for ( Class > c u r r e n t = o b j e c t . g e t C l a s s ( ) ; c u r r e n t != null && f a c t o r y == null ; current = current . getSuperclass ()) { f a c t o r y = this . s e l e c t ( c u r r e n t , v a r i a n t ) ; } Codefragment 5.8: De ndBestForObject methode, deel 1.
In codefragment 5.8 staat de interne functie van de
Configurator-klasse die de opzoeking
zal doen. Wanneer een object wordt aangeboden aan de congurator, zal deze de klasse
Implementatie
47 findBestForObject-methode zal voor deze klassen een AdapterFactory zoeken. Hiervoor zal
van het object opvragen. van zijn bovenliggende
De
klasse of één de lus in het
codefragment zorgen. In de lus wordt net zolang gezocht tot een factory gevonden is of de klasse geen bovenliggende klasse meer heeft, wat wil zeggen dat op dat moment de waarde van
Object
current de
klasse is. In het beste geval bestaat er een factory voor de klasse van het object.
Dit heeft als gevolg dat de volledige functionaliteit van dat object zal aangesproken kunnen worden. Bij het vinden van een factory voor een bovenliggende klasse van het object zal enkel de functionaliteit aangesproken kunnen worden die in die bovenliggende klasse gedenieerd is. 1 2 3 4 5 6 7 8 9 10 11 12 13
}
i f ( f a c t o r y == null ) { for ( Class > c l a z z : this . g e t S u p p o r t e d C l a s s e s ( ) ) { i f ( c l a z z . i s I n s t a n c e ( o b j e c t ) && c l a z z . i s I n t e r f a c e ( ) ) { f a c t o r y = this . s e l e c t ( c l a z z , v a r i a n t ) ; break ; } } } } else { f a c t o r y = this . s e l e c t ( null , v a r i a n t ) ; } return f a c t o r y ;
Codefragment 5.9: De ndBestForObject methode, deel 2.
Indien er voor geen enkele bovenliggende klasse van het object een factory gevonden is, zal de
factory variabele na deze lus nog altijd de waarde null bevatten.
Er wordt in dit
geval in codefragment 5.9 verder gezocht naar een interface die geïmplementeerd wordt door het object. In deze lus worden alle door de toolkit ondersteunde klassen overlopen. Als er een interface wordt gevonden, dan wordt de factory opgehaald. Hierbij maakt het niet uit welke interface er wordt gevonden aangezien niet kan afgeleid worden welke de meeste functionaliteit zal kunnen aanbieden. De enige gegevens die kunnen opgevraagd worden over de interface zijn het aantal methodes, de namen van de methodes en de argumenten van de methodes die gedeclareerd zijn in die interface. Deze gegevens zijn geen aanduiding voor het al dan niet nuttig zijn van de interface. Er wordt verondersteld dat in de toolkit enkel die interfaces ondersteund worden waarvoor een nuttige factory kan afgeleverd worden.
5.5.2 ndToolkit De methode die gebruikt wordt om een toolkit te zoeken, met een naam die overeenkomt met het gegeven patroon, is de
findToolkit-methode.
Deze methode zal op zoek
gaan naar implementaties voor de SPI op de locaties die vermeld zijn in de `ClassPath' omgevingsvariabele. De locaties in de classpath omgevingsvariabele zijn directories waar klassebestanden staan met de implementatie van Java-klassen.
1 2 3 4 5 6 7 8
private s t a t i c T o o l k i t f i n d T o o l k i t ( Pattern toolkitName , S t r i n g l i b P a t h ) throws IOException , NoSuchToolkitException { T o o l k i t t o o l k i t = null ; S e r v i c e L o a d e r l o a d e r ; ClassLoader parent = Thread . currentThread ( ) . getContextClassLoader ( ) ; URLClassLoader u r l L o a d e r = new URLClassLoader ( g e t J a r s ( parent , l i b P a t h ) , parent ) ; l o a d e r = S e r v i c e L o a d e r . l o a d ( T o o l k i t . class , u r l L o a d e r ) ; Codefragment 5.10: De ndToolkit methode, deel 1.
Codefragment 5.10 beschrijft het eerste deel van de
findToolkit-methode.
Er wordt
ClassLoader opgevraagd die de klassen kan laden uit de JRE-bibliotheek. Deze als ouder toegevoegd aan een nieuwe URLClassLoader, waardoor niet alleen klas-
eerst een wordt
sen geladen kunnen worden vanuit de gegeven URL's, maar ook vanuit de locaties die gekend zijn door de ouder. De
getJars-methode,
die hierna zal besproken worden, zal
de URL's ophalen van alle JAR's die in de directory staan, aangegeven door het argument. Als laatste in dit fragment wordt een implementaties gaat zoeken van de 1 2 3 4 5 6 7 8 9 10 11 12 13 14
}
libPath
ServiceLoader opgevraagd die naar alle
Toolkit-interface.
for ( T o o l k i t tk : l o a d e r ) { try { i f ( toolkitName . matcher ( tk . getName ( ) ) . matches ( ) ) { t o o l k i t = tk ; break ; } } catch ( Throwable t ) { } } i f ( t o o l k i t == null ) { throw new NoSuchToolkitException ( toolkitName . t o S t r i n g ( ) ) ; } return t o o l k i t ;
Codefragment 5.11: De ndToolkit methode, deel 2.
Alle gevonden toolkit-implementaties worden overlopen in codefragment 5.11. Voor elke implementatie wordt de naam vergeleken met het patroon dat als eerste argument is meegegeven aan de
findToolkit-methode.
Indien een implementatie met overeenkomstige
naam wordt gevonden, wordt deze als resultaat van de methode teruggegeven. In het andere geval wordt een
NoSuchToolkitException
gegooid die aangeeft dat er geen toolkit
kan gevonden worden dat voldoet aan het patroon.
5.5.3 getJars De
getJars-methode
classpath.
(codefragment 5.12) zal de URL's van alle JAR's zoeken in de
Eerst wordt de directory opgevraagd die aangegeven is met het
libPath-
Implementatie 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
49
private s t a t i c URL [ ] g e t J a r s ( ClassLoader context , S t r i n g l i b P a t h ) throws IOException { ArrayList j a r s = new ArrayList ( ) ; for (URL u r l : C o l l e c t i o n s . l i s t ( c o n t e x t . g e t R e s o u r c e s ( l i b P a t h ) ) ) { try { i f ( u r l . toURI ( ) . getScheme ( ) . e q u a l s ( " j a r " ) ) { continue ; // u n a b l e t o l o a d JARs f r o m w i t h i n JARs . } F i l e f i l e = new F i l e ( u r l . toURI ( ) ) ; if ( f i l e . isDirectory ()) { File [ ] children = f i l e . l i s t F i l e s (); for ( F i l e f : c h i l d r e n ) { i f ( f . getName ( ) . endsWith ( " . j a r " ) ) j a r s . add ( f . toURI ( ) . toURL ( ) ) ; } } } catch ( URISyntaxException e ) { } } return j a r s . toArray ( new URL[ j a r s . s i z e ( ) ] ) ; } Codefragment 5.12: De getJars methode.
argument. Dit gebeurt in de context van de gegeven
ClassLoader door de getResources-
methode. Het is noodzakelijk dat
libPath
een relatief pad opgeeft omdat
getResources
enkel
hiermee overweg kan. Opzoekingen gebeuren altijd relatief ten opzichte van de bestaande classpath. Door de API te verpakken in een JAR kan opgegeven worden dat de directory waarin de JAR zich bevindt tot de classpath behoort. De URL's die teruggegeven worden door de directories in de
libPath-directory.
getResources-methode zijn alle bestanden en getJars-methode worden
In het volgende stuk van de
deze bestanden gelterd zodat enkel de JAR-bestanden overgehouden worden. Als eerste ltering wordt er gekeken of het gebruikte protocol van de URL gelijk is aan het `jar'protocol. Dit protocol wordt gebruikt om een bestand aan te geven dat zich in een JAR Bijvoorbeeld jar://http://server.domein.org/directory_van_archief/ archief.jar!/directory_in_archief/bestandsnaam is de URL van een bestand met padnaam /directory_in_archief/bestandsnaam dat zich in een JAR bevindt. Deze JAR is zelf terug te vinden op de locatie http://server.domein.org/directory_van_ archief/archief.jar. (Oracle, 2010d) bevindt.
Er wordt verondersteld dat het bestand dat voorgesteld wordt door de URL een JAR is. Als dit JAR-bestand genest is in een ander JAR-bestand, zal de inhoud van het binnenste JAR-bestand niet geladen kunnen worden. De
URIStreamHandler implementatie voor het
`jar'-protocol is immers niet in staat om geneste JAR's te verwerken. Bestanden in een JAR worden daarom overgeslagen. Vervolgens wordt de URL omgezet in een
File-object.
Van dit object kan opgevraagd
worden of het een bestand of een directory is. We zijn enkel geïnteresseerd in directories,
meerbepaald de directory die opgegeven is door
libPath.
Daarom wordt er in deze
directory op zoek gegaan naar alle bestanden in het JAR-formaat. extentie.
Vandaar de `.jar'-
Als we zo een bestand hebben gevonden, wordt deze toegevoegd aan de lijst
van archieven. De lijst van archieven bevat alle JAR's die mogelijk een `Service Provider' zijn.
5.6 Besluit In dit hoofdstuk zijn de implementaties van een aantal belangrijke methodes en klassen besproken. Dit wil niet zeggen dat de andere onderdelen van de API minder belangrijk zijn.
Moest dit wel het geval zijn, dan werden die beter weggelaten.
De reden dat
een implementatie werd toegelicht in dit hoofdstuk is dat deze naar mijn mening niet vanzelfsprekend is en een verklaring nodig heeft. Andere onderdelen van de API zouden voldoende begrepen moeten kunnen worden door de broncode te bekijken. Ik heb de broncode met zorg uitgewerkt zodat deze zo goed als mogelijk te begrijpen is. Er is daarom aandacht besteed aan de structuur van methodes en de naamgeving van variabelen en methodes. De variabelen en methodes hebben allemaal zinvolle namen gekregen, volgens de conventies van de Java taal, waardoor deze zelfverklarend zijn. Op plaatsen waar niet altijd duidelijk is wat de code doet, werd ook documentatie aan de code toegevoegd. Bijkomend is er voor elke publieke methode Javadoc documentatie voorzien zodat de functie van deze methodes kan opgezocht worden.
Hoofdstuk 6 Gebruik en uitbreiding van de API In dit hoofdstuk zal aan de hand van een aantal voorbeelden duidelijk gemaakt worden op welke manier de API gebruikt kan worden. De beschreven voorbeelden zullen niet alle mogelijkheden van de API dekken. Het is eerder de bedoeling om een idee te geven van de mogelijkheden, zodat men meteen aan de slag kan met de API. Voor de veeleisende gebruikers bestaat de mogelijkheid om de API aan te passen. Ook hiervan zullen een aantal voorbeelden gegeven worden. De voorbeelden die gegeven worden, zijn gebaseerd op de etsconguratiedemo (sectie 1.2.2.
Oorspronkelijk maakte deze applicatie gebruik van de congNowbibliotheek
(sectie 1.2.1).
De applicatie is aangepast zodat deze nu gebruik maakt van de in deze
masterproef ontwikkelde API. De etsconguratiedemo laat zien hoe een ets zou kunnen gecongureerd worden. Deze applicatie zou gebruikt kunnen worden door een etswinkel om een ets samen te stellen. Eerst zal er in sectie 6.1 een voorbeeld gegeven worden hoe een applicatie kan aangepast worden aan een lokale taal.
Daarna worden de verschillende methodes beschreven en
verklaard om de API te gebruiken. De eerste en de tweede methode werkt met behulp van annotaties. Hiermee kan een statische GUI automatisch worden gekoppeld aan IDP. In de eerste methode in sectie 6.2.1 wordt de parser op een snelle en gemakkelijke manier met standaardinstellingen aangemaakt. De tweede methode in sectie 6.2.2 zal de programmeur zelf toelaten de parser te congureren met de functionaliteit die hij kiest. Om een dynamische GUI te koppelen wordt er in sectie 6.3 een derde methode uitgelegd. Als laatste manier om de API te gebruiken, wordt er in sectie 6.4 aangegeven hoe het model en de toestand van de kennisbank rechtstreeks kan opgevraagd en aangepast worden. Het laatste deel van het hoofdstuk zal toelichten hoe een eigen toolkit kan gemaakt worden. Met een dergelijke toolkit kunnen nieuwe GUI componenten ondersteund worden of nieuwe funtionaliteit aangeboden worden voor reeds ondersteunde componenten.
6.1 Translator De
Translator-interface
is een onderdeel uit de API waarmee de symbolen uit de ken-
nisbank een zinvolle naam en beschrijving kunnen krijgen.
In sectie 2.3 is aangegeven
dat de standaardmanier om in Java vertalingen aan te bieden gebruik maakt van resource bundles. Daarom is er in de API een implementatie van de
ResourceBundle-object de ResourceBundleTranslator genoemd.
zien die aan de hand van een implementatie is
1 2 3 4 5 6 7 8 9 10 11 12
SelGen . Male . name = SelGen . Male . d e s c = SelGen . Female . name SelGen . Female . d e s c
Heren Kader voor een h e r e n f i e t s . = Dames = Kader voor een d a m e s f i e t s .
S e l H e i . name = SelHei . desc = S e l H e i . C150160 . name S e l H e i . C160170 . name S e l H e i . C170180 . name S e l H e i . C180190 . name S e l H e i . C190200 . name
Grootte De g r o o t t e van het kader . = 150 cm − 160 cm = 160 cm − 170 cm = 170 cm − 180 cm = 180 cm − 190 cm = 190 cm − 200 cm
Translator-interface
symbolen zal vertalen.
voorDeze
Codefragment 6.1: Deel van een properties bestand met de vertaling van symbolen en tuples.
Om gebruik te maken van de
ResourceBundleTranslator
klasse is een `properties'-
bestand nodig met de vertalingen van de symbolen. In codefragment 6.1 staat een knipsel van een properties bestand. In dit properties bestand staan de vertalingen voor de tuples van twee symbolen en ook voor het tweede symbool zelf.
De syntax van een vertaling
bestaat uit een label en een waarde. De waarde is de vertaling en het label is het symbool waarvoor de vertaling gebruikt moet worden. Het label bestaat voor een symbool uit de naam van het symbool, gevolgd door name voor de vertaling van de naam van het symbool en desc voor de vertaling van een beschrijving van het symbool. Deze twee waarden worden gescheiden door een punt `.'. Voor het label van een tuple wordt tussen de naam van het symbool en name of desc de naam van het tuple toegevoegd. Indien het tuple uit meerdere waarden bestaat zullen deze waarden gescheiden zijn door een underscore `_'. Dit is het geval voor predikaten met meerdere parameters. De symbolen en tuples waarvoor geen vertaling is gegeven zullen door de translator niet vertaald worden. De naam en beschrijving van deze symbolen en tuples zullen gelijk zijn aan de symbolische naam. Dit is de naam van het symbool zoals het vermeld is in het IDP-model. Indien enkel de naam vertaald is, zal de beschrijving dezelfde waarde krijgen als de naam. De naam van het propertiesbestand moet voldoen aan een aantal regels om herkend te worden door de ResourceBundle.(Oracle, 2010b) Het begin van de bestandsnaam is een prex die dient om onderscheid te maken tussen resource bundles van verschillende pakketten. Deze naam wordt gevolgd door een underscore `_', de lettercode van de te gebruiken taal en de `.properties'-extentie.
Om een standaardvertaling te maken voor
Gebruik en uitbreiding van de API
53
talen die niet ondersteund worden, kan een properties bestand gemaakt worden waarbij in de naam de underscore en de lettercode is weggelaten. Voor het voorbeeld is de `bike'-prex.
De bestandsnaam voor de standaardvertaling is
dan bike.properties en voor Nederlandse vertaling is dit bike_nl.properties. Om deze bestanden te kunnen laden, moeten ze voor de
ClassLoader beschikbaar zijn als resource.
6.2 Annotatie parser De gemakkelijkste manier om de API te gebruiken is met behulp van annotaties. Componenten die men wil koppelen met de kennisbank kunnen dan met één of meerdere annotaties geannoteerd worden. Het enige wat dan nog moet gebeuren is de conguratie van de verschillende modules van de API.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public c l a s s BikeScreenAnnotated extends javax . swing . JFrame { @IDPAdapterAction ( " complete " ) private javax . swing . JButton jButtonCheck ; @IDPAdapterAction ( " r e s e t " ) private javax . swing . JButton jButtonReset ; @IDPPredicate ( " SelGen " ) @IDPTuple ( "Male" ) @IDPAdapterVariant ( " c o l o r " ) public javax . swing . JCheckBox @IDPPredicate ( " SelGen " ) @IDPTuple ( " Female " ) @IDPAdapterVariant ( " c o l o r " ) public javax . swing . JCheckBox @IDPPredicate ( " S e l H e i " ) private javax . swing . JLabel
33
@IDPPredicate ( " S e l H e i " ) @IDPTuple ( "C150160" ) private javax . swing . JCheckBox @IDPPredicate ( " S e l H e i " ) @IDPTuple ( "C160170" ) private javax . swing . JCheckBox @IDPPredicate ( " S e l H e i " ) @IDPTuple ( "C170180" ) private javax . swing . JCheckBox @IDPPredicate ( " S e l H e i " ) @IDPTuple ( "C180190" ) private javax . swing . JCheckBox @IDPPredicate ( " S e l H e i " ) @IDPTuple ( "C190200" ) private javax . swing . JCheckBox
34
/
19 20 21 22 23 24 25 26 27 28 29 30 31 32
∗
...
jCheckBox12 ;
jCheckBox13 ; jLabel13 ;
jCheckBox14 ; jCheckBox15 ; jCheckBox16 ; jCheckBox17 ; jCheckBox18 ;
∗/ Codefragment 6.2: Declaratie van geannoteerde velden.
Het voorbeeld in codefragment 6.2 laat de verschillende annotaties zien die gebruikt kunnen worden. De IDPPredicate-annotatie wordt toegevoegd aan een component die bij een predikaat hoort. Dit kan een label zijn waarvan in het codefragment een voorbeeld is gegeven. Mogelijk is dit ook een component, zoals een keuzelijst, die toelaat om een selectie te maken uit alle tuples van een predikaat. Met de
IDPPredicate-annotatie
wordt opge-
geven voor welk symbool de component geannoteerd is. De optionele `inverted'-parameter kan gebruikt worden om aan te geven dat de component voor een negatieve selectie gebruikt moet worden zodat bij het selecteren een van een De
AddPositive-operatie.
IDPTuple-annotatie
AddNegative-operatie
gebeurt in plaats
zal aan een component worden toegevoegd die de controle heeft
over één tuple van een predikaat. Om aan te geven over welk predikaat het gaat, moet de component ook met IDPPredicate geannoteerd zijn. In het codefragment is deze annota-
jCheckBox12-keuzevakje. Wanneer dat keuzevakje geselecteerd wordt, zal een AddPositive actie voor het SelGen.Male-tuple worden uitgevoerd. Bij het deselecteren wordt een Remove-actie uitgevoerd. tie onder andere toegevoegd aan het
De
IDPAdapterAction-annotatie laat toe om een actie toe te voegen aan een component. jButtonReset-knop is deze annotatie toegevoegd met als parameter de waarde
Bij de
reset. Dit wil zeggen dat de resetactie aan de knop gekoppeld zal worden. De ondersteunde acties voor de
reset
IDPAdapterAction-annotatie
zijn:
Zet de staat van alle symbolen terug naar hun beginwaarden. De undo- en redostack wordt leeggemaakt.
clear
Zet de staat van alle symbolen terug naar hun beginwaarden. Deze bewerking voegt een stap toe aan de undo- en redostack.
undo
Maak een stap ongedaan.
redo
Voer een stap opnieuw uit.
complete
Kijk na of het probleem opgelost is.
De wijzigingen die hierbij gebeuren,
worden als een stap toegevoegd aan de undo- en redostack. Deze acties kunnen aan om het even welke adapter gekoppeld worden. Dit wil niet zeggen dat de actie ooit zal uitgevoerd worden aangezien niet elke adapter het commando geeft om een actie uit te voeren. De laatste annotatie,
IDPAdapterVariant,
kan ook voor elke adapter gebruikt worden.
Deze geeft aan welke versie van de adapter gebruikt moet worden. Wanneer de gewenste versie niet beschikbaar is, zal de standaardversie gebruikt worden.
Dit is hetzelfde als
wanneer deze annotatie niet zou gebruikt zijn. Let op, de annotaties voegen enkel metadata toe aan de velden van een klasse.
Deze
velden dienen daarom in dit geval nog verwerkt te worden door een parser. De parser zal door middel van reectie de waarden van de velden ophalen. Daarom is het belangrijk dat deze velden reeds geïnitialiseerd zijn en de juiste component bevatten op het moment dat de parser deze gaat verwerken.
Gebruik en uitbreiding van de API
55
6.2.1 Snelle methode Deze manier van werken is bedoeld om snel een standaardconguratie aan de praat te krijgen. Hiervoor moet een toegeving gedaan worden ten opzichte van de exibiliteit. Het is namelijk niet mogelijk om de implementaties van een aantal klassen zelf te bepalen. De parser zal voor deze klassen een standaardimplementatie uitkiezen. Meestal zijn deze implementaties wel de juiste of voldoen ze ten minste aan de vereisten van de programmeur. In vele gevallen kan dus gekozen worden voor deze methode. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
System . s e t P r o p e r t y ( GidLApproxFactory .GIDL_MODULE_PROPERTY, " t o o l s /GidL" ) ; System . s e t P r o p e r t y ( GidLApproxFactory .APPROX_MODULE_PROPERTY, " t o o l s /Approx" ) ; try { Parser parser = Parser . configureParser ( new GidLApproxFactory ( " f i l e s / b i k e .mx" ) , Pattern . compile ( " Swing . ∗ " ) , " f i l e s / b i k e " , new AnnotationParser . Factory ( ) ) ; p a r s e r . p a r s e ( this ) ; parser . getSolution ( ) . reset ( ) ; } catch ( Exception e ) { e . printStackTrace ( ) ; System . e x i t ( − 1); } Codefragment 6.3: Code voor de koppeling van geannoteerde velden, de snelle manier.
Hoe deze methode in zijn werk gaat is beschreven in codefragment 6.3. Het valt meteen op dat er niet veel code nodig is om de kennisbank te koppelen met de geannoteerde velden. Het eerste wat hier gebeurt, is het instellen van twee omgevingsvariabelen. De variabele GidLApproxFactory.GIDL_MODULE_PROPERTY bepaalt op welk relatief pad GidL kan gevonden worden en GidLApproxFactory.APPROX_MODULE_PROPERTY bepaalt het relatief pad voor Approx. Deze twee opdrachten hebben niets met het parsen zelf te maken. Ze moeten ingesteld worden zodat de API weet waar het kennisbanksysteem zich bevindt. Op regel 6 wordt een parser aangemaakt. De eerste parameter is de
processingFactory
die gebruikt zal worden om het kennisbanksysteem op te roepen. Hieraan wordt meegegeven waar het modelbestand van de kennisbank zich bevindt op het bestandssysteem. De tweede parameter die gebruikt wordt om de parser aan te maken is het patroon dat moet overeenkomen met de naam van de toolkit. Op basis hiervan zal een toolkit door de
Configurator uitgekozen worden.
De derde parameter geeft de prex aan van de resour-
ce bundle die gebruikt zal worden om de symbolen te vertalen. De laatste parameter is een factory die het parserobject zal aanmaken. In dit geval zal er een parser aangemaakt worden voor het parsen van annotaties. Als de parser is aangemaakt, wordt de parsemethode opgeroepen met als parameter het
Solution-object opgevraagd en Indien er ergens in het try blok een Exception optreedt zal deze afgedrukt
object dat moet verwerkt worden. Als laatste wordt het geïnitialiseerd.
worden en de applicatie zal eindigen.
6.2.2 Flexibele methode 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
P r o c e s s i n g E x c e p t i o n H a n d l e r h a n d l e r = null ; S o l u t i o n s o l u t i o n = null ; S t r i n g m o d e l F i l e = " f i l e s / b i k e .mx" ; C o n f i g u r a t o r c o n f = null ; System . s e t P r o p e r t y ( GidLApproxFactory .GIDL_MODULE_PROPERTY, " t o o l s /GidL" ) ; System . s e t P r o p e r t y ( GidLApproxFactory .APPROX_MODULE_PROPERTY, " t o o l s /Approx" ) ; try { c o n f = C o n f i g u r a t o r . newConfigurator ( Pattern . compile ( " Swing . ∗ " ) ) ; P r o c e s s i n g F a c t o r y p r o c e s s i n g F a c t o r y = new GidLApproxFactory ( modelFile ) ; Model model = p r o c e s s i n g F a c t o r y . getModelReader ( ) . read ( ) ; s o l u t i o n = S o l u t i o n . newSolution ( model , p r o c e s s i n g F a c t o r y , 0 , new MaxCountIterationPolicy ( 0 ) ) ; s o l u t i o n . setCompletionHandler ( new DefaultCompletionHandler ( ChangeAction . AddNegative ) ) ; h a n d l e r = c o n f . g e t P r o c e s s i n g E x c e p t i o n H a n d l e r ( null , null ) ; s o l u t i o n = new ThreadedSolution ( s o l u t i o n , h a n d l e r ) ; ResourceBundleTranslator t r a n s l a t o r = null ; try { t r a n s l a t o r = new ResourceBundleTranslator ( " f i l e s / b i k e " ) ; } catch ( M i s s i n gR e s o u r ce E x c e p ti o n e ) { e . printStackTrace ( ) ; } conf . setTranslator ( t r a n s l a t o r ) ; P a r s e r p a r s e r = new AnnotationParser ( s o l u t i o n , conf , t r a n s l a t o r ) ; p a r s e r . p a r s e ( this ) ; solution . reset (); } catch ( Exception e ) { e . printStackTrace ( ) ; System . e x i t ( − 1); } Codefragment 6.4: Code voor de koppeling van geannoteerde velden met meer exibiliteit.
Om volledige controle te hebben over de manier waarop de API gaat functioneren, kunnen de drie parameters die gebruikt worden bij het instantiëren van de parser handmatig gecongureerd en samengesteld worden.
De parameters die bedoeld worden, zijn een
Solution-, Configurator- en Translator-object.
De werkwijze om deze te congureren
is beschreven in codefragment 6.4.
Configurator-object en het ProcessingFactory-object aangemaakt worden. Uit het ProcessingFactory-object kan de ModelReader gehaald worden en dan het Model gelezen worden. Als het Model gekend is, kan hiermee een Solution-object gemaakt worden, waarbij ook de ProcessingFactory nodig is. Deze twee objecten worden als parameters doorgegeven aan de methode die een Solution-object zal aanmaken. De Eerst moet het
twee parameters die daar nog achter komen bepalen de maximum grootte van de undostack en het maximum aantal extra iteraties die mogen uitgevoerd worden om de resultaten te verjnen bij het oproepen van het kennisbanksysteem. Deze parameters zijn niet ver-
Gebruik en uitbreiding van de API
57
eist en bij het weglaten worden hiervoor standaardwaarden ingesteld. De grootte van de undostack mag dan oneindig groot worden en het maximum aantal extra iteraties zal tien bedragen. In dit voorbeeld zijn de waarden
0
en
new MaxCountIterationPolicy(0)
ge-
bruikt. Dit wil zeggen dat er geen undo- en redostack beschikbaar zal zijn en dat er geen extra iteraties toegelaten worden. Deze instellingen zorgen er voor dat de demo vanuit het standpunt van de gebruiker dezelfde (beperkte) functionaliteit heeft als de originele demo die gebruik maakte van congNow (sectie 1.2.1). Op het
Solution-object
wordt de standaard
CompletionHandler
ingesteld.
Deze zal
alle niet gekende tuples toevoegen aan de negatieve lijst. Vervolgens wordt de standaard
ProcessingExceptionHandler opgevraagd. Op de volgende regel wordt deze gebruikt bij het aanmaken van het ThreadedSolution-object. Dit object voegt de functionaliteit aan Solution toe om in een andere thread uitgevoerd te worden. Daarna wordt een poging ondenomen om een te stellen.
Translator-object
aan te maken en in
Indien dit niet lukt, zal een foutboodschap afgedrukt worden en zullen de
symbolen niet vertaald worden. Vervolgens wordt het parserobject aangemaakt met de net aangemaakte en gecongureerde objecten. Het resterende gedeelte van de conguratie is identiek aan dat van de snelle methode (sectie 6.2.2). Het is duidelijk dat deze methode meer werk vraagt van de programmeur. Daartegenover staat wel dat die alle instellingen zelf kan kiezen. Zo zijn er een aantal onderdelen in de conguratie die de programmeur mogelijk anders zou willen instellen. De implementatie van de
Configurator
(lijn 10 in het codefragment) zou men bijvoor-
beeld kunnen vervangen door één waarvoor een toolkit gekozen wordt op basis van de ondersteunde klassen in plaats van de naam van de toolkit.
Solution-object gebruikt wordt voor het Solution-object en IterationPolicy.
Op lijn 14 zou ook een andere implementatie voor het
kunnen
worden.
ook de
In de snelle methode (sectie 6.2.2)
standaardwaarden gebruikt voor de undostack Er kan een andere
ProcessingExceptionHandler opgevraagd worden voor het Threaded-
Solution-object. Het is ook mogelijk om de API niet in een andere thread uit te voeren door geen ThreadedSolution-object aan te maken maar door het eerste Solution-object rechtstreeks te gebuiken. Tenslotte kan een andere implementatie van de den. Dit zou bijvoorbeeld een
Translator
Translator-interface
aangemaakt wor-
kunnen zijn die gebruik maakt van Google
Translate of Babelsh.
6.3 Congurator Er zijn applicaties waarvoor annotaties niet gebruikt kunnen worden. Wanneer de gebruikersinterface bijvoorbeeld dynamisch gegenereerd wordt, is het niet mogelijk om geannoteerde velden van een object te parsen aangezien die velden niet bestaan of nog niet naar een object verwijzen.
Als dit het geval is, kan de
worden om adapters aan te maken.
Configurator
rechtstreeks gebruikt
Om de conguratie van de API te vergemakkelijken, kan er wel gebruik gemaakt worden van een
Parser.
Hoe deze gemakkelijk aangemaakt wordt, is beschreven in sectie 6.2.1.
Hierbij zal de parsemethode van de parser niet veel nut hebben omdat er niets is om te verwerken. Wel kunnen via de getters van de parser de nodige objecten opgevraagd worden zoals de
Configurator-
en
Solution-objecten.
Het is natuurlijk mogelijk om beide methodes te combineren.
De parser kan gebruikt
worden om het statische gedeelte van de GUI te koppelen en de congurator kan gebruikt worden om het dynamische gedeelte te congureren.
Solution solution = parser . getSolution ( ) ; Configurator conf = parser . getConfigurator ( ) ; Model model = s o l u t i o n . getModel ( ) ; attachAdapter ( conf , jButtonReset , s o l u t i o n , null , parser . getAction ( " r e s e t " ) ) ; attachAdapter ( conf , btnUndo , s o l u t i o n , "undo" , p a r s e r . g e t A c t i o n ( "undo" ) ) ; symbol = model . g e t P r e d i c a t e ( " SelGen " ) ; t u p l e = StateTuple . g e t S t a t e T u p l e ( symbol , "Male" ) ; attachAdapter ( conf , jCheckBox12 , s o l u t i o n , t u p l e ) ; attachAdapter ( conf , jComboBox8 , s o l u t i o n , model . g e t P r e d i c a t e ( " S elFra " ) ) ;
1 2 3 4 5 6 7 8 9 10 11 12
Codefragment 6.5: Koppeling van componenten aan de kennisbank met behulp van Adapters.
In codefragment 6.5 is voor een aantal componenten een koppelingsactie uitgewerkt. Met behulp van de verschillende
attachAdapter-methodes,
die de programmeur zelf moet
schrijven, worden de componenten aan de kennisbank gekoppeld. de
jButtonReset-
en
btnUndo-knop
In dit voorbeeld is
gekoppeld met de reset- en de undoactie.
undoknop wordt er als vierde parameter ook undo doorgegeven aan de
Bij de
attachAdapter-
methode. Deze parameter is de variant van de adapter. De undovariant van de buttonadapter zal een visuele aanduiding geven als er geen stap meer ongedaan gemaakt kan worden door de knop uit te schakelen. Als de standaardvariant voor deze knop gebruikt wordt, dan kan de gebruiker op de knop blijven klikken zelfs al is er geen stap meer om terug te gaan. Er bestaat ook een redovariant die de knop zal uitschakelen als er geen stap meer is om opnieuw uit te voeren. Verder in het codefragment wordt er ook nog een keuzevakje aan een tuple gekoppeld en een keuzelijst aan een symbool. Voor deze twee moet er ook een
attachAdapter-methode
aangemaakt worden. De
attachAdapter-methodes
worden beschreven in codefragment 6.6. Er zijn vier zulke
methodes aangemaakt in dit voorbeeld. De methode op lijn 1 koppelt knoppen aan acties. Een andere methode op lijn 22 koppelt keuzevakjes aan tuples. Nog een andere methode op lijn 15 koppelt keuzelijsten aan symbolen. De twee laatstvernoemde methodes roepen, wanneer ze een adapter hebben aangemaakt, de methode op lijn 29 op die de adapter verder zal congureren.
Gebruik en uitbreiding van de API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
59
private s t a t i c void attachAdapter ( C o n f i g u r a t o r conf , JButton o b j e c t , S o l u t i o n s o l u t i o n , S t r i n g v a r i a n t , Runnable a c t i o n ) { Pattern p a t t e r n = null ; i f ( v a r i a n t != null ) { p a t t e r n = Pattern . compile ( v a r i a n t , Pattern . LITERAL ) ; } Adapter adapter = c o n f . getAdapter ( o b j e c t , pattern , s o l u t i o n ) ; i f ( adapter != null ) { adapter . s e t A d a p t e r L i s t e n e r ( new D e f a u l t A d a p t e r L i s t e n e r ( null , a c t i o n ) ) ; s o l u t i o n . a d d P r o g r e s s L i s t e n e r ( adapter ) ; } } private s t a t i c void attachAdapter ( C o n f i g u r a t o r conf , JComboBox o b j e c t , S o l u t i o n s , S t a t e f u l S y m b o l symbol ) { Adapter adapter = c o n f . getAdapter ( o b j e c t , null , s , symbol , f a l s e ) ; attachAdapter ( adapter , symbol . getName ( ) , s , c o n f . g e t P r o c e s s i n g E x c e p t i o n H a n d l e r ( null , null ) ) ; } private s t a t i c void attachAdapter ( C o n f i g u r a t o r conf , JCheckBox o b j e c t , S o l u t i o n s , StateTuple t u p l e ) { Adapter adapter = c o n f . getAdapter ( o b j e c t , null , s , t uple , f a l s e ) ; attachAdapter ( adapter , t u p l e . getValue ( ) . g e t ( 0 ) . getName ( ) , s , c o n f . g e t P r o c e s s i n g E x c e p t i o n H a n d l e r ( null , null ) ) ; } private s t a t i c void attachAdapter ( Adapter adapter , S t r i n g name , Solution s , ProcessingExceptionHandler handler ) { i f ( adapter != null ) { s . a d d S t a t e L i s t e n e r ( adapter ) ; s . a d d P r o g r e s s L i s t e n e r ( adapter ) ; adapter . s e t A d a p t e r L i s t e n e r ( new D e f a u l t A d a p t e r L i s t e n e r ( h a n d l e r ) ) ; } } Codefragment 6.6: De attachAdapter-methodes voor het koppelen van de adapters aan de ken-
nisbank.
6.4 Kennisbankabstractie Om directe controle uit te oefenen op de kennisbank kan via de kennisbankabstractiemodule gewerkt worden. De applicatie kan dan bijvoorbeeld bij initialisatie een aantal waarden automatisch instellen. In een etswinkel staat bijvoorbeeld een elektronische meetlat aan de conguratiecomputer. bij het starten van de etsconguratiesoftware zal dan automatisch de juiste grootte van het frame gekozen worden aan de hand van de grootte van de persoon die aan de computer heeft plaatsgenomen.
1 2 3 4 5 6 7
/
∗
initialisatie
van
de
parser
∗/
Solution solution = parser . getSolution ( ) ; S t a t e f u l S y m b o l symbol = s o l u t i o n . getModel ( ) . getPredicate (" SelHei " ) ; StateTuple t u p l e = StateTuple . g e t S t a t e T u p l e ( symbol , "C180190" ) ; s o l u t i o n . changeState ( new SymbolChange ( ChangeAction . AddPositive , t u p l e ) ) ; Codefragment 6.7: Directe wijziging van de staat van de kennisbank.
De gebruikte code zou er dan ongeveer uitzien zoals beschreven in codefragment 6.7. Het symbool dat gewijzigd moet worden (SelHei), wordt eerst opgehaald uit het
Solution-
SymbolChange-object aan te maken. changeState-methode van het Solution-object kan de juiste frame-
object. Dan haalt men het juiste tuple op om een Door middel van de
grootte geselecteerd worden.
6.5 Interfaces en abstracte klassen implementeren Het is al een aantal keren aangehaald dat voor sommige onderdelen van de API nieuwe implementaties gemaakt kunnen worden.
Het zijn die onderdelen die de API exibel
en uitbreidbaar maken. In totaal bestaat de API uit 11 pakketten met daarin samen 76 klassen. Hiervan zijn er 45 klassen die dienen voor de standaardimplementatie van de API. De andere 31 klassen dienen om nieuwe funtionaliteit te kunnen toevoegen. Hiervan zijn er 24 interfaces en 7 abstracte klassen om te implementeren. Deze klassen en interfaces zijn ook gebruikt bij de implementatie van sommige van de gewone klassen. Parser, BasicAdapter, BasicAdapterFactory, BasicToolkit, Selector, Solution en SolutionState zijn de abstracte klassen. De interfaces zijn:
ParserFactory, Adapter, AdapterFactory, AdapterListener, Proces-
singExceptionHandler, ProcessingExceptionHandlerFactory, Toolkit, Translator, CompletionHandler, SymbolCompletionHandler, CompletionListener, ConictHandler, ConictListener, ProcessingListener, ProgressListener, StateListener, IterationPolicy, IterationSession, ProcessingFactory, ModelReader, StateReader, StateWriter, StateProcessor en SymbolState.
Gebruik en uitbreiding van de API
61
Opmerking: Wanneer een implementatie voor een interface of abstracte klasse wordt ge-
maakt, is het belangrijk dat in deze implementatie het contract van de geïmplementeerde interface of klasse nageleefd wordt. De API vertrouwd namelijk op dit contract om correct te kunnen functioneren. Indien niet aan dit contract wordt voldaan, kan er geen garantie gegeven worden over het al dan niet correct werken van de API. Bijvoorbeeld: Het contract van de
tie van de
ConflictHandler-interface vereist dat de implementa-
handleConflict-methode een conict probeert op te lossen.
Het slagen of falen
van het oplossen van een conict moet aangegeven worden door de juiste status terug te geven. Als dit verkeerdelijk wordt gedaan, zal de API niet naar behoren functioneren. Voor informatie over de klassen en hun contracten kan gebruik gemaakt worden van de Javadoc documentatie van de API.
6.6 Maken van een Toolkit Om ondersteuning te bieden voor andere GUI toolkits of om andere implementaties te maken voor een toolkit kan een `Service Provider' aangemaakt worden. Deze zal inhaken op de Toolkit SPI. (sectie 2.4) Als voorbeeld zal er een kleurenvariant gemaakt worden van de adapter die zal koppelen met een object van de
javax.swing.JCheckBox
klasse. De kleurenvariant zal het keuze-
vakje rood kleuren als een keuze niet beschikbaar is, en groen kleuren als de keuze verplicht is. Hiervoor zal ook een implementatie van de
Toolkit-interface
aangemaakt worden die
enkel die adapter zal bevatten. Ook zal er beschreven worden hoe, met behulp van de Eclipse IDE, van deze klassen een JAR gemaakt kan worden die juist gecongureerd is voor het gebruik met de API.
6.6.1 Adapter klasse Een adapter kan bevestigd worden aan een bepaald object. Hierdoor zal die adapter ervoor zorgen dat het object de toestand weergeeft van het gewenste symbool of een tuple van dat symbool. Bijkomend zal het object de toestand van het symbool kunnen aanpassen. De adapterimplementatie die hier zal toegelicht worden, is speciek ontwikkeld om bevestigd te worden op een object van de
javax.swing.JCheckBox
klasse. Deze adapter zal
dus code bevatten om met dat soort van objecten samen te werken. De adapterklasse in dit voorbeeld draagt de naam ColorJCheckboxAdapter en is een subklasse van de
BasicAdapter-klasse.
De
BasicAdapter-klasse implementeert de Adapter-
interface en zorgt voor hulpmethodes. Hierdoor zal een deel van het werk niet voor elke implementatie opnieuw moeten gebeuren. De klasse heeft twee velden gedenieerd, zoals te zien is in codefragment 6.8. Het eerste veld houdt het tuple bij waarvoor het keuzevakje de staat moet weergeven en aanpassen. Het tweede veld is een vlag die aangeeft dat een bewerking aan de gang is. Dit veld wordt gebruikt om aan te geven dat geen nieuwe wijzigingen mogen doorgestuurd worden naar de kennisbank.
1 2 3
public c l a s s ColorJCheckboxAdapter extends BasicAdapter<JCheckBox> { private StateTuple tuple ; private boolean processing ;
4
public ColorJCheckboxAdapter ( JCheckBox checkbox , S o l u t i o n s o l u t i o n , StateTuple tup le , boolean i n v e r t e d ) { super ( checkbox , s o l u t i o n , t u p l e . getSymbol ( ) , i n v e r t e d ) ; this . t u p l e = t u p l e ; g e t S u b j e c t ( ) . a d d I t e m L i s t e n e r ( new CheckBoxListener ( ) ) ; }
5 6 7 8 9 10
Codefragment 6.8: Klassedenitie, velden en constructor van de ColorJCheckboxAdapter.
Hierna volgt de constructor. De constructor zal de adapter initialiseren als deze wordt aangemaakt. De constructor van de superklasse wordt ook opgeroepen om ook de velden van die te initialiseren. Er wordt een
CheckBoxListener
toegevoegd die events van het
keuzevakje zal opvangen wanneer deze geselecteerd en gedeselecteerd wordt. De
CheckBoxListener-klasse
is een interne klasse. Deze wordt weergegeven in codefrag-
ment 6.9. Doordat dit een interne klasse is, hoort elke instantie van deze klasse bij een instantie van de omsluitende klasse. Dit heeft als voordeel dat vanuit deze klasse toegang mogelijk is tot de methodes en velden van de omsluitende klasse. De
itemStateChanged-methode van deze klasse wordt opgeroepen wanneer het keuzevakje
wordt geselecteerd of gedeselecteerd, ongeacht of dit door de gebruiker of door de applicatie gebeurt. Dit is lastig omdat de wijzigingen die deze adapter zelf maakt teruggekoppeld zullen worden.
Er moet dus een mechanisme voorzien worden om deze terugkoppeling
te doorbreken. Hiervoor dient de processing vlag. Wanneer een bewerking wordt gestart zal deze vlag waar worden en als de bewerking eindigt zal de vlag terug onwaar worden. Het wijzigen van de processing vlag gebeurt verderop in de omsluitende klasse. Op deze manier zal een wijziging niet teruggekoppeld worden tijdens een bewerking. Een tweede vlag die gebruikt wordt, is de de
itemStateChanged-methode
forwardChanges-vlag.
De eerste keer dat
wordt opgeroepen zal de wijziging nog niet doorgegeven
worden. In plaats hiervan wordt de wijziging opgeslagen en wordt de omgekeerde wijzing uitgevoerd op het keuzevakje. Die wijziging zal deze methode terug oproepen, maar dit keer zullen de wijzigingen wel doorgegeven worden aan het kennisbanksysteem.
Door
de eerste keer het keuzevakje terug op de vorige waarde te zetten, zal die pas de juiste waarde krijgen wanneer de wijziging door het kennisbanksysteem goedgekeurd wordt. Indien dit niet zou gebeuren, dan moet, als achteraf blijkt dat de wijziging niet aanvaard is, de waarde van het keuzevakje teruggezet worden. Dit zou de implementatie complexer maken. Als aangegeven is dat deze adapter een inverterende werking heeft, zal een actie uitgevoerd worden. In het andere geval wordt een
addNegative-
addPositive-actie
uitgevoerd.
Een inverterende werking betekent dat wanneer het keuzevakje aangevinkt wordt, de keuze niet bij de oplossing mag horen. In het normale geval moet een geselecteerde keuze bij de oplossing horen.
Gebruik en uitbreiding van de API
1 2 3
private c l a s s CheckBoxListener implements I t e m L i s t e n e r { private boolean forwardChanges = f a l s e ; private boolean inputSelected = false ;
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
}
@Override public void itemStateChanged ( ItemEvent e ) { if (! processing ) { i f ( ! forwardChanges ) { forwardChanges = true ; i n p u t S e l e c t e d = e . getStateChange ( ) == ItemEvent .SELECTED; getSubject ( ) . setSelected ( ! inputSelected ) ; } else { forwardChanges = f a l s e ; if ( inputSelected ) { if ( isInverted ()) { changeState ( ChangeAction . AddNegative , ColorJCheckboxAdapter . this . t u p l e ) ; } else { changeState ( ChangeAction . AddPositive , ColorJCheckboxAdapter . this . t u p l e ) ; } } else { changeState ( ChangeAction . Remove , ColorJCheckboxAdapter . this . t u p l e ) ; } } } }
Codefragment 6.9: De geneste CheckBoxListener klasse.
63
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
@Override public void onStateChange ( S o l u t i o n s o l u t i o n , f i n a l SymbolChange change , ChangeSource s o u r c e ) { i f ( this . t u p l e . e q u a l s ( change . getValue ( ) ) ) { S w i n g U t i l i t i e s . i n v o k e L a t e r ( new Runnable ( ) { @Override public void run ( ) { JCheckBox box = g e t S u b j e c t ( ) ; switch ( change . g e t A c t i o n ( ) ) { case Remove : box . s e t S e l e c t e d ( f a l s e ) ; box . setForeground ( Color .BLACK) ; break ; case AddPositive : box . s e t S e l e c t e d ( ! i s I n v e r t e d ( ) ) ; box . setForeground ( Color .GREEN) ; break ; case AddNegative : box . s e t S e l e c t e d ( i s I n v e r t e d ( ) ) ; box . setForeground ( Color .RED) ; break ; } } }); } } Codefragment 6.10: De onStateChange methode.
Gebruik en uitbreiding van de API De
onStateChange-methode
65
in codefragment 6.10 wordt opgeroepen wanneer de staat
van het symbool gewijzigd wordt waarvoor deze adapter geregistreerd is.
Dit wil niet
noodzakelijk zeggen dat deze wijziging te maken heeft met het tuple van deze adapter. Er zal enkel gereageerd worden als het tuple overeenkomt met het tuple van deze adapter. Indien dit het geval is, betekent dit dat de wijziging aanvaard is, of dat door de wijziging van een ander tuple dit tuple ook aangepast moet worden. Afhankelijk van de wijziging zal de checkbox al dan niet geselecteerd worden en een andere kleur krijgen.
SwingUtilities.invokeLater-methode. Dit lijkt op het eerste zicht misschien niet nodig, maar de onStateChange-methode wordt opgeroepen door de Solution-klasse. Zoals eerder vermeld in dit hoofdstuk in codefragDeze bewerkingen worden opgeroepen met de
ment 6.4, is het mogelijk dat deze klasse wordt uitgevoerd in een andere thread dan de `Swing GUI'. De specicatie van Swing vermeld dat de `Swing GUI' enkel in de daarvoor voorziene thread mag gebruikt worden. (Oracle, 2010a) Om te voorkomen dat de applicatie zich onverwacht gaat gedragen worden de wijzigingen met behulp van invokeLater in de `Swing thread' uitgevoerd. Dit zal overal in deze adapterklasse zo gebeuren.
@Override public void o n P r o c e s s i n g S t a r t e d ( S o l u t i o n s o l u t i o n ) { S w i n g U t i l i t i e s . i n v o k e L a t e r ( new Runnable ( ) { @Override public void run ( ) { p r o c e s s i n g = true ; g e t S u b j e c t ( ) . setEnabled ( f a l s e ) ; } }); }
1 2 3 4 5 6 7 8 9 10 11
@Override public void onProcessingEnded ( S o l u t i o n s o l u t i o n ) { S w i n g U t i l i t i e s . i n v o k e L a t e r ( new Runnable ( ) { @Override public void run ( ) { processing = false ; g e t S u b j e c t ( ) . setEnabled ( true ) ; } }); }
12 13 14 15 16 17 18 19 20 21
Codefragment 6.11: De onProcessingStarted en onProcessingEnded methode.
onProcessingStarted- en onProcessingEnded-methodes het Solution-object wanneer de verwerking begint en eindigt. De
worden opgeroepen door Hier (codefragment 6.11)
wordt de processing vlag aangepast zodat wijzigingen tijdens de verwerking niet worden teruggekoppeld. Om te voorkomen dat de gebruiker tijdens de verwerking het keuzevakje kan aanklikken, wordt dit bij het begin uitgeschakeld en weer ingeschakeld op het einde. De
setName- en setDescription-methodes in codefragment 6.12 worden gebruikt om aan
de gebruiker aan te geven voor welk tuple het keuzevakje gebruikt wordt.
@Override public void setName ( f i n a l S t r i n g name ) { S w i n g U t i l i t i e s . i n v o k e L a t e r ( new Runnable ( ) { @Override public void run ( ) { g e t S u b j e c t ( ) . setText ( name ) ; g e t S u b j e c t ( ) . setName ( name ) ; } }); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
}
@Override public void s e t D e s c r i p t i o n ( f i n a l S t r i n g d e s c r i p t i o n ) { S w i n g U t i l i t i e s . i n v o k e L a t e r ( new Runnable ( ) { @Override public void run ( ) { g e t S u b j e c t ( ) . setToolTipText ( d e s c r i p t i o n ) ; } }); }
Codefragment 6.12: De setName en setDescription methode.
6.6.2 Factory klasse Om adapters aan te kunnen maken vanuit de applicatie, moet er een factory voorzien worden. Deze factory zal de klasse van de objecten waarvoor adapters gemaakt kunnen worden aangeven. Ook wordt de variant aangegeven van de adapters die geproduceerd worden. De factory moet uiteraard ook minstens één van de drie
getAdapter-methodes
implementeren om de adapters daadwerkelijk aan te maken. In codefragment 6.13 zijn deze methodes geïmplementeerd. wordt is de
javax.swing.JCheckBox
De klasse die ondersteund
klasse. color is de aangeboden variant aangezien
de keuzevakjes gekleurd zullen worden. Bij het aanmaken van de adapter wordt eerst nagekeken worden dat het doorgegeven object wel van het
JCheckBox-type
is.
Indien dit niet zou gebeuren, zou de cast een
Exception kunnen gooien wanneer een verkeerd object wordt doorgegeven.
Hierna wordt
de adapter aangemaakt en de translator gebruikt om de naam en beschrijving van het keuzevakje in te stellen. Uiteindelijk zal een nieuwe adapter teruggegeven worden en kan de API hiermee verder werken.
6.6.3 Toolkit klasse De
Toolkit-interface is de providerklasse van de Toolkit-SPI. Voor elke `Service Provider' Toolkit-interface imple-
die aangemaakt wordt moet een klasse gemaakt worden die de
Gebruik en uitbreiding van de API 1
public s t a t i c c l a s s ColorCheckBoxFactory extends BasicAdapterFactory {
2
@Override public Class > g e t S u p p o r t e d C l a s s ( ) { return JCheckBox . c l a s s ; }
3 4 5 6 7
@Override public ColorJCheckboxAdapter newAdapter ( Object adapted , S o l u t i o n s o l u t i o n , StateTuple tup le , boolean i n v e r t e d ) { ColorJCheckboxAdapter adapter = null ; i f ( adapted instanceof JCheckBox ) { adapter = new ColorJCheckboxAdapter ( ( JCheckBox ) adapted , s o l u t i o n , tuple , i n v e r t e d ) ; adapter . setName ( g e t T r a n s l a t o r ( ) . getName ( t u p l e ) ) ; adapter . s e t D e s c r i p t i o n ( g e t T r a n s l a t o r ( ) . g e t D e s c r i p t i o n ( t u p l e ) ) ; } return adapter ; }
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
67
}
@Override public S t r i n g getVariantName ( ) { return " c o l o r " ; }
Codefragment 6.13: De ColorCheckBoxFactory klasse.
menteerd. Hierin worden alle aangeboden
Adapter-klassen geregistreerd door middel van
hun factory. In codefragment 6.14 is zo een toolkitklasse geïmplementeerd. van de
BasicToolkit-klasse.
de factories te registreren. worden.
De
BasicToolkit-klasse
Deze is een subklasse
voorziet alle functionaliteit om
Hierdoor kan een toolkitklasse zeer snel geïmplementeerd
Het enige wat de maker van een `Service Provider' moet doen is de
implementatie een naam geven en de factories met behulp van de
Toolkit-
putFactory-methode
registreren. Belangrijk is wel dat deze klasse een constructor heeft zonder argumenten. Anders zou de Service Loader deze klasse niet kunnen laden.
6.6.4 JAR samenstellen Het laatste wat moet gebeuren om zelf een Toolkit Service Provider te maken, is deze inpakken in een Java Archief. Om de ColorToolkit klasse bekend te maken als implementatie van de toolkitproviderklasse wordt het bestand dat weergegeven is in codefragment 6.15 aangemaakt. Dit bestand moet in het Java Archief in de /META-INF/services directory geplaatst worden. Als die directory in de Eclipse IDE in de root van het project wordt aangemaakt, zal deze ook aangemaakt worden in het Java Archief. Om Het project te exporteren kan de Export Wizard gebruikt worden. Deze wizard zal aan de hand van vijf screenshots worden verklaard.
1
package t o o l k i t s . swing . c o l o r ;
2 3 /∗ i m p o r t s ∗/ 4 5
public c l a s s C o l o r T o o l k i t extends B a s i c T o o l k i t {
6
public C o l o r T o o l k i t ( ) { putFactory ( new ColorCheckBoxFactory ( ) ) ; }
7 8 9 10 11 12 13 14 15
}
@Override public S t r i n g getName ( ) { return " Swing − c o l o r " ; }
Codefragment 6.14: De ColorToolkit klasse.
1
t o o l k i t s . swing . c o l o r . C o l o r T o o l k i t Codefragment 6.15: Het jidp.conguration.spi.Toolkit bestand.
Figuur 6.1: Oproepen van de Export Wizard.
Gebruik en uitbreiding van de API
69
In guur 6.1 wordt de Export Wizard opgeroepen. Hiervoor wordt met de rechter muisknop op het project geklikt en daarna Export... geselecteerd. De export wizard zal zich openen. Als de export wizard geopend is, zal guur 6.2(a) zichtbaar zijn. Om een Java Archief te maken, moet in de Java categorie het element JAR le geselecteerd worden.
Er
kan een gewoon JAR-bestand gemaakt worden omdat dit niet op zichzelf uitgevoerd zal worden. Klik op volgende. Figuur 6.2(b) zal verschijnen en de inhoud van het archief kan geselecteerd worden. Vink op deze pagina de src en META-INF directories aan. Het is belangrijk dat het bestand uit codefragment 6.15 in de /META-INF/services staat. Als dit bestand vergeten wordt, zal de
ServiceLoader de toolkit niet vinden.
Op deze pagina moet ook de bestandsnaam
en locatie van de aan te maken JAR opgegeven worden. Als alle instellingen naar wens zijn, klik dan op volgende. De volgende afbeelding, guur 6.2(c), kan normaal onaangepast blijven. Klik op volgende. In de laatste stap van de `Export Wizard' (guur 6.2(d)), kan het gebruikte manifest gekozen worden. Indien het manifest nog niet bestaat kan de wizard deze genereren. Ook hier zijn de standaardinstellingen normaalgezien goed. Als je op nish klikt zal het archief op de gewenste locatie aangemaakt worden. Mogelijk vraagt de wizard toestemming om bestaande bestanden te overschrijven. De `Service Provider' is nu klaar. Door het archief in een directory te plaatsen die door de
Configurator
gebruikt wordt voor het laden van toolkits, zal de nieuwe adapter voortaan beschikbaar zijn in de applicatie.
6.7 Besluit In dit hoofstuk werd het gebruik van de API aan de hand van een aantal voorbeelden behandeld. Om te beginnen werd aangehaald hoe de Translator voorzien kan worden van vertalingen. Dit gebeurt door middel van properties bestanden. Vervolgens werden de verschillende methodes uitgelegd om de API te gebruiken. Hierbij werd er vertrokken bij het gemakkelijkste en minst exibele voorbeeld, om gaandeweg naar het meest uitvoerige en volledig instelbare voorbeeld te gaan. Ook werd getoond hoe de kennisbankabstractiemodule manueel kan aangesproken worden om rechtstreeks via de applicatie wijzigingen door te voeren. Hierna werd er kort de verschillende interfaces en abstracte klassen vermeld die gebruikt kunnen worden om de API te laten functioneren zoals de applicatieprogrammeur dat wenst. Er wordt naar de documentatie verwezen voor meer informatie over het gebruik van de klassen. Tenslotte werd er een voorbeeld toolkit gemaakt om de workow van het ontwikkelen van een dergelijke toolkit te verduidelijken.
(a) Export dialoogvenster
(b) JAR inhoud
(c) JAR export opties
(d) JAR manifest
Figuur 6.2: De Export Wizard
Besluit Het IDP-kennisbanksysteem is, op zichzelf, een interessant systeem voor academici om mee te experimenteren.
Helaas kan het systeem slechts met veel moeite geïntegreerd
worden in een applicatie. In deze masterproef werd daarom een Java API onwikkeld die op een exibele en eenvoudige manier toegang biedt tot een kennisbank in het IDP kennisbanksysteem. Hierdoor kan voor een brede waaier aan applicaties toegang geboden worden tot het IDP kennisbanksysteem, die de business logic van de applicatie implementeert. Aangezien de API toegang biedt tot een kennisbanksysteem, wordt een bepaalde functionaliteit verondersteld. Het moet mogelijk zijn om de inhoud van een kennisbank uit te lezen, alsook de toestand van die kennisbank te wijzigen. Deze functionaliteit wordt onder andere aangeboden door de kennisbank-abstractiemodule. Maar de API kan veel meer.
Deze bevat immers onderdelen die gebruikt kunnen worden om de applicatie en
dus ook de gebruiker op de hoogte te brengen van wijzigingen in de kennisbank. Ook is het mogelijk om applicatiecode op een bijna-automatische manier te koppelen met een kennisbank. Als er gekeken wordt naar het voorbeeld in sectie 1.2.1 valt het op dat dit niet veel meer is dan een venster met componenten, die rechtstreeks gekoppeld zijn met een kennisbank.
In de code van dit venster kunnen componenten met behulp
van korte aanduidingen, annotaties genoemd, verbonden worden met de kennisbank. Dit vraagt veel minder werk, is overzichtelijker en bijgevolg gemakkelijker aan te passen dan wanneer hiervoor zelf conguratiecode geschreven moet worden. De API die in deze masterproef ontwikkeld werd, is niet vanuit het ijle onstaan. Er was reeds een rudimentaire implementatie beschikbaar, samen met een aantal voorbeelden die hiervan gebruik maken.
Met die implementatie, congNow genaamd, was het mogelijk
om het kennisbanksysteem aan te spreken, maar ze creëerde meer problemen dan dat ze oploste. In de eerste plaats was er geen documentatie, was de code bijna onbegrijpbaar en deed de implementatie niet altijd wat ervan verwacht werd. Bijkomend was congNow zo speciek en rechtlijnig geïmplementeerd, dat het onmogelijk was om deze in een exibele API om te zetten. Daarom is de ontwikkeling van de API met een schone lei gestart.
Hierbij werd eerst
de functionaliteit nagebootst die door congNow aangeboden werd aan de meegeleverde voorbeeldapplicaties. Voor deze funtionaliteit werd eerst een ontwerp gemaakt en reeds nagedacht over nieuwe functionaliteit die achteraf toegevoegd zou kunnen worden. Hiermee rekening gehouden, werd een eerste versie van de API geschreven.
Door een doordacht ontwerp te maken was het niet moeilijk om nieuwe funtionaliteit toe te voegen en de huidige vorm van de API te bekomen.
In deze vorm is het nog
steeds gemakkelijk om aanpassingen uit te voeren. De ontwikkelde API is een geslaagde implementatie, die gemakkelijk en op verschillende manieren gebruikt kan worden en waarop men kan voortbouwen.
Bibliograe Abhinav, K. (2010), `Interactive Conguration Tool'. Clayberg, E. and Rubel, D. (2008), Eclipse Plug-ins, Addison-Wesley Professional.
R
Holzner, S. (2006), Design patterns for dummies , John Wiley & Sons, Inc., New York, NY, USA. Horstmann, C. and Cornell, G. (2007), Core Java
TM , volume I-fundamentals, eighth edi-
tion, Prentice Hall Press, Upper Saddle River, NJ, USA.
Horstmann, C. and Cornell, G. (2008), Core Java
TM , volume II-advanced features, eighth
edition, Prentice Hall Press, Upper Saddle River, NJ, USA.
TM EE
Jendrock, E., Ball, J., Carson, D., Evans, I., Fordin, S. and Haase, K. (2006), Java
5 Tutorial (3rd Edition) (The Java Series), Prentice Hall PTR, Upper Saddle River,
NJ, USA. KUL (2008), The IDP system: A model expansion system for an extension of classical logic, ACCO.
Liang, S. (1999), Java Native Interface: Programmer's Guide and Reference, AddisonWesley Longman Publishing Co., Inc., Boston, MA, USA. Mariën, M., Wittocx, J. and Denecker, M. (2006), The IDP framework for declarative problem solving.
Nielsen, J. (1994), Usability Engineering, Morgan Kaufmann Publishers, San Francisco, California.
TM
Tutorials)', http://download. oracle.com/javase/tutorial/uiswing/concurrency/index.html.
Oracle (2010a), `Concurrency in Swing (The Java
Oracle
(2010b),
`Internationalization
(The
TM
Java
Tutorials)',
oracle.com/javase/tutorial/i18n/index.html. Oracle (2010c),
`JAR File Specication',
docs/technotes/guides/jar/jar.html.
http://download.
http://download.oracle.com/javase/6/
http://download.oracle. com/javase/6/docs/api/java/net/JarURLConnection.html.
Oracle (2010d), `JarURLConnection (Java Platform SE 6)',
Oracle (2010e), `The Reection API (The Java
TM Tutorials)', http://download.oracle.
com/javase/tutorial/reflect/class/index.html. Wittocx, J. and Mariën, M. (2010), The IDP system.