Ontwikkeling van een Java API voor een kennisbanksysteem

advertisement
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
<idp : label v a l u e=" S e l H e i " />
< j s f : combobox>
<idp : p r e d i c a t e v a l u e=" S e l H e i ">
</ j s f : combobox>
<idp : label v a l u e=" SelExt "/>
< j s f : checkbox/>
<idp : p r e d i c a t e v a l u e=" SelExt ">
<idp : t u p l e v a l u e="Pump">
</ j s f : checkbox>
< j s f : checkbox/>
<idp : p r e d i c a t e v a l u e=" SelExt ">
<idp : t u p l e v a l u e="Mudguard">
</ j s f : checkbox>
< j s f : checkbox/>
<idp : p r e d i c a t e v a l u e=" SelExt ">
<idp : t u p l e v a l u e=" B o t t l e ">
</ 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<int>. 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<int>
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<int> 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
<<abstract>>
Instance
*
1
*
Type
StatefulSymbol
<<abstract>>
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
<oud>
SolutionState
<tijdelijk>
SolutionState
<nieuw>
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
<permanent>
solutionState
<permanent>
Stap
+ dupliceren
+ dupliceren
inputState
<tijdelijk>
solutionState
<tijdelijk>
Stap
Change
Applicatie
inputState
<tijdelijk>
State listener
solutionState
<tijdelijk>
Stap
Change
inputState
<tijdelijk>
Kennisbanksysteem
solutionState
<tijdelijk>
Change
Change
inputState
<permanent>
solutionState
<permanent>
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 <To o lk i t > 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 <URL> j a r s = new ArrayList <URL> ( ) ;
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.
Download