SpeADL Dynamic Tutorial » Historique » Version 21
Anonyme, 16/10/2014 12:49
| 1 | 3 | Anonyme | h1. SpeADL for Dynamic Architectures Tutorial |
|---|---|---|---|
| 2 | 2 | Anonyme | |
| 3 | 7 | Anonyme | {{>toc}} |
| 4 | |||
| 5 | 1 | Anonyme | This is a tutorial for SpeADL: understanding ecosystems and species, defining a simple ecosystem, composing species together with uses. |
| 6 | 3 | Anonyme | |
| 7 | h2. Objectives |
||
| 8 | |||
| 9 | The objective of this tutorial to understand the abstractions of species and uses. |
||
| 10 | 17 | Anonyme | We won't focus on the workflow and mostly present already defined architectures and implementation without explaining how we arrived to that solution. |
| 11 | 3 | Anonyme | |
| 12 | We will create a Logging ecosystem that contains Logger species. |
||
| 13 | Then we will create a Banking ecosystem that contains Account species that need to log what happens. |
||
| 14 | 16 | Anonyme | After, we will create a Bank ecosystem that compose Account species with Logger species thanks to the use abstraction. |
| 15 | And finally we will add a GUI that will instantiate species and visualise them. |
||
| 16 | 3 | Anonyme | |
| 17 | h2. Prerequisites |
||
| 18 | |||
| 19 | It is needed to understand the content of the [[SpeADL Minus Tutorial|SpeADL⁻ tutorial]] before doing this one. |
||
| 20 | |||
| 21 | h2. Creating a New Project and Organisation |
||
| 22 | |||
| 23 | Create a Java project. |
||
| 24 | We will use again an organisation of the package and namespaces as explained in [[MAY Best Practices#Project Organisation|this best practice]]. |
||
| 25 | |||
| 26 | h2. The Logging Ecosystem |
||
| 27 | |||
| 28 | Logging will be an ecosystem, and its particularity is that it will be able to create Loggers: while logging is the subsystem responsible of all logging, each logger will be responsible of logging one particular aspect of the system identified by a _name_. |
||
| 29 | We will have multiple implementation for the Logging ecosystem: |
||
| 30 | * One with only one file where to log with each line prepended with the name. |
||
| 31 | * One with one file per Logger all in the same directory. |
||
| 32 | |||
| 33 | 9 | Anonyme | In any case, a port will be provided by Logging to create standalone instances of Logger. |
| 34 | |||
| 35 | 3 | Anonyme | h3. Defining the Ecosystem and the Species |
| 36 | 1 | Anonyme | |
| 37 | 8 | Anonyme | Create a SpeADL file named _logging.speadl_ in the package _tutorial2.logging_. |
| 38 | 3 | Anonyme | |
| 39 | Define in it the ecosystem and the species in the right namespace as well as the needed interface: |
||
| 40 | <pre> |
||
| 41 | 8 | Anonyme | import tutorial2.logging.interfaces.ILog |
| 42 | 3 | Anonyme | |
| 43 | namespace tutorial2.logging { |
||
| 44 | 1 | Anonyme | |
| 45 | 9 | Anonyme | ecosystem Logging { |
| 46 | provides create: ICreateLogger |
||
| 47 | |||
| 48 | 3 | Anonyme | species Logger(name: String) { |
| 49 | provides log: ILog |
||
| 50 | } |
||
| 51 | } |
||
| 52 | |||
| 53 | } |
||
| 54 | </pre> |
||
| 55 | |||
| 56 | <pre> |
||
| 57 | 8 | Anonyme | package tutorial2.logging.interfaces; |
| 58 | 3 | Anonyme | |
| 59 | public interface ILog { |
||
| 60 | |||
| 61 | public void addLine(String line); |
||
| 62 | 1 | Anonyme | } |
| 63 | 9 | Anonyme | |
| 64 | public interface ICreateLogger { |
||
| 65 | |||
| 66 | public Logging.Logger.Component createStandaloneLogger(String name); |
||
| 67 | } |
||
| 68 | 3 | Anonyme | </pre> |
| 69 | |||
| 70 | h3. Implementing the Ecosystem and the Species, First One |
||
| 71 | |||
| 72 | 12 | Anonyme | Create a new Java class in _tutorial2.logging.impl_ named _LoggingImplOneFile_ that extends _Logging_, that takes the a _File_ as a parameter to the constructor to store the logs, and resolve the error with the Quick Fixes of Eclipse: |
| 73 | 3 | Anonyme | <pre> |
| 74 | package tutorial2.logging.impl; |
||
| 75 | |||
| 76 | import java.io.File; |
||
| 77 | import java.io.FileNotFoundException; |
||
| 78 | 5 | Anonyme | import java.io.FileWriter; |
| 79 | 3 | Anonyme | import java.io.IOException; |
| 80 | import java.io.PrintWriter; |
||
| 81 | |||
| 82 | 1 | Anonyme | import tutorial2.logging.Logging; |
| 83 | 9 | Anonyme | import tutorial2.logging.interfaces.ICreateLogger; |
| 84 | 3 | Anonyme | |
| 85 | 12 | Anonyme | public class LoggingImplOneFile extends Logging { |
| 86 | 3 | Anonyme | |
| 87 | private PrintWriter logWriter; |
||
| 88 | |||
| 89 | 12 | Anonyme | public LoggingImplOneFile(File logFile) { |
| 90 | 3 | Anonyme | // if an exception happens, nothing can be done about it |
| 91 | // we just let logStream be null and |
||
| 92 | // the operations of logging won't be done |
||
| 93 | try { |
||
| 94 | 5 | Anonyme | this.logWriter = new PrintWriter(new FileWriter(logFile), true); |
| 95 | 3 | Anonyme | } catch (FileNotFoundException e) { |
| 96 | 5 | Anonyme | System.err.println("An error happened with the file, nothing will be logged."); |
| 97 | 1 | Anonyme | this.logWriter = null; |
| 98 | 3 | Anonyme | } catch (IOException e) { |
| 99 | 5 | Anonyme | System.err.println("An error happened with the file, nothing will be logged."); |
| 100 | 3 | Anonyme | this.logWriter = null; |
| 101 | } |
||
| 102 | 1 | Anonyme | } |
| 103 | 9 | Anonyme | |
| 104 | @Override |
||
| 105 | protected ICreateLogger make_create() { |
||
| 106 | // TODO Auto-generated method stub |
||
| 107 | return null; |
||
| 108 | } |
||
| 109 | 3 | Anonyme | |
| 110 | @Override |
||
| 111 | protected Logger make_Logger(String name) { |
||
| 112 | // TODO Auto-generated method stub |
||
| 113 | return null; |
||
| 114 | } |
||
| 115 | } |
||
| 116 | </pre> |
||
| 117 | |||
| 118 | As we can see, the species we defined needs to be implemented and this implementation needs to be returned by the method _Logger make_Logger(String name)_. |
||
| 119 | |||
| 120 | 12 | Anonyme | Let's define an inner class inside _LoggingImplOneFile_ to do that, it will take as a parameter to the constructor the _name_ of the Logger and that will exploit the _logWriter_ to do the actual logging. |
| 121 | 3 | Anonyme | <pre> |
| 122 | 12 | Anonyme | public class LoggingImplOneFile extends Logging { |
| 123 | 3 | Anonyme | |
| 124 | private PrintWriter logWriter; |
||
| 125 | |||
| 126 | // ... |
||
| 127 | |||
| 128 | @Override |
||
| 129 | protected Logger make_Logger(String name) { |
||
| 130 | return new LoggerImpl(name); |
||
| 131 | } |
||
| 132 | |||
| 133 | private class LoggerImpl extends Logger implements ILog { |
||
| 134 | |||
| 135 | private final String name; |
||
| 136 | |||
| 137 | public LoggerImpl(String name) { |
||
| 138 | this.name = name; |
||
| 139 | } |
||
| 140 | |||
| 141 | @Override |
||
| 142 | protected ILog make_log() { |
||
| 143 | return this; |
||
| 144 | 1 | Anonyme | } |
| 145 | |||
| 146 | @Override |
||
| 147 | 5 | Anonyme | public void addLine(String line) { |
| 148 | 1 | Anonyme | if (logWriter != null) { |
| 149 | 5 | Anonyme | logWriter.println("["+name+"] "+ line); |
| 150 | } |
||
| 151 | } |
||
| 152 | } |
||
| 153 | } |
||
| 154 | </pre> |
||
| 155 | 1 | Anonyme | |
| 156 | 9 | Anonyme | Then let's implement the create port by exploiting the method *newLogger(String name)* present in the extended class that enables to instantiate a species: |
| 157 | <pre> |
||
| 158 | 12 | Anonyme | public class LoggingImplOneFile extends Logging { |
| 159 | 9 | Anonyme | |
| 160 | // ... |
||
| 161 | |||
| 162 | @Override |
||
| 163 | protected ICreateLogger make_create() { |
||
| 164 | return new ICreateLogger() { |
||
| 165 | @Override |
||
| 166 | public Logger.Component createStandaloneLogger(String name) { |
||
| 167 | return newLogger(name); |
||
| 168 | } |
||
| 169 | }; |
||
| 170 | } |
||
| 171 | |||
| 172 | // ... |
||
| 173 | } |
||
| 174 | 10 | Anonyme | </pre> |
| 175 | 9 | Anonyme | |
| 176 | 5 | Anonyme | We now have a complete implementation for the Logging ecosystem. |
| 177 | 9 | Anonyme | Let's test it with a very simple program that we put in the package _tutorial2.logging.tests_*: |
| 178 | 5 | Anonyme | <pre> |
| 179 | package tutorial2.logging.tests; |
||
| 180 | |||
| 181 | 1 | Anonyme | import java.io.File; |
| 182 | |||
| 183 | 9 | Anonyme | import tutorial2.logging.Logging; |
| 184 | import tutorial2.logging.Logging.Logger; |
||
| 185 | 12 | Anonyme | import tutorial2.logging.impl.LoggingImplOneFile; |
| 186 | 5 | Anonyme | |
| 187 | 9 | Anonyme | public class LoggingTest { |
| 188 | public static void main(String[] args) { |
||
| 189 | 5 | Anonyme | |
| 190 | 12 | Anonyme | Logging.Component logging = new LoggingImplOneFile(new File("/tmp/tutorial2-logging-test.txt")).newComponent(); |
| 191 | 5 | Anonyme | |
| 192 | // create new Loggers |
||
| 193 | 9 | Anonyme | Logger.Component l1 = logging.create().createStandaloneLogger("a"); |
| 194 | Logger.Component l2 = logging.create().createStandaloneLogger("b"); |
||
| 195 | Logger.Component l3 = logging.create().createStandaloneLogger("c"); |
||
| 196 | |||
| 197 | 5 | Anonyme | // log things to them |
| 198 | l1.log().addLine("1 a says hi"); |
||
| 199 | l1.log().addLine("2 test test"); |
||
| 200 | l2.log().addLine("3 b says hoy"); |
||
| 201 | l1.log().addLine("4 blabla"); |
||
| 202 | l3.log().addLine("5 hop"); |
||
| 203 | 6 | Anonyme | } |
| 204 | 5 | Anonyme | } |
| 205 | </pre> |
||
| 206 | |||
| 207 | Execute it and confirm that the file _/tmp/tutorial2-logging-test.txt_ contains the following: |
||
| 208 | <pre> |
||
| 209 | [a] 1 a says hi |
||
| 210 | 3 | Anonyme | [a] 2 test test |
| 211 | [b] 3 b says hoy |
||
| 212 | 1 | Anonyme | [a] 4 blabla |
| 213 | [c] 5 hop |
||
| 214 | 6 | Anonyme | </pre> |
| 215 | |||
| 216 | h3. Implementing the Ecosystem and the Species, Second One |
||
| 217 | |||
| 218 | We can now define a second implementation that stores the logs of each Logger in its own file: |
||
| 219 | <pre> |
||
| 220 | package tutorial2.logging.impl; |
||
| 221 | |||
| 222 | import java.io.File; |
||
| 223 | import java.io.FileNotFoundException; |
||
| 224 | import java.io.FileWriter; |
||
| 225 | import java.io.IOException; |
||
| 226 | import java.io.PrintWriter; |
||
| 227 | |||
| 228 | import tutorial2.logging.Logging; |
||
| 229 | 9 | Anonyme | import tutorial2.logging.interfaces.ICreateLogger; |
| 230 | 1 | Anonyme | import tutorial2.logging.interfaces.ILog; |
| 231 | |||
| 232 | 12 | Anonyme | public class LoggingImplDirectory extends Logging { |
| 233 | 1 | Anonyme | |
| 234 | private final File logDir; |
||
| 235 | |||
| 236 | 12 | Anonyme | public LoggingImplDirectory(File logDir) { |
| 237 | 1 | Anonyme | this.logDir = logDir; |
| 238 | this.logDir.mkdirs(); |
||
| 239 | 6 | Anonyme | } |
| 240 | |||
| 241 | @Override |
||
| 242 | protected Logger make_Logger(String name) { |
||
| 243 | return new LoggerImpl(new File(logDir, name+".txt")); |
||
| 244 | } |
||
| 245 | 9 | Anonyme | |
| 246 | @Override |
||
| 247 | protected ICreateLogger make_create() { |
||
| 248 | return new ICreateLogger() { |
||
| 249 | @Override |
||
| 250 | public Logger.Component createStandaloneLogger(String name) { |
||
| 251 | return newLogger(name); |
||
| 252 | } |
||
| 253 | }; |
||
| 254 | } |
||
| 255 | 6 | Anonyme | |
| 256 | private class LoggerImpl extends Logger implements ILog { |
||
| 257 | |||
| 258 | private PrintWriter logWriter; |
||
| 259 | |||
| 260 | public LoggerImpl(File logFile) { |
||
| 261 | // if an exception happens, nothing can be done about it |
||
| 262 | // we just let logStream be null and |
||
| 263 | // the operations of logging won't be done |
||
| 264 | try { |
||
| 265 | this.logWriter = new PrintWriter(new FileWriter(logFile), true); |
||
| 266 | } catch (FileNotFoundException e) { |
||
| 267 | System.err.println("An error happened with the file, nothing will be logged."); |
||
| 268 | this.logWriter = null; |
||
| 269 | } catch (IOException e) { |
||
| 270 | System.err.println("An error happened with the file, nothing will be logged."); |
||
| 271 | this.logWriter = null; |
||
| 272 | } |
||
| 273 | 1 | Anonyme | } |
| 274 | |||
| 275 | @Override |
||
| 276 | protected ILog make_log() { |
||
| 277 | return this; |
||
| 278 | } |
||
| 279 | |||
| 280 | @Override |
||
| 281 | public void addLine(String line) { |
||
| 282 | if (logWriter != null) { |
||
| 283 | logWriter.println(line); |
||
| 284 | } |
||
| 285 | } |
||
| 286 | } |
||
| 287 | } |
||
| 288 | </pre> |
||
| 289 | |||
| 290 | 9 | Anonyme | It can be tested with the previous class by changing the first line instantiating the component: |
| 291 | 1 | Anonyme | <pre> |
| 292 | 12 | Anonyme | Logging.Component logging = new LoggingImplDirectory(new File("/tmp/tutorial2-logging-test/")).newComponent(); |
| 293 | 9 | Anonyme | </pre> |
| 294 | 1 | Anonyme | |
| 295 | 9 | Anonyme | Execute it and confirm that the _/tmp/tutorial2-logging-test/_ directory contains one file per Logger with the correct content. |
| 296 | 1 | Anonyme | |
| 297 | 9 | Anonyme | h2. The Banking Ecosystem |
| 298 | 1 | Anonyme | |
| 299 | 9 | Anonyme | Banking will also be an ecosystem, it will contain Account that are species with a particularity: they have required ports! |
| 300 | Because of that, they can't simply be created but must be composed with other components or species to be usable. |
||
| 301 | 1 | Anonyme | |
| 302 | 9 | Anonyme | h3. Defining the Ecosystem and the Species |
| 303 | |||
| 304 | Create a SpeADL file named _banking.speadl_ in the package _tutorial2.banking_ and define a _Banking_ ecosystem in _tutorial2.banking_ namespace: |
||
| 305 | <pre> |
||
| 306 | import tutorial2.banking.interfaces.IAccountOperations |
||
| 307 | import tutorial2.banking.interfaces.IAccountStatus |
||
| 308 | import tutorial2.logging.interfaces.ILog |
||
| 309 | |||
| 310 | namespace tutorial2.banking { |
||
| 311 | 1 | Anonyme | |
| 312 | 9 | Anonyme | ecosystem Banking { |
| 313 | |||
| 314 | species Account(owner: String) { |
||
| 315 | |||
| 316 | provides operations: IAccountOperations |
||
| 317 | provides status: IAccountStatus |
||
| 318 | |||
| 319 | requires log: ILog |
||
| 320 | } |
||
| 321 | 1 | Anonyme | } |
| 322 | } |
||
| 323 | </pre> |
||
| 324 | |||
| 325 | 9 | Anonyme | Then the interfaces in _tutorial2.banking.interfaces_: |
| 326 | <pre> |
||
| 327 | package tutorial2.banking.interfaces; |
||
| 328 | 1 | Anonyme | |
| 329 | 9 | Anonyme | public interface IAccountOperations { |
| 330 | 1 | Anonyme | |
| 331 | 9 | Anonyme | public void deposit(int value); |
| 332 | public void withdraw(int value); |
||
| 333 | } |
||
| 334 | 1 | Anonyme | |
| 335 | 9 | Anonyme | public interface IAccountStatus { |
| 336 | |||
| 337 | public int getBalance(); |
||
| 338 | } |
||
| 339 | </pre> |
||
| 340 | |||
| 341 | h3. Implementing the Ecosystem and the Species |
||
| 342 | |||
| 343 | We now can define an implementation named _BankingImpl_ in the package _tutorial2.banking.impl_. |
||
| 344 | It exploits the provided ports and the required ports of the species, but also the required port of the ecosystem: |
||
| 345 | <pre> |
||
| 346 | package tutorial2.banking.impl; |
||
| 347 | |||
| 348 | import java.util.concurrent.atomic.AtomicInteger; |
||
| 349 | |||
| 350 | import tutorial2.banking.Banking; |
||
| 351 | import tutorial2.banking.interfaces.IAccountOperations; |
||
| 352 | import tutorial2.banking.interfaces.IAccountStatus; |
||
| 353 | |||
| 354 | public class BankingImpl extends Banking { |
||
| 355 | |||
| 356 | @Override |
||
| 357 | protected Account make_Account(final String owner) { |
||
| 358 | return new Account() { |
||
| 359 | |||
| 360 | private final AtomicInteger balance = new AtomicInteger(); |
||
| 361 | |||
| 362 | @Override |
||
| 363 | protected void start() { |
||
| 364 | eco_requires().elog().addLine("Added a new account for "+owner); |
||
| 365 | } |
||
| 366 | |||
| 367 | @Override |
||
| 368 | protected IAccountOperations make_operations() { |
||
| 369 | return new IAccountOperations() { |
||
| 370 | |||
| 371 | @Override |
||
| 372 | public void withdraw(int value) { |
||
| 373 | provides().operations().deposit(-value); |
||
| 374 | requires().log().addLine(value+" were withdrawn, current balance: "+provides().status().getBalance()); |
||
| 375 | } |
||
| 376 | |||
| 377 | @Override |
||
| 378 | public void deposit(int value) { |
||
| 379 | balance.addAndGet(value); |
||
| 380 | requires().log().addLine(value+" were deposited, current balance: "+provides().status().getBalance()); |
||
| 381 | } |
||
| 382 | }; |
||
| 383 | } |
||
| 384 | |||
| 385 | @Override |
||
| 386 | protected IAccountStatus make_status() { |
||
| 387 | return new IAccountStatus() { |
||
| 388 | @Override |
||
| 389 | public int getBalance() { |
||
| 390 | return balance.get(); |
||
| 391 | } |
||
| 392 | }; |
||
| 393 | } |
||
| 394 | }; |
||
| 395 | } |
||
| 396 | } |
||
| 397 | 1 | Anonyme | </pre> |
| 398 | |||
| 399 | 11 | Anonyme | Notice the calls to *provides()* and *requires()*. |
| 400 | |||
| 401 | Now let's add a bit more log for when a species is instantiated: it must be logged in a file specific to the ecosystem and not to the species. |
||
| 402 | Let's add a required port to the ecosystem _Banking_: |
||
| 403 | <pre> |
||
| 404 | requires elog: ILog |
||
| 405 | </pre> |
||
| 406 | |||
| 407 | Then let's add a *start()* method to the implementation of _Account_ in _BankingImpl_: |
||
| 408 | <pre> |
||
| 409 | @Override |
||
| 410 | protected void start() { |
||
| 411 | 1 | Anonyme | eco_requires().elog().addLine("Added a new account for "+owner); |
| 412 | } |
||
| 413 | 11 | Anonyme | </pre> |
| 414 | |||
| 415 | Notice the use of *eco_requires()* from within the species to access the ports of the ecosystem. |
||
| 416 | |||
| 417 | h2. Composing with Uses into a Bank Application |
||
| 418 | |||
| 419 | Now that we have the two functionality we need, we can build our application to compose them together. |
||
| 420 | In order to do that, we need to define an ecosystem with a species that "uses" the other two. |
||
| 421 | |||
| 422 | 12 | Anonyme | h3. Defining the Ecosystem and the Species |
| 423 | |||
| 424 | 11 | Anonyme | Let's define the ecosystem _Bank_ in a SpeADL file named _bank.speadl_ in the package and namespace _tutorial2.bank_: |
| 425 | <pre> |
||
| 426 | import tutorial2.banking.Banking |
||
| 427 | import tutorial2.banking.interfaces.IAccountOperations |
||
| 428 | import tutorial2.banking.interfaces.IAccountStatus |
||
| 429 | import tutorial2.logging.Logging |
||
| 430 | import tutorial2.logging.interfaces.ILog |
||
| 431 | |||
| 432 | namespace tutorial2.bank { |
||
| 433 | |||
| 434 | ecosystem Bank { |
||
| 435 | |||
| 436 | provides elog: ILog |
||
| 437 | |||
| 438 | part l: Logging |
||
| 439 | |||
| 440 | part b: Banking { |
||
| 441 | bind elog to elog |
||
| 442 | } |
||
| 443 | |||
| 444 | 19 | Anonyme | species BankAccount(owner: String) { |
| 445 | 11 | Anonyme | |
| 446 | 1 | Anonyme | use ll: l.Logger(owner) |
| 447 | use ba: b.Account(owner) { |
||
| 448 | bind log to ll.log |
||
| 449 | } |
||
| 450 | } |
||
| 451 | } |
||
| 452 | } |
||
| 453 | </pre> |
||
| 454 | |||
| 455 | 12 | Anonyme | Notice the following points: |
| 456 | 19 | Anonyme | * Each of the ecosystem that are needed are declared as parts of the _Bank_ ecosystem, and the species we defined in them are declared as uses of the species _BankAccount_. |
| 457 | 12 | Anonyme | * In order to bind the required port of _b: Banking_, the containing ecosystem needs to provide a port that will be used in the binding. This port is not meant to be used from the exterior but from inside the ecosystem. |
| 458 | * The required ports of the _Account_ species are provided by the _Logger_ species. |
||
| 459 | |||
| 460 | 19 | Anonyme | The idea behind such a composition is that when an instance of _BankAccount_ is created, then it implies that an instance of _Logger_ and an instance of _Account_ are created at the same time and used as parts of _BankAccount_. |
| 461 | 12 | Anonyme | Each of the species retains its links to its own ecosystem without leaking it to the outside. |
| 462 | |||
| 463 | h3. Implementing the Ecosystem and the Species |
||
| 464 | |||
| 465 | Implementing the ecosystem is quite straightforward as there is not so much things to add in the implementation. |
||
| 466 | Let's create _BankImpl_ in the package _tutorial2.bank.impl_: |
||
| 467 | <pre> |
||
| 468 | package tutorial2.bank.impl; |
||
| 469 | |||
| 470 | import tutorial2.bank.Bank; |
||
| 471 | import tutorial2.banking.Banking; |
||
| 472 | import tutorial2.logging.Logging; |
||
| 473 | import tutorial2.logging.interfaces.ILog; |
||
| 474 | |||
| 475 | public class BankImpl extends Bank { |
||
| 476 | |||
| 477 | @Override |
||
| 478 | protected ILog make_elog() { |
||
| 479 | // TODO Auto-generated method stub |
||
| 480 | return null; |
||
| 481 | } |
||
| 482 | |||
| 483 | @Override |
||
| 484 | protected Logging make_l() { |
||
| 485 | // TODO Auto-generated method stub |
||
| 486 | return null; |
||
| 487 | } |
||
| 488 | |||
| 489 | @Override |
||
| 490 | protected Banking make_b() { |
||
| 491 | // TODO Auto-generated method stub |
||
| 492 | return null; |
||
| 493 | } |
||
| 494 | |||
| 495 | @Override |
||
| 496 | 19 | Anonyme | protected BankAccount make_BankAccount(String owner) { |
| 497 | 12 | Anonyme | // TODO Auto-generated method stub |
| 498 | return null; |
||
| 499 | } |
||
| 500 | } |
||
| 501 | </pre> |
||
| 502 | |||
| 503 | First, let's fill the methods for the parts: |
||
| 504 | <pre> |
||
| 505 | public class BankImpl extends Bank { |
||
| 506 | |||
| 507 | private final File logDir; |
||
| 508 | |||
| 509 | public BankImpl(File logDir) { |
||
| 510 | this.logDir = logDir; |
||
| 511 | } |
||
| 512 | |||
| 513 | @Override |
||
| 514 | protected Logging make_l() { |
||
| 515 | return new LoggingImplDirectory(logDir); |
||
| 516 | } |
||
| 517 | |||
| 518 | @Override |
||
| 519 | protected Banking make_b() { |
||
| 520 | return new BankingImpl(); |
||
| 521 | } |
||
| 522 | |||
| 523 | // ... |
||
| 524 | } |
||
| 525 | </pre> |
||
| 526 | |||
| 527 | Then let's fill the method for the species. |
||
| 528 | As the species has nothing apart from uses and already delegated provided ports, there is no implementation to do: |
||
| 529 | <pre> |
||
| 530 | public class BankImpl extends Bank { |
||
| 531 | |||
| 532 | // ... |
||
| 533 | |||
| 534 | @Override |
||
| 535 | 19 | Anonyme | protected BankAccount make_BankAccount(String owner) { |
| 536 | return new BankAccount() {}; |
||
| 537 | 12 | Anonyme | } |
| 538 | |||
| 539 | // ... |
||
| 540 | } |
||
| 541 | </pre> |
||
| 542 | |||
| 543 | Finally, let's use the _Logging_ component to get a special _Logger_ for the whole ecosystem: |
||
| 544 | <pre> |
||
| 545 | public class BankImpl extends Bank { |
||
| 546 | |||
| 547 | // ... |
||
| 548 | |||
| 549 | private Logging.Logger.Component eLogger; |
||
| 550 | |||
| 551 | @Override |
||
| 552 | protected void start() { |
||
| 553 | eLogger = parts().l().create().createStandaloneLogger("BANK"); |
||
| 554 | } |
||
| 555 | |||
| 556 | @Override |
||
| 557 | protected ILog make_elog() { |
||
| 558 | return new ILog() { |
||
| 559 | @Override |
||
| 560 | public void addLine(String line) { |
||
| 561 | eLogger.log().addLine(line); |
||
| 562 | } |
||
| 563 | }; |
||
| 564 | } |
||
| 565 | |||
| 566 | // ... |
||
| 567 | } |
||
| 568 | </pre> |
||
| 569 | |||
| 570 | 13 | Anonyme | Notice for that last one that because the _make_elog()_ method is called during initialisation of the component before the *start()* method, we have to explicitly delegate the methods of the _ILog_ interface and can't just return _eLogger.log()_ as the implementation of the port _elog_. |
| 571 | The _eLogger_ class member must be initialised in the *start()* method because *parts()* won't work properly before. |
||
| 572 | |||
| 573 | h2. Adding a GUI to the Bank Ecosystem |
||
| 574 | |||
| 575 | 19 | Anonyme | In order for the _Bank_ ecosystem to be usable, we need to be able to create new _BankAccount_ and interact with them. |
| 576 | 13 | Anonyme | We could add provided ports and interact with it, but here we are going to define another ecosystem encapsulating a GUI for managing the accounts. |
| 577 | This GUI could have been directly added to the _Bank_ ecosystem as it is quite empty, but for the sake of pedagogy, we create another ecosystem in order to show more examples and idioms. |
||
| 578 | |||
| 579 | h3. Preparing the Other Ecosystems |
||
| 580 | |||
| 581 | We need first to add a method to _IAccountStatus_ to be able to get the owner of an account: |
||
| 582 | <pre> |
||
| 583 | public String getOwner(); |
||
| 584 | </pre> |
||
| 585 | |||
| 586 | The implementation in the species of _BankingImpl_ just returns the owner: |
||
| 587 | <pre> |
||
| 588 | @Override |
||
| 589 | public String getOwner() { |
||
| 590 | return owner; |
||
| 591 | } |
||
| 592 | </pre> |
||
| 593 | |||
| 594 | |||
| 595 | Then we need to add a way to instantiate a new account in the _Bank_ ecosystem, let's add a port for that in the ecosystem: |
||
| 596 | <pre> |
||
| 597 | ecosystem Bank { |
||
| 598 | provides manage: IBankManage |
||
| 599 | // ... |
||
| 600 | } |
||
| 601 | </pre> |
||
| 602 | |||
| 603 | <pre> |
||
| 604 | package tutorial2.bank.interfaces; |
||
| 605 | |||
| 606 | public interface IBankManage { |
||
| 607 | public void instantiateAccount(String owner); |
||
| 608 | } |
||
| 609 | </pre> |
||
| 610 | |||
| 611 | 19 | Anonyme | Its implementation in _BankImpl_ simply calls _newBankAccount(owner)_: |
| 612 | 13 | Anonyme | <pre> |
| 613 | @Override |
||
| 614 | protected IBankManage make_manage() { |
||
| 615 | return new IBankManage() { |
||
| 616 | @Override |
||
| 617 | public void instantiateAccount(String owner) { |
||
| 618 | 19 | Anonyme | newBankAccount(owner); |
| 619 | 13 | Anonyme | } |
| 620 | }; |
||
| 621 | } |
||
| 622 | </pre> |
||
| 623 | |||
| 624 | Notice that we don't return the created instance because we won't need it in the GUI: everything will be done using bindings of ports between the uses. |
||
| 625 | |||
| 626 | h3. Defining the Ecosystem |
||
| 627 | |||
| 628 | Let's define a _BankGUI_ ecosystem in a SpeADL file named _gui.speadl_ in the package and namespace _tutorial2.gui_: |
||
| 629 | 14 | Anonyme | <pre> |
| 630 | import tutorial2.bank.interfaces.IBankManage |
||
| 631 | import tutorial2.banking.interfaces.IAccountOperations |
||
| 632 | import tutorial2.banking.interfaces.IAccountStatus |
||
| 633 | |||
| 634 | namespace tutorial2.gui { |
||
| 635 | |||
| 636 | ecosystem BankGUI { |
||
| 637 | |||
| 638 | requires manage: IBankManage |
||
| 639 | |||
| 640 | species AccountGUI { |
||
| 641 | requires operations: IAccountOperations |
||
| 642 | requires status: IAccountStatus |
||
| 643 | } |
||
| 644 | } |
||
| 645 | } |
||
| 646 | </pre> |
||
| 647 | |||
| 648 | The species _AccountGUI_ is responsible of the part of the GUI related to one account, while the ecosystem itself is responsible of the whole frame. |
||
| 649 | In order to manipulate the account, the species has some required ports, and in order to trigger the instantiation of the species, the ecosystem has also some required ports. |
||
| 650 | |||
| 651 | We then need to update the _Bank_ ecosystem as follows in order to exploit _BankGUI_: |
||
| 652 | <pre> |
||
| 653 | namespace tutorial2.bank { |
||
| 654 | |||
| 655 | ecosystem Bank { |
||
| 656 | |||
| 657 | provides manage: IBankManage |
||
| 658 | provides elog: ILog |
||
| 659 | |||
| 660 | part l: Logging |
||
| 661 | |||
| 662 | part b: Banking { |
||
| 663 | bind elog to elog |
||
| 664 | } |
||
| 665 | |||
| 666 | part gui: BankGUI { |
||
| 667 | bind manage to manage |
||
| 668 | } |
||
| 669 | |||
| 670 | 19 | Anonyme | species BankAccount(owner: String) { |
| 671 | 14 | Anonyme | |
| 672 | use ll: l.Logger(owner) |
||
| 673 | use ba: b.Account(owner) { |
||
| 674 | bind log to ll.log |
||
| 675 | } |
||
| 676 | use ga: gui.AccountGUI { |
||
| 677 | bind operations to ba.operations |
||
| 678 | bind status to ba.status |
||
| 679 | } |
||
| 680 | } |
||
| 681 | } |
||
| 682 | } |
||
| 683 | </pre> |
||
| 684 | |||
| 685 | 19 | Anonyme | Notice that we removed the provided ports of _BankAccount_ because they were actually not needed. |
| 686 | 14 | Anonyme | |
| 687 | 19 | Anonyme | Now, when a new _BankAccount_ is instantiated, the species _AccountGUI_ will also be along the other ones. |
| 688 | 14 | Anonyme | We can exploit that to define in the implementation what happens at that point. |
| 689 | |||
| 690 | h3. Implementing the Ecosystem and Species |
||
| 691 | |||
| 692 | The implementation relies on all these ports to instantiate the species when the user clicks on a button. |
||
| 693 | This instantiation will trigger the instantiation of the _AccountGUI_ species, and we will add a row in our GUI for this account. |
||
| 694 | Using the required ports, we can fill the row and call the operations on the account. |
||
| 695 | |||
| 696 | <pre> |
||
| 697 | package tutorial2.gui.impl; |
||
| 698 | |||
| 699 | import java.awt.BorderLayout; |
||
| 700 | import java.awt.event.ActionEvent; |
||
| 701 | import java.awt.event.ActionListener; |
||
| 702 | |||
| 703 | import javax.swing.BoxLayout; |
||
| 704 | import javax.swing.JButton; |
||
| 705 | import javax.swing.JFormattedTextField; |
||
| 706 | import javax.swing.JFrame; |
||
| 707 | import javax.swing.JLabel; |
||
| 708 | import javax.swing.JPanel; |
||
| 709 | import javax.swing.JTextField; |
||
| 710 | import javax.swing.SwingUtilities; |
||
| 711 | |||
| 712 | import tutorial2.gui.BankGUI; |
||
| 713 | |||
| 714 | public class BankGUIImpl extends BankGUI { |
||
| 715 | |||
| 716 | private final JFrame f = new JFrame(); |
||
| 717 | private final JPanel accPanel = new JPanel(); |
||
| 718 | |||
| 719 | @Override |
||
| 720 | protected void start() { |
||
| 721 | SwingUtilities.invokeLater(new Runnable() { |
||
| 722 | @Override |
||
| 723 | public void run() { |
||
| 724 | final JTextField tfAccName = new JTextField("OwnerName"); |
||
| 725 | tfAccName.setColumns(15); |
||
| 726 | final JButton bAddAccount = new JButton("Add"); |
||
| 727 | final JPanel addAccPanel = new JPanel(); |
||
| 728 | addAccPanel.add(tfAccName); |
||
| 729 | addAccPanel.add(bAddAccount); |
||
| 730 | bAddAccount.addActionListener(new ActionListener() { |
||
| 731 | @Override |
||
| 732 | public void actionPerformed(ActionEvent e) { |
||
| 733 | if (!tfAccName.getText().isEmpty()) { |
||
| 734 | requires().manage().instantiateAccount(tfAccName.getText()); |
||
| 735 | tfAccName.setText(""); |
||
| 736 | } |
||
| 737 | } |
||
| 738 | }); |
||
| 739 | f.getContentPane().add(addAccPanel, BorderLayout.PAGE_START); |
||
| 740 | accPanel.setLayout(new BoxLayout(accPanel, BoxLayout.Y_AXIS)); |
||
| 741 | f.getContentPane().add(accPanel, BorderLayout.CENTER); |
||
| 742 | |||
| 743 | f.pack(); |
||
| 744 | f.setVisible(true); |
||
| 745 | f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); |
||
| 746 | } |
||
| 747 | }); |
||
| 748 | } |
||
| 749 | |||
| 750 | @Override |
||
| 751 | protected AccountGUI make_AccountGUI() { |
||
| 752 | return new AccountGUI() { |
||
| 753 | |||
| 754 | private final JPanel panel = new JPanel(); |
||
| 755 | private final JLabel balance = new JLabel(); |
||
| 756 | |||
| 757 | @Override |
||
| 758 | protected void start() { |
||
| 759 | panel.add(new JLabel(requires().status().getOwner())); |
||
| 760 | panel.add(balance); |
||
| 761 | final JFormattedTextField amount = new JFormattedTextField(new Integer(0)); |
||
| 762 | amount.setColumns(5); |
||
| 763 | panel.add(amount); |
||
| 764 | final JButton bPlus = new JButton("+"); |
||
| 765 | bPlus.addActionListener(new ActionListener() { |
||
| 766 | @Override |
||
| 767 | public void actionPerformed(ActionEvent e) { |
||
| 768 | final int toAdd = ((Number) amount.getValue()).intValue(); |
||
| 769 | requires().operations().deposit(toAdd); |
||
| 770 | updateBalance(); |
||
| 771 | } |
||
| 772 | }); |
||
| 773 | panel.add(bPlus); |
||
| 774 | final JButton bMinus = new JButton("-"); |
||
| 775 | bMinus.addActionListener(new ActionListener() { |
||
| 776 | @Override |
||
| 777 | public void actionPerformed(ActionEvent e) { |
||
| 778 | final int toRem = ((Number) amount.getValue()).intValue(); |
||
| 779 | requires().operations().withdraw(toRem); |
||
| 780 | updateBalance(); |
||
| 781 | } |
||
| 782 | }); |
||
| 783 | panel.add(bMinus); |
||
| 784 | final JButton bRem = new JButton("Remove"); |
||
| 785 | bRem.addActionListener(new ActionListener() { |
||
| 786 | @Override |
||
| 787 | public void actionPerformed(ActionEvent e) { |
||
| 788 | accPanel.remove(panel); |
||
| 789 | f.pack(); |
||
| 790 | } |
||
| 791 | }); |
||
| 792 | panel.add(bRem); |
||
| 793 | updateBalance(); |
||
| 794 | accPanel.add(panel); |
||
| 795 | f.pack(); |
||
| 796 | } |
||
| 797 | |||
| 798 | private void updateBalance() { |
||
| 799 | balance.setText(""+requires().status().getBalance()); |
||
| 800 | } |
||
| 801 | }; |
||
| 802 | } |
||
| 803 | } |
||
| 804 | </pre> |
||
| 805 | |||
| 806 | We can notice that the remove button will simply remove the panel that contains references to the species. |
||
| 807 | 19 | Anonyme | Because the elements of the _BankAccount_ species and its sub-components are all related to each others but not at all with the exterior, the fact that this only reference between the GUI and the species disappear means that the garbage collector of the JVM will remove the instance of the species and its content from the memory. |
| 808 | 15 | Anonyme | |
| 809 | h3. Executing |
||
| 810 | |||
| 811 | 20 | Anonyme | We can finally define a class to execute all of that: |
| 812 | <pre> |
||
| 813 | package tutorial2; |
||
| 814 | |||
| 815 | import java.io.File; |
||
| 816 | |||
| 817 | public class Application { |
||
| 818 | |||
| 819 | public static void main(String[] args) { |
||
| 820 | new BankImpl(new File("/tmp/tutorial2-bank-logs/")).newComponent(); |
||
| 821 | } |
||
| 822 | } |
||
| 823 | </pre> |
||
| 824 | 18 | Anonyme | |
| 825 | h2. Conclusion |
||
| 826 | |||
| 827 | This tutorial for SpeADL finishes here. |
||
| 828 | |||
| 829 | h1. Wiki Page Resources |