-
Notifications
You must be signed in to change notification settings - Fork 0
/
overview.html
252 lines (251 loc) · 16.3 KB
/
overview.html
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
<!DOCTYPE html>
<html>
<head>
<title>Yaclpplib</title>
<meta charset="utf8"/>
</head>
<body>
<p><strong>Yaclpplib</strong> is a library written in Java 8 for parsing arguments
given during startup of your programs. The library is built upon Java reflection and annotations.
The user of this library is meant to define a class implementing interface <code>Options</code>
and annotate those class' fields and methods. The library will then parse the arguments and set values
of annotated fields and call annotated methods.
</p>
<p>
This page describes the architecture of the library and the functions of various packages and interfaces.
For basic usage guide and a quick overview of provided classes and annotations, see README.
</p>
<h1>
<a href="cz/cuni/mff/yaclpplib/annotation/package-summary.html">Annotations</a>
</h1>
<p>
This library uses annotations,
which users use to annotate their class members. These annotations are predefined
and adding your own shouldn't be needed unless you need something similar to the
<a href="cz/cuni/mff/yaclpplib/annotation/Range.html"><code>Range</code></a> annotation.
Each annotation is processed in a different part of the library implementation,
so we will go over them as we need them.
</p>
<h1>
<a href="cz/cuni/mff/yaclpplib/OptionValue.html">OptionValue</a>
</h1>
<p>
An <a href="cz/cuni/mff/yaclpplib/OptionValue.html"><code>OptionValue</code></a>
is abstracting the pair of an option and its value.
The classes implementing this interface are supposed to take a string (or two, if it's in "-o value" format)
and split it into "option" and "value".
Currently, the library offers implementations for two different type of option formats.
</p>
<p>
The first supported format is described by
<a href="cz/cuni/mff/yaclpplib/implementation/ShortOptionValue.html"><code>ShortOptionValue</code></a>.
It handles options, which start with
a dash ("-") and a single letter or number. It supports two different type of values,
either a value separated by a space, or without it. In other words, both "-pvalue" and "-p value" are supported.
</p>
<p>
The second supported format is described by
<a href="cz/cuni/mff/yaclpplib/implementation/LongOptionValue.html"><code>LongOptionValue</code></a>.
It handles options starting with two
dashes ("--") and at least one letter or number. The values of these options are separated by an equals sign ("=").
Some examples of handled options are "--o", "--all", "--type=something", "--size=5".
</p>
<p>
The instances of these classes are created by
<a href="cz/cuni/mff/yaclpplib/implementation/InternalOptionValueFactory.html"><code>InternalOptionValueFactory</code></a>.
If you wish to support a new type, create a new class implementing
<a href="cz/cuni/mff/yaclpplib/implementation/InternalOptionValue.html"><code>InternalOptionValue</code></a>
interface and modify the
<a href="cz/cuni/mff/yaclpplib/implementation/InternalOptionValueFactory.html#tryCreate-java.lang.String-"><code>InternalOptionValueFactory#tryCreate()</code></a> method.
</p>
<p>
If you want, you can replace it completely. Just replace the InternalOptionValueFactory with your implementation.
There were plans to make this configurable, but it would clutter the API.
</p>
<h1>
<a href="cz/cuni/mff/yaclpplib/driver/Driver.html">Drivers</a>
</h1>
<p>
The parsing of values is handled by drivers, which are short classes implementing the <code>Driver</code> interface.
The main function is in the
<a href="cz/cuni/mff/yaclpplib/driver/Driver.html#parse-cz.cuni.mff.yaclpplib.OptionValue-">
<code>parse(OptionValue value)</code></a> method, which takes an OptionValue instance
and tries to convert the value into desired type.
We will shortly go over our drivers, since some of them handle multiple types at once. All of these drivers
are found in the <a href="cz/cuni/mff/yaclpplib/driver/package-summary.html"><code>yaclpplib.driver</code></a> package.
</p>
<ul>
<li><a href="cz/cuni/mff/yaclpplib/driver/BooleanDriver.html"><code>BooleanDriver</code></a> - parses all usual
representations of truthy and falsy values into true or false</li>
<li><a href="cz/cuni/mff/yaclpplib/driver/CharacterDriver.html"><code>CharacterDriver</code></a>
- converts a String into char</li>
<li><a href="cz/cuni/mff/yaclpplib/driver/GenericEnumDriver.html"><code>GenericEnumDriver</code></a>
and <a href="cz/cuni/mff/yaclpplib/driver/GenericCaseInsensitiveEnumDriver.html"><code>GenericCaseInsensitiveEnumDriver</code></a>
- convert a String constant into a matching enum value depending on CaseSensitive annotation</li>
<li><a href="cz/cuni/mff/yaclpplib/driver/StringDriver.html"><code>StringDriver</code></a>
- a special driver which can convert string into anything,
that has a String constructor. This driver also works for all numeric types.</li>
<li><a href="cz/cuni/mff/yaclpplib/driver/VoidDriver.html"><code>VoidDriver</code></a>
- a special driver used for options, that never take value</li>
</ul>
<p>
These drivers are provided by classes implementing
<a href="cz/cuni/mff/yaclpplib/implementation/drivers/DriverLocator.html"><code>DriverLocator</code></a>
interface. The purpose of
a <a href="cz/cuni/mff/yaclpplib/implementation/drivers/DriverLocator.html"><code>DriverLocator</code></a>
is to determine, whether it has a driver for a given Java <code>Class</code>
and if so, provide a driver which can parse string into instances of those classes.
</p>
<p>
Our library currently implements 4 driver locators. These and their interfaces are found in
<a href="cz/cuni/mff/yaclpplib/implementation/drivers/package-summary.html"><code>yaclpplib.implementation.drivers</code></a> package.
</p>
<ul>
<li><a href="cz/cuni/mff/yaclpplib/implementation/drivers/EnumDriverFactory.html"><code>EnumDriverFactory</code></a> - locator creating instances of enum drivers for a concrete enum class</li>
<li><a href="cz/cuni/mff/yaclpplib/implementation/drivers/StringConstructableDriverFactory.html">
<code>StringConstructableDriverFactory</code></a> - locator providing a driver instance for classes,
that have a constructor taking a String</li>
<li><a href="cz/cuni/mff/yaclpplib/implementation/drivers/HashDriverLocator.html"><code>HashDriverLocator</code></a>
- which stores all miscellaneous drivers</li>
<li><a href="cz/cuni/mff/yaclpplib/implementation/drivers/DriverCache.html"><code>DriverCache</code></a>
- which covers other locators and tries to use each of them in order, also caching the results</li>
</ul>
<p>
The main argument parser implementation then checks, what type the parsed option has and asks a driver cache
for a matching driver. If the driver is found, it uses it to parse the argument and create a value and if not,
the argument is treated as a plain argument if possible, otherwise an exception is thrown.
</p>
<h1>
<a href="cz/cuni/mff/yaclpplib/implementation/OptionHandler.html">OptionHandlers</a>
</h1>
<p>
<a href="cz/cuni/mff/yaclpplib/implementation/OptionHandler.html"><code>OptionHandlers</code></a>
are classes responsible for accessing and modifying annotated fields and methods,
and also covers certain "behaviours" of options.
All the relevant classes are in the <a href="cz/cuni/mff/yaclpplib/implementation/options/package-summary.html">
<code>yaclpplib.implementation.options</code></a> package.
These are the classes that set values of fields annotated with
<a href="cz/cuni/mff/yaclpplib/annotation/Option.html"><code>Option</code></a> or call methods with the same
annotations. The methods are described by <a href="cz/cuni/mff/yaclpplib/implementation/OptionHandler.html">
<code>OptionHandlers</code></a> interface and common implementation
is provided by <code>MemberOptionHandler</code> abstract class.
</p>
<p>
The two main implementations of <a href="cz/cuni/mff/yaclpplib/implementation/OptionHandler.html">
<code>OptionHandler</code></a> are <a href="cz/cuni/mff/yaclpplib/implementation/options/FieldOption.html">
<code>FieldOption</code></a>,
which is used when user annotates a class field and sets the field to the parsed value,
and <a href="cz/cuni/mff/yaclpplib/implementation/options/MethodOption.html">
<code>MethodOption</code></a>, which calls an annotated method with the parsed value.
These <a href="cz/cuni/mff/yaclpplib/implementation/OptionHandler.html">
<code>OptionHandlers</code></a> are created by <a href="cz/cuni/mff/yaclpplib/ArgumentParser.html">
<code>ArgumentParser</code></a> itself in
<a href="cz/cuni/mff/yaclpplib/ArgumentParser.html#addOptions-T-"><code>addOptions()</code></a>
method.
If you wish to implement a handler for a different entity,
you might need to create a new <a href="cz/cuni/mff/yaclpplib/implementation/OptionHandler.html">
<code>OptionHandler</code></a>.
</p>
<p>
The library also provides decorators, which are wrapper classes providing special functionality.
These are needed to provide some of the required functions and are wrapped around
<a href="cz/cuni/mff/yaclpplib/implementation/options/FieldOption.html"><code>FieldOption</code></a>
or <a href="cz/cuni/mff/yaclpplib/implementation/options/MethodOption.html"><code>MethodOption</code></a>.
If you are looking to implement a simple functionality or a quirk for some
special case, creating a new decorator may solve your problem.
</p>
<p>
For example, multiple option occurrences for an option of array type is implemented using decorators. The underlying
(decorated) handler takes e.g. a <code>String[]</code> array, the decorator,
<a href="cz/cuni/mff/yaclpplib/implementation/options/ArrayOption.html"><code>ArrayOption</code></a>
behaves like a <code>String</code> option.
It aggregates the values given and an the end (in the
<a href="cz/cuni/mff/yaclpplib/implementation/OptionHandler.html#finish--"><code>finish()</code></a>)
method, it passes the array to the decorated handler.
The ArrayOption is therefore the only place we need to take care about arrays.
</p>
<p>Similarly, the <a href="cz/cuni/mff/yaclpplib/implementation/options/RangeOption.html"><code>RangeOption</code></a>
takes care of the range checking, <a href="cz/cuni/mff/yaclpplib/implementation/options/BooleanOption.html">
<code>BooleanOption</code></a> does the trick with boolean fields
(like "--verbose") not requiring value (otherwise we would have to pass "--verbose=true", because fields always
have to get a value).
<a href="cz/cuni/mff/yaclpplib/implementation/options/BoxedOption.html"><code>BoxedOption</code></a>
wraps primitive types, so we need to check only boxed types elsewhere.</p>
<p>
<b>Warning: the order of wrapping decorators matters as some of them consume or change the value, and the order defines semantics of handling different combinations.</b>
</p>
<p>The rationale behind decorators is that these quirks have "stacked" behavior - they change some properties on the way
up (value policy, type, etc.), and appropriately alters the value on the way down (arrays).</p>
<h1>
<a href="cz/cuni/mff/yaclpplib/ArgumentParser.html">ArgumentParser</a>
</h1>
<p>
Next few paragraphs will describe the function of argument parser itself,
especially the library implementation,
<a href="cz/cuni/mff/yaclpplib/implementation/ArgumentParserImpl.html"><code>ArgumentParserImpl</code></a>.
</p>
<p>
Argument parser works in two stages. The first stage in configuration, which sets up the parser and creates
required objects. One part of the configuration doesn't usually have to be altered by the library user.
That includes initializing internal structures and more importantly, adding the driver locators.
When the driver locators are added, users can call
<a href="cz/cuni/mff/yaclpplib/ArgumentParser.html#addOptions-T-"><code>addOptions()</code></a> method.
This method analyses the given
instance's class, finds all fields and methods annotated with
<a href="cz/cuni/mff/yaclpplib/annotation/Option.html"><code>Option</code></a> and methods annotated with
<a href="cz/cuni/mff/yaclpplib/annotation/AfterParse.html"><code>AfterParse</code></a>
annotation, creates handlers for them and creates a mapping from option to handler.
If any of these steps fail, the library throws an
<a href="cz/cuni/mff/yaclpplib/InvalidSetupError.html"><code>InvalidSetupError</code></a>
as all of these are a result of mistakes in the code, not values found in runtime.
</p>
<p>
As a part of the configuration stage, user can request a positional arguments, which gives him a list, which will
be populated by positional arguments later. Internally, the unhandled exception handler is changed from a simple
lambda throwing an exception to an adder to this list.
This API gives us the information, whether the user wants positional arguments before they try to parse options (so
we can throw exceptions eagerly), but also requires user to call only one API call to make everything work.
</p>
<p>
The second stage is parsing itself. This is done by calling
<a href="cz/cuni/mff/yaclpplib/ArgumentParser.html#parse-java.lang.String:A-"><code>parse()</code></a>
method. This method can throw
<code>RuntimeException</code>, which is done to allow user calling the library without surrounding the code
with try-catch block, as some users may want to let the exception end the program. Also, a great part of the
exceptions happen only when users use (or do not use) a specific feature -
<a href="cz/cuni/mff/yaclpplib/MissingMandatoryOptionException.html"><code>MissingMandatoryOptionException</code></a>,
<a href="cz/cuni/mff/yaclpplib/IllegalOptionValue.html"><code>IllegalOptionValue</code></a>,
<a href="cz/cuni/mff/yaclpplib/UnhandledArgumentException.html"><code>UnhandledArgumentException</code></a>) etc.,
so it would result in many collapsed catch blocks with a comment "cannot happen". Remember InterruptedException?
</p>
<p>
The <a href="cz/cuni/mff/yaclpplib/ArgumentParser.html#parse-java.lang.String:A-"><code>parse()</code></a>
methods goes through the list of string tokens given in the argument (which should
be command line arguments). For each token, it tries to create a valid
<a href="cz/cuni/mff/yaclpplib/OptionValue.html"><code>OptionValue</code></a>.
If an <a href="cz/cuni/mff/yaclpplib/OptionValue.html"><code>OptionValue</code></a> is created,
an <a href="cz/cuni/mff/yaclpplib/implementation/OptionHandler.html"><code>OptionHandler</code></a> is looked up.
If either of these fails, the token is considered a plain argument and the handler is called.
Then, since the type is known by the handler, the driver locator is called and the driver used to parse the token.
The parsed token is then given to the handler so it can set up the value.
</p>
<p>This method may look complex, but it is an inherent complexity. You need to parse the option to have its name, only
then you can look if it takes a value (optionally or always). But only after then you know, how you should have
parsed it before… So we introduced another parsing step,
<a href="cz/cuni/mff/yaclpplib/implementation/InternalOptionValue.html#completeValue-cz.cuni.mff.yaclpplib.implementation.TokenList-cz.cuni.mff.yaclpplib.implementation.ValuePolicy-"><code>InternalOptionValue#completeValue</code></a>,
which can take another token as an value, if it wishes to do so.</p>
<p>
After the token list is processed, every field or method with
<a href="cz/cuni/mff/yaclpplib/annotation/Mandatory.html"><code>Mandatory</code></a> annotation is checked,
whether it was really encountered. Then, every method annotated with
<a href="cz/cuni/mff/yaclpplib/annotation/AfterParse.html"><code>AfterParse</code></a> is called and
all RuntimeExceptions are forwarded to user. This ends the parsing.
</p>
<p>
Most of the parts are separated into methods or even special classes
(for example <a href="cz/cuni/mff/yaclpplib/implementation/MandatoryManager.html"><code>MandatoryManager</code></a>).
Therefore should you want to change the behavior of parsing, you should be able to do so without
large scale changes to the code.
</p>
</body>
</html>