JavaGram Agile Development Version 4 Sharam Hekmat PragSoft Corporation Copyright © 2009-2012 by Sharam Hekmat, PragSoft Corporation All rights reserved. Printed in the USA by PragSoft Press. To download JavaGram and code samples included in this book, visit www.pragsoft.com To send feedback to the author (bug reports, enhancement suggestions, and the like) write to: hekmat@pragsoft.com JavaGram is free for non-commercial use. To enquire about commercial use and licensing of JavaGram, or formal training, write to: sales@pragsoft.com Contents 1 INTRODUCTION .............................................................................................................................. 12 1.1 BACKGROUND ..................................................................................................................................12 1.1.1 Agility Criteria .......................................................................................................................13 1.1.2 Barriers to Rapid Development .............................................................................................14 1.1.3 Barriers to Rapid Testing ......................................................................................................15 1.1.4 Barriers to Rapid Evolution ...................................................................................................16 1.2 SALIENT FEATURES..........................................................................................................................16 1.2.1 Server Centricity ....................................................................................................................17 1.2.2 Browser-based and Native Desktop Clients ..........................................................................19 1.2.3 Static and Dynamic Loading..................................................................................................19 1.2.4 Code Caching ........................................................................................................................19 1.2.5 Built-in Types .........................................................................................................................20 1.2.6 Object Orientation .................................................................................................................20 1.2.7 Multiple Inheritance ..............................................................................................................21 1.2.8 Automatic Remoting...............................................................................................................21 1.2.9 Asynchronous Method Invocation..........................................................................................22 1.2.10 Declarative GUIs ..............................................................................................................22 1.2.11 Parameterized Text ...........................................................................................................22 1.2.12 Database Interaction ........................................................................................................23 1.2.13 Serialization and Parsing .................................................................................................23 1.2.14 Business Objects ...............................................................................................................23 1.2.15 Java Interoperation ...........................................................................................................23 1.3 IMPLEMENTATION ............................................................................................................................24 1.3.1 JavaGram Runtime Environment...........................................................................................24 1.3.2 Compilation ...........................................................................................................................25 1.3.3 JavaGram IDE .......................................................................................................................26 1.3.4 JavaGram Server Monitor .....................................................................................................26 1.3.5 JavaGram Standard Library Scripts ......................................................................................26 1.4 DOWNLOAD AND INSTALLATION .....................................................................................................27 2 FUNDAMENTALS ............................................................................................................................ 28 2.1 EXAMPLE .........................................................................................................................................28 2.2 LOADING SCRIPTS ............................................................................................................................30 2.3 EXPRESSIONS AND STATEMENTS......................................................................................................34 2.4 TYPES ..............................................................................................................................................34 2.5 CONTROL FLOW ...............................................................................................................................35 2.6 ITERATION .......................................................................................................................................39 2.7 COMPOSITES ....................................................................................................................................42 2.7.1 Containers .............................................................................................................................42 2.7.2 Lists........................................................................................................................................43 2.7.3 Vectors ...................................................................................................................................44 2.7.4 Maps ......................................................................................................................................47 2.7.5 Object Literals .......................................................................................................................50 2.7.6 Literal versus Dynamic ..........................................................................................................52 2.8 EXCEPTION HANDLING ....................................................................................................................53 3 OBJECT-ORIENTED PROGRAMMING ...................................................................................... 56 3.1 3.2 3.3 3.4 Content INHERITANCE ...................................................................................................................................56 SHOPPING CART EXAMPLE...............................................................................................................58 MUTUAL CLASSES ...........................................................................................................................67 FINAL QUALIFIER .............................................................................................................................68 3 3.5 3.6 3.7 4 THIS AND SUPER ..............................................................................................................................68 METHOD PARAMETERS ....................................................................................................................69 CLASS VARIABLES ...........................................................................................................................71 GUI PROGRAMMING ..................................................................................................................... 72 4.1 DEMO APPLICATION ........................................................................................................................72 4.2 PANELS, LAYOUTS, AND FIELDS ......................................................................................................74 4.2.1 Data Binding..........................................................................................................................80 4.3 TREES ..............................................................................................................................................82 4.3.1 Using a Tree Model ...............................................................................................................85 4.4 TABLES ............................................................................................................................................88 4.4.1 Using a Table Model .............................................................................................................90 4.4.2 Lists........................................................................................................................................93 4.5 GRIDS ..............................................................................................................................................95 4.6 COMPONENT STATUS .....................................................................................................................102 4.7 DIALOGS AND ALERTS ...................................................................................................................107 4.7.1 Dialogs ................................................................................................................................107 4.7.2 Alerts....................................................................................................................................109 4.7.3 File Chooser ........................................................................................................................110 4.7.4 Color Chooser .....................................................................................................................111 4.8 GRAPHS .........................................................................................................................................112 4.8.1 Pie Charts ............................................................................................................................112 4.8.2 Line Graphs .........................................................................................................................114 4.8.3 Bar and Stack Graphs ..........................................................................................................115 4.8.4 Bubble Graph ......................................................................................................................117 4.8.5 Pipe Graph ..........................................................................................................................119 4.8.6 Gauge and Range Bar .........................................................................................................120 4.8.7 Flow Graphs ........................................................................................................................121 4.9 MENUS AND TOOLBARS .................................................................................................................125 4.9.1 Pull-down Menus .................................................................................................................125 4.9.2 Popup Menus .......................................................................................................................128 4.9.3 Toolbars ...............................................................................................................................128 4.10 GOOGLE MAP ............................................................................................................................130 4.11 CODE EDITOR ............................................................................................................................132 4.12 WORKING WITH HTML.............................................................................................................134 4.12.1 Displaying HTML ...........................................................................................................134 4.12.2 Generating HTML Reports .............................................................................................136 4.12.3 Generating Images ..........................................................................................................137 4.13 TIPS AND TRICKS.......................................................................................................................137 4.13.1 Using Boiler Plates .........................................................................................................137 4.13.2 Managing Component Visibility .....................................................................................138 4.13.3 Correct Use of Layouts ...................................................................................................139 4.13.4 Managing Periodic Tasks ...............................................................................................140 4.13.5 Using Worker Threads ....................................................................................................141 4.13.6 Procedural Creation of Components ..............................................................................143 4.14 BROWSER-BASED GUIS.............................................................................................................144 4.15 SUMMARY OF ELEMENTS ..........................................................................................................145 5 SQL PROGRAMMING .................................................................................................................. 147 5.1 WORKING WITH DATABASES .........................................................................................................147 5.1.1 Explicit Connection .............................................................................................................148 5.1.2 Implicit Connection .............................................................................................................148 5.2 TEXT MEMBERS .............................................................................................................................150 5.2.1 Text ......................................................................................................................................150 5.2.2 Text.sql.................................................................................................................................151 4 JavaGram Agile Development 5.3 TRANSACTIONS ..............................................................................................................................152 5.3.1 Creating a Database ............................................................................................................152 5.3.2 Creating Tables ...................................................................................................................152 5.3.3 Dropping Tables ..................................................................................................................154 5.3.4 Inserting Rows .....................................................................................................................154 5.3.5 Updating Rows ....................................................................................................................156 5.3.6 Deleting Rows ......................................................................................................................157 5.4 QUERIES.........................................................................................................................................157 5.4.1 Retrieving Rows ...................................................................................................................158 5.4.2 Processing a Result Set ........................................................................................................159 5.4.3 Retrieving Attributes ............................................................................................................159 5.4.4 Counting Rows .....................................................................................................................160 5.4.5 Performing Joins .................................................................................................................160 5.5 CALLING STORED PROCEDURES .....................................................................................................162 5.6 STREAMLINED DATA MODELING ...................................................................................................163 5.7 BUSINESS OBJECT MODEL .............................................................................................................165 5.7.1 Subclassing Object...............................................................................................................165 5.7.2 File Handling .......................................................................................................................168 5.7.3 Locking ................................................................................................................................173 6 ADVANCED TOPICS ..................................................................................................................... 179 6.1 CLIENT-SERVER COMMUNICATION ................................................................................................179 6.1.1 Remote Methods ..................................................................................................................180 6.1.2 Remote Classes ....................................................................................................................185 6.1.3 Exception Handling .............................................................................................................189 6.1.4 Targeted Remote Calls ........................................................................................................189 6.1.5 Clocal, slocal, and side ........................................................................................................191 6.2 THREADS .......................................................................................................................................192 6.2.1 Working with Threads..........................................................................................................192 6.2.2 Synchronization ...................................................................................................................194 6.2.3 ThreadLocal Fields..............................................................................................................195 6.2.4 Timer Class ..........................................................................................................................196 6.3 ASYNCHRONOUS BEHAVIOR ..........................................................................................................197 6.3.1 Local Asynchronous Call .....................................................................................................198 6.3.2 Parallel Processing .............................................................................................................199 6.3.3 Remote Asynchronous Call ..................................................................................................200 6.3.4 Guarded Asynchronous Call................................................................................................201 6.4 REPORT GENERATION ....................................................................................................................201 6.4.1 Report Class ........................................................................................................................203 6.4.2 ReportEngine Class .............................................................................................................203 6.4.3 FopConverter Class .............................................................................................................204 6.4.4 JodConverter Class .............................................................................................................206 6.4.5 Example ...............................................................................................................................207 6.5 WEB SERVICES...............................................................................................................................213 6.5.1 Architecture .........................................................................................................................213 6.5.2 JavaGramService .................................................................................................................213 6.5.2.1 6.5.2.2 6.5.2.3 JavaGramService Methods ............................................................................................................ 214 XML Syntax for methodCall() ...................................................................................................... 215 XML Representation of JavaGram Data ....................................................................................... 217 6.5.3 Software Installation ............................................................................................................218 EDIT TOMCAT’S .......................................................................................................................................219 6.5.4 Configuration.......................................................................................................................220 6.5.4.1 6.5.4.2 6.5.4.3 6.5.4.4 Content Server Configuration ..................................................................................................................... 220 JAGWS Configuration .................................................................................................................. 221 Additional JAR files ...................................................................................................................... 221 Virtual Directory ........................................................................................................................... 222 5 6.5.5 Virtual Directory .................................................................................................................222 6.5.6 Client Example ....................................................................................................................223 6.6 EFFICIENCY CONSIDERATIONS .......................................................................................................225 6.6.1 System-level Caching ...........................................................................................................225 6.6.2 System-level Compression ...................................................................................................225 6.6.3 Application-level Caching ...................................................................................................226 6.6.4 Remoting Performance Factors ...........................................................................................226 7 DEPLOYMENT ............................................................................................................................... 228 7.1 RUNNING A PROGRAM ...................................................................................................................228 7.1.1 JavaGram Options ...............................................................................................................228 7.1.2 Java Options ........................................................................................................................231 7.1.3 Compilation .........................................................................................................................231 7.2 SERVER DEPLOYMENT ...................................................................................................................232 7.2.1 Application Server ...............................................................................................................232 7.2.2 Proxy Server ........................................................................................................................237 7.2.3 Multi-tier Servers .................................................................................................................238 7.2.4 Deploying a Server as a Service ..........................................................................................239 7.3 NATIVE CLIENT DEPLOYMENT .......................................................................................................239 7.3.1 JavaGram Application Booter .............................................................................................239 7.3.2 Automatic Upgrade..............................................................................................................241 7.4 BROWSER CLIENT DEPLOYMENT ...................................................................................................242 7.4.1 HTML Embedding ...............................................................................................................242 7.4.2 Flash Security Sandbox .......................................................................................................244 7.4.3 Partitions .............................................................................................................................244 7.4.4 Deploying to a Web Server ..................................................................................................246 7.4.5 Server Deployment with BlazeDS ........................................................................................246 7.5 STANDALONE DEPLOYMENT ..........................................................................................................247 7.6 FILE CACHING ................................................................................................................................248 7.6.1 Server-side Caching.............................................................................................................248 7.6.2 Client-side Caching .............................................................................................................248 7.7 JAVAGRAM SERVER MONITOR ......................................................................................................249 7.7.1 Running JSM........................................................................................................................249 7.7.2 Server Tabs ..........................................................................................................................250 8 QUEST SAMPLE APPLICATION................................................................................................ 256 8.1 INTRODUCTION ..............................................................................................................................256 8.1.1 Requirements .......................................................................................................................256 8.1.1.1 8.1.1.2 8.1.1.3 8.1.1.4 8.1.1.5 8.1.1.6 8.1.2 User Interface ......................................................................................................................260 8.1.2.1 8.1.2.2 8.1.2.3 8.1.2.4 8.1.2.5 8.1.2.6 8.1.2.7 8.1.2.8 8.1.2.9 8.1.2.10 8.1.2.11 8.2 6 Issue Life Cycle ............................................................................................................................. 257 Issue BO ........................................................................................................................................ 258 History BO .................................................................................................................................... 259 User BO......................................................................................................................................... 259 Attachment BO .............................................................................................................................. 259 Rules BO ....................................................................................................................................... 260 Login Window............................................................................................................................... 260 Main Window................................................................................................................................ 261 Menubar and Toolbar .................................................................................................................... 262 User Admin Panel ......................................................................................................................... 263 User Details Panel ......................................................................................................................... 263 Issue Search Panel ......................................................................................................................... 264 Issue Details Panel ........................................................................................................................ 266 Generating Reports ........................................................................................................................ 268 Log Panel ...................................................................................................................................... 269 Pick Lists Panel ........................................................................................................................ 269 Help Panel ................................................................................................................................ 270 DESIGN ..........................................................................................................................................270 JavaGram Agile Development 8.2.1 Object Model .......................................................................................................................270 8.2.1.1 8.2.1.2 8.2.1.3 8.2.1.4 8.2.1.5 8.2.1.6 8.2.1.7 quest/ ............................................................................................................................................. 270 quest/gui/ ....................................................................................................................................... 271 quest/bom/ ..................................................................................................................................... 272 quest/gui/user/ ............................................................................................................................... 273 quest/gui/issue/ .............................................................................................................................. 274 quest/gui/misc/ .............................................................................................................................. 275 quest/gui/setting/ ........................................................................................................................... 275 8.2.2 Data Model ..........................................................................................................................276 8.3 IMPLEMENTATION ..........................................................................................................................276 8.3.1 Business Objects ..................................................................................................................276 8.3.1.1 8.3.1.2 8.3.1.3 8.3.1.4 8.3.1.5 8.3.1.6 8.3.2 Main Window .......................................................................................................................290 8.3.2.1 8.3.2.2 8.3.2.3 8.3.2.4 8.3.3 9 PickListPanel................................................................................................................................. 322 LogPanel ....................................................................................................................................... 323 Miscellaneous ......................................................................................................................324 8.3.6.1 8.3.6.2 8.3.7 IssueSearch .................................................................................................................................... 306 IssueScreen .................................................................................................................................... 309 AssignDialog ................................................................................................................................. 319 ResolveDialog ............................................................................................................................... 320 RejectDialog .................................................................................................................................. 321 Setting ..................................................................................................................................322 8.3.5.1 8.3.5.2 8.3.6 UserSearch .................................................................................................................................... 301 UserScreen .................................................................................................................................... 303 PasswordDialog............................................................................................................................. 304 Issue Management ...............................................................................................................306 8.3.4.1 8.3.4.2 8.3.4.3 8.3.4.4 8.3.4.5 8.3.5 Quest ............................................................................................................................................. 290 AppPanel ....................................................................................................................................... 293 AppTree ........................................................................................................................................ 295 Commands..................................................................................................................................... 298 User Admin ..........................................................................................................................301 8.3.3.1 8.3.3.2 8.3.3.3 8.3.4 User BO......................................................................................................................................... 276 Issue BO ........................................................................................................................................ 280 Attachment BO .............................................................................................................................. 284 History BO .................................................................................................................................... 287 Rule BO......................................................................................................................................... 288 DbTables ....................................................................................................................................... 289 AboutDialog .................................................................................................................................. 324 HtmlPanel ...................................................................................................................................... 324 Configuration.......................................................................................................................324 EXTENSIONS .................................................................................................................................. 326 9.1 INTRODUCTION ..............................................................................................................................326 9.1.1 Parse Tree............................................................................................................................326 9.1.2 Types ....................................................................................................................................328 9.1.3 Objects .................................................................................................................................329 9.1.4 IO .........................................................................................................................................330 9.1.5 Environment.........................................................................................................................330 9.2 WRITING A GUI EXTENSION ..........................................................................................................330 9.2.1 Usage ...................................................................................................................................331 9.2.2 Naming Conventions ............................................................................................................332 9.2.3 Implementation ....................................................................................................................332 9.3 WRITING A TEXT EXTENSION.........................................................................................................336 9.4 WRITING AN AD-HOC EXTENSION..................................................................................................338 9.5 PACKAGING YOUR EXTENSION ......................................................................................................340 10 CORE REFERENCE ...................................................................................................................... 341 10.1 10.2 Content COMMENTS ...............................................................................................................................341 IDENTIFIERS ..............................................................................................................................341 7 10.2.1 Reserved Words ..............................................................................................................341 10.2.2 Qualification ...................................................................................................................342 10.2.3 Naming Conventions .......................................................................................................342 10.3 JAG ELEMENT............................................................................................................................343 10.3.1 Jag Element Properties ...................................................................................................343 10.4 LOAD ELEMENT ........................................................................................................................344 10.4.1 Load Element Properties ................................................................................................345 10.4.2 Data Types ......................................................................................................................345 10.4.2.1 10.4.2.2 10.4.2.3 10.4.2.4 10.4.2.5 10.4.2.6 10.4.2.7 10.4.2.8 Atomic Types ........................................................................................................................... 346 Composite Types...................................................................................................................... 346 User-defined Types .................................................................................................................. 347 Native Type .............................................................................................................................. 347 Vague Type .............................................................................................................................. 347 Void Type ................................................................................................................................ 347 Implicit Type Cast .................................................................................................................... 347 Explicit Type Cast .................................................................................................................... 348 10.5 LITERALS ..................................................................................................................................348 10.5.1 Atomic Literals ................................................................................................................348 10.5.1.1 10.5.1.2 10.5.1.3 10.5.1.4 10.5.1.5 10.5.1.6 10.5.1.7 10.5.1.8 10.5.1.9 10.5.2 10.5.2.1 10.5.2.2 10.5.2.3 10.5.2.4 Boolean Literals ....................................................................................................................... 348 Character Literals ..................................................................................................................... 349 Integer Literals ......................................................................................................................... 349 Real Literals ............................................................................................................................. 349 String Literals........................................................................................................................... 350 Symbol Literals ........................................................................................................................ 350 Date Literals ............................................................................................................................. 350 Binary Literals ......................................................................................................................... 351 XML Literals ........................................................................................................................... 351 Composite Literals ..........................................................................................................351 Vector Literals ......................................................................................................................... 351 List Literals .............................................................................................................................. 352 Map Literals ............................................................................................................................. 352 Object Literals .......................................................................................................................... 353 10.6 EXPRESSIONS ............................................................................................................................354 10.6.1 Unary Operators .............................................................................................................354 10.6.1.1 10.6.1.2 10.6.1.3 10.6.1.4 10.6.1.5 10.6.1.6 10.6.1.7 10.6.1.8 10.6.1.9 10.6.2 10.6.2.1 10.6.2.2 10.6.2.3 10.6.2.4 10.6.2.5 10.6.2.6 10.6.2.7 10.6.2.8 10.6.2.9 10.6.2.10 10.6.2.11 10.6.2.12 10.6.2.13 10.6.2.14 10.6.2.15 10.6.3 8 + Operator ................................................................................................................................ 354 – Operator ................................................................................................................................ 354 ++ Operator .............................................................................................................................. 354 -- Operator................................................................................................................................ 354 ! Operator ................................................................................................................................. 355 ~ Operator ................................................................................................................................ 355 Typeof() Operator .................................................................................................................... 355 Valueof() Operator ................................................................................................................... 355 Arg() Operator ......................................................................................................................... 356 Binary Operators ............................................................................................................356 = and @= Operators ................................................................................................................. 356 + and += Operators .................................................................................................................. 357 – and –= Operators ................................................................................................................... 358 * and *= Operators ................................................................................................................... 359 and /= Operators ....................................................................................................................... 359 % and %= Operators ................................................................................................................ 359 Equality (==, ?=, !=, ===, !==) Operators ............................................................................... 359 Relational (<, <=, >, >=) Operators.......................................................................................... 360 Logical (&&, ||) Operators ....................................................................................................... 360 Bitwise (&, &=, |, |=, ^, ^=) Operators ..................................................................................... 360 Bitwise Shift (<<, <<=, >>, >>=) Operators ............................................................................ 361 List/vector/map access Operator .............................................................................................. 361 Object access (. and ?.) Operators ............................................................................................ 361 @ Operator............................................................................................................................... 362 Instanceof Operator .................................................................................................................. 363 Ternary Operators ..........................................................................................................363 JavaGram Agile Development 10.6.3.1 10.6.3.2 10.6.4 10.6.4.1 10.6.4.2 10.6.4.3 10.6.4.4 10.6.4.5 Conditional Expressions........................................................................................................... 363 Asynchronous Method Invocation ........................................................................................... 363 N-ary Operators ..............................................................................................................365 Vector Operator........................................................................................................................ 365 List Operator ............................................................................................................................ 365 Map Operator ........................................................................................................................... 365 New Operator ........................................................................................................................... 366 Object Operator ........................................................................................................................ 366 10.6.5 Operator Precedence ......................................................................................................367 10.6.6 Delayed Strings ...............................................................................................................367 10.6.7 Method Invocation ..........................................................................................................368 10.7 STATEMENTS .............................................................................................................................368 10.7.1 Expression Statement ......................................................................................................368 10.7.2 Blocks ..............................................................................................................................368 10.7.3 If Statement .....................................................................................................................369 10.7.4 While Loop ......................................................................................................................369 10.7.5 Do Loop ..........................................................................................................................370 10.7.6 For Loop .........................................................................................................................370 10.7.7 For-in Loop .....................................................................................................................371 10.7.8 Break Statement ..............................................................................................................371 10.7.9 Continue Statement .........................................................................................................371 10.7.10 Return Statement .............................................................................................................372 10.7.11 Switch Statement .............................................................................................................372 10.7.12 Throw Statement .............................................................................................................373 10.7.13 Try Statement ..................................................................................................................373 10.7.14 Synchronized Statement ..................................................................................................374 10.7.15 Query Statement ..............................................................................................................374 10.7.16 Transaction Statement ....................................................................................................375 10.7.17 Assert Statement ..............................................................................................................375 10.8 CLASSES ....................................................................................................................................375 10.8.1 Class qualifiers ...............................................................................................................376 10.8.2 Class Properties ..............................................................................................................377 10.8.3 Fields ..............................................................................................................................377 10.8.3.1 10.8.3.2 10.8.4 10.8.4.1 10.8.4.2 10.8.4.3 10.8.4.4 10.8.4.5 10.8.4.6 10.8.4.7 10.8.4.8 10.8.4.9 10.8.4.10 10.8.4.11 10.8.5 10.8.5.1 10.8.5.2 10.8.5.3 10.8.5.4 10.8.5.5 10.8.6 10.8.6.1 10.8.6.2 10.8.7 10.8.8 Content Field Qualifiers ........................................................................................................................ 377 Field Initialization .................................................................................................................... 378 Methods...........................................................................................................................378 Method Qualifiers .................................................................................................................... 379 Constructors ............................................................................................................................. 380 Invoking Methods .................................................................................................................... 380 This .......................................................................................................................................... 381 Default Arguments ................................................................................................................... 381 Variable Number of Arguments ............................................................................................... 381 Method Overloading ................................................................................................................ 382 Type Checking ......................................................................................................................... 382 Remote Methods ...................................................................................................................... 382 Targeted Remote Calls ............................................................................................................. 384 Local Methods ......................................................................................................................... 384 Inheritance ......................................................................................................................385 Abstract Methods ..................................................................................................................... 385 Polymorphism .......................................................................................................................... 386 Super ........................................................................................................................................ 386 Mutual Classes ......................................................................................................................... 386 Subclass Constructor Rules ...................................................................................................... 387 Text Members ..................................................................................................................388 Text Properties ......................................................................................................................... 389 Text Qualification .................................................................................................................... 389 GUI Members .................................................................................................................390 Static Initialization Blocks ..............................................................................................390 9 10.8.9 Singleton Classes ............................................................................................................390 10.8.10 Remote Classes ...............................................................................................................391 10.9 EXCEPTIONS ..............................................................................................................................392 10.10 THE SYS PSEUDO CLASS ...........................................................................................................393 10.10.1 Sys Attributes ..................................................................................................................393 10.10.2 Sys Methods ....................................................................................................................395 10.10.2.1 10.10.2.2 10.10.2.3 10.10.2.4 10.10.2.5 10.10.2.6 10.10.2.7 10.10.2.8 10.10.2.9 10.10.2.10 11 Parsing and Evaluation............................................................................................................. 395 File Handling ........................................................................................................................... 396 Input/Output ............................................................................................................................. 400 Debugging ................................................................................................................................ 401 String Handling ........................................................................................................................ 402 Client-server ............................................................................................................................. 407 Maths ....................................................................................................................................... 413 Date .......................................................................................................................................... 415 Collections ............................................................................................................................... 416 Miscellaneous ..................................................................................................................... 419 GUI REFERENCE .......................................................................................................................... 424 11.1 MARKUP....................................................................................................................................424 11.1.1 Element Qualifiers ..........................................................................................................425 11.1.2 Element Identifiers ..........................................................................................................425 11.1.3 Element Properties..........................................................................................................426 11.1.3.1 11.1.3.2 Event Handlers ......................................................................................................................... 426 Data Models ............................................................................................................................. 427 11.1.4 Boiler Plates ...................................................................................................................428 11.2 ELEMENT TYPES........................................................................................................................428 11.2.1 Abstract Elements ...........................................................................................................428 11.2.2 Application Elements ......................................................................................................432 11.2.3 Window Elements ............................................................................................................433 11.2.4 Layout Elements ..............................................................................................................436 11.2.5 Container Elements .........................................................................................................438 11.2.6 Menu Item Elements ........................................................................................................442 11.2.7 Decorative Elements .......................................................................................................443 11.2.8 Field Elements ................................................................................................................444 11.2.9 Selection Elements ..........................................................................................................446 11.2.10 Button Elements ..............................................................................................................449 11.2.11 Feedback Elements .........................................................................................................449 11.2.12 Area Elements .................................................................................................................450 11.2.13 Tree Elements .................................................................................................................450 11.2.14 Table Elements ................................................................................................................452 11.2.15 Grid Elements .................................................................................................................455 11.2.16 Graph Elements ..............................................................................................................459 11.2.17 Gadget Elements .............................................................................................................468 11.2.18 Map Elements .................................................................................................................469 11.2.19 Jade Elements .................................................................................................................472 11.2.20 Reference Elements .........................................................................................................473 11.2.21 Invisible Elements ...........................................................................................................474 11.3 THE GUI PSEUDO CLASS ...........................................................................................................476 11.3.1 Gui Attributes ..................................................................................................................476 11.3.2 Gui Methods ....................................................................................................................476 12 SQL REFERENCE .......................................................................................................................... 482 12.1 QUERY AND TRANSACTION STATEMENTS .................................................................................482 12.2 MARKUP....................................................................................................................................482 12.2.1 Field Translation ............................................................................................................482 12.2.2 Text.sql ............................................................................................................................482 12.2.3 Text.sql.query ..................................................................................................................483 10 JavaGram Agile Development 12.2.4 Text.sql.update ................................................................................................................484 12.2.5 Text.sql.prepare ..............................................................................................................484 12.2.6 Text.sql.callable ..............................................................................................................484 12.2.7 Element Properties..........................................................................................................485 12.2.8 Capability Summary........................................................................................................486 12.3 DATABASE CONNECTION CONFIGURATION ...............................................................................487 12.4 THE SQL PSEUDO CLASS ...........................................................................................................488 12.5 BUSINESS OBJECT MANAGER ....................................................................................................495 12.6 THE BOM PSEUDO CLASS ..........................................................................................................497 13 LIBRARY REFERENCE................................................................................................................ 500 13.1 13.2 13.3 13.4 13.5 13.6 14 LIB/LANG/ .................................................................................................................................500 LIB/SVR/ ....................................................................................................................................502 LIB/IO/ .......................................................................................................................................503 LIB/GUI/ ....................................................................................................................................504 LIB/BOM/ ...................................................................................................................................511 LIB/SQL/ ....................................................................................................................................513 JADE – JAVAGRAM IDE .............................................................................................................. 514 14.1 OVERVIEW ................................................................................................................................514 14.2 PROJECTS ..................................................................................................................................515 14.2.1 Project Operations ..........................................................................................................515 14.2.2 Project Tree ....................................................................................................................516 14.2.3 Outline Tree ....................................................................................................................518 14.2.4 Project Properties ...........................................................................................................519 14.2.5 Project Statistics .............................................................................................................523 14.2.6 Building a Project ...........................................................................................................523 14.3 EDITOR ......................................................................................................................................524 14.3.1 File Operations ...............................................................................................................525 14.3.2 Editing Operations ..........................................................................................................527 14.3.3 Code Insight ....................................................................................................................529 14.3.4 Reformatting a File .........................................................................................................530 14.3.5 Viewing a Script in HTML ..............................................................................................531 14.4 SEARCHING AND REFACTORING ................................................................................................531 14.4.1 Searching a File ..............................................................................................................531 14.4.2 Searching a Project.........................................................................................................532 14.4.3 Refactoring......................................................................................................................533 14.5 RUNNING ...................................................................................................................................533 14.6 DEBUGGING ..............................................................................................................................534 14.7 TOOLS .......................................................................................................................................537 14.7.1 Import Java Classes ........................................................................................................537 14.7.2 Preferences .....................................................................................................................539 15 JAVAGRAM SYNTAX ................................................................................................................... 543 15.1 15.2 15.3 Content CONVENTIONS ...........................................................................................................................543 JAG PRODUCTION RULES .........................................................................................................543 JTP PRODUCTION RULES...........................................................................................................553 11 1 Introduction JavaGram is a new technology specifically designed to support agile development. As a language, it shares many of Java’s features – syntax, platform independence, strong type checking, object orientation, and garbage collection – but also offers capabilities that make it a much easier-to-use and productive platform, such as declarative programming, automatic remoting, asynchronous method invocation, and dynamic loading. JavaGram enables you to deploy a standalone or multi-tier client-server application from a single code base, allowing you to run your client in an Internet browser as well as a native desktop application. It manages the underlying complexities of distributed applications for you so that you can focus on what matters most: implementing business functionality. Furthermore, time consuming activities such as application maintenance and patching are significantly simplified through a server-centric release process that automatically updates all dependent deployment points. The technology is simple and easy to learn; yet the benefits are overwhelming – up to an order of magnitude gain in productivity and robustness when compared to conventional technologies. 1.1 Background There has been growing interest in recent years in agile software development methods. The shortcomings of traditional, waterfall methods have been known for at least three decades, namely: The requirements for a complex system can rarely be specified fully and accurately in advance. Despite tight quality control, the output of each phase will contain gaps or flaws that won’t be discovered until a later phase. User requirements will evolve during the course of a long project, thus making the end-result inconsistent with the latest requirements. The cost of fixing a requirement or design defect discovered later in the project is substantial. Given that a working system is not available until late in the project, there is little opportunity for user participation and feedback; this increases the risk of the system not being accepted by the users. These challenges often cause schedule delays and budget blowouts. The agile approach attempts to address these difficulties by promoting a more iterative lifecycle, where emphasis is on prototyping, user participation, having a working system 12 JavaGram Agile Development all along, and less documentation. The approach is best summarized by the agile manifesto (www.agilemanifesto.org), which places more value on: individuals and interactions over processes and tools, working software over comprehensive documentation, customer collaboration over contract negotiation, and responding to change over following a plan. Successful adoption of the agile approach requires the overcoming of cultural, process, and practice barriers. Although these challenges are primarily non-technological, the use of suitable technology can be of substantial benefit. The reality is that most technologies available today are not particularly well-suited to the agile approach, typically because they either predate the ‘agile age’ or ignore its dynamics. JavaGram has been specifically designed to support the dynamics of agile development. Its conception is the result of over ten years of experience gained from successfully practicing RAD and agile in commercial software projects. As such, it’s a technology designed by a practitioner for practitioners. 1.1.1 Agility Criteria So what makes a technology more suited to agile development than others? The answer to this question lies in the dynamics of practicing agile in real-life projects. Agile relies heavily on iterative development. The first priority in a project is to produce a working prototype of a proposed system and then to use this as a vehicle for eliciting detailed user requirements. As requirements emerge, these are used to further refine and enhance the prototype. Enhancements are done as a series of mini development cycles where during each cycle we design, code, and test the next increment, and invite users to evaluate the outcome. The cyclic and iterative nature of agile places great emphasis on going from requirements to working software rapidly. Speed is of the essence; otherwise cycles become slow and ineffective. Rapid delivery of the next cycle ensures that users will remain engaged and the project will not lose momentum. At the same time, it’s important to keep the project team small to avoid communication overheads and to minimize the need for detailed documentation, both of which will slow things down. In order to speed up each cycle, we must not only have the means to design and code business functionality quickly, but also to rapidly turnaround defects raised during the testing phase. When this is not the case, testing can become hopelessly inefficient, as testers will spend most of their time waiting for critical fixes without which further testing can’t take place. Equally important is the cross cycle speed. If each subsequent cycle takes longer than the previous one due to developers finding it difficult to add new functionality then Introduction 13 momentum will be lost and eventually grind to a halt. It is therefore vital that the application under development lends itself to alteration and evolution. While this is primarily influenced by the quality and foresight of the original design, the underlying technology can go a long way in promoting good practices that avoid design inflexibility. Finally, agile is all about simplicity. Simplicity is achieved by untangling complexity so that the essential is separated from the accidental. Once we’ve identified what’s essential, the least complicated way of getting there has the potential to deliver the best outcome. Without constant awareness of this, IT professionals have a tendency to be dazzled by technological complexity and run the risk of over-engineering their solutions. 1.1.2 Barriers to Rapid Development In order to design a language that facilitates speedy development, we must consider the things that slow down developers; namely: Language complexity. The more complex a language is, the steeper its learning curve will be. Contrary to the popular opinion that a language’s complexity is a function of its number of features and constructs, it’s primarily a product of giving the programmer too many different ways of doing the same thing. Too much choice leaves the programmer in a situation where they have to constantly think about choosing the best approach, and this will slow down development. JavaGram avoids this pitfall by not attempting to be a totally versatile and general purpose programming language. For example, JavaGram offers only three container types – lists, vectors, and maps – each of which has only one implementation. So when the programmer sees the need for a container, no time is lost on deciding which one to use. Plumbing overhead. More than 50% of code in a typical application tends to be of ‘plumbing’ nature. This is code that doesn’t implement business functionality but performs essential housekeeping. For example, data entered by the user into a screen typically needs to be extracted, validated, stored in a suitable data structure defined by the programmer, transmitted from the client to the server using a message defined by the programmer, unwrapped on the server-side, processed by interacting with a database, and so on. Typically, such data goes through a number of transformations where it’s changed from one format to another, and yet another, until it finally can be acted upon. JavaGram greatly reduces the need for plumbing code by performing such tasks behind the scene without the programmer having to worry about them. This saves the programmer much valuable time, enabling them to focus on actual business functionality. Interestingly, because a lot of unnecessary transformation is avoided, the end-to-end process executes faster, thus also saving computational resources. Procedural clutter. Most languages (including Java) require the programmer to think procedurally. While this works well in some situations (e.g., implementing transactions), it hinders tasks that are better suited to a declarative style. GUI programming is one such task. Because GUIs are visual and typically hierarchical, a declarative notation can be far more expressive and convenient for implementing 14 JavaGram Agile Development them. JavaGram adopts this style by allowing the programmer to define GUIs using a markup notation. This not only results in a lot less code, it also greatly enhances the readability of the code, to the extent that the GUI can be easily visualized by simply observing the code. Static composition. Most languages take a static view of the components that comprise a program and require all the referred components to be in place before the program can be executed. Because of the incremental nature of agile, this is a major inconvenience which forces the programmer to define placeholders and stubs to work around the issue. JavaGram overcomes this problem by allowing the programmer to dynamically load scripts. For example, if the action of a push button is defined by a dynamically-loaded script, the programmer will be able to run the program even if this script is not defined or is incomplete and has compilation errors. These errors will not surface unless the user actually pushes the button. Complex object model. In object-oriented programming, business objects can be a source of substantial complexity. Most of this complexity is in the underlying implementation of the business objects (e.g., persistence). However, from an agile development viewpoint, it’s not the implementation that’s important but the business functionality offered by the object. JavaGram reduces this complexity by offering a straightforward implementation model based on a generic object which hides much of the underlying complexity. By sub-classing the generic business object class and using declarative schemas, the programmer can do away with time-consuming and error-prone activities such as implementing object persistence. 1.1.3 Barriers to Rapid Testing The key to an effective and fast testing cycle is the ability to turnaround defects quickly. The usual pattern in system testing is that a tester runs a number of test cases and records observed defects. Some of these defects become showstoppers, preventing the tester from progressing any further. At this point the tester will need to wait until enough defects have been fixed and a new release produced so that testing can continue. The main barrier to quickly delivering a test release is a slow build process. JavaGram addresses this by eliminating the need to produce an actual build. A release can simply consist of the correct versions of the scripts that comprise the application, extracted from a version control system and placed on a test application server. Compilation is not required, as the application server will incrementally compile the scripts on demand. This means that even a large application can be released in minutes. Even better, for the majority of fixes, a complete release is not required. Developers can choose to release only the few affected scripts that fix the outstanding defects. JavaGram even allows an application to be hot fixed without restarting the server. Experience has shown that critical defects can be turned around shortly after they’re raised by testers, thus enabling testers to continue their work with minimal disruption. Similar benefits are gained in production support. Introduction 15 1.1.4 Barriers to Rapid Evolution A live application is best regarded as an evolving entity. The more the application is used, the more users will demand from it – active use leads to rapid evolution. When a new application is designed, it’s almost impossible to foresee all the future demands that will be placed on it. At any point in time, it’s only practical to consider features that are likely to be demanded in the near to medium term. Future (and especially unforeseen) demand is likely to stretch the application design to an extent that couldn’t have been predicted. Long term therefore, the malleability of the design becomes a critical issue. A design that’s not accommodative of change will eventually break and disrupt the evolution process. So how can a design be made malleable? There are two competing views on this subject. The first view argues that considerable flexibility should be built into the design from the beginning to make it future proof. This is intellectually appealing but in practice rarely successful. The problem with this approach is that devising extensive design flexibility can be very costly and invariably leads to design complexity, both of which go against the agile principles. Experience indicates that, in the long run, very few of such expensive flexibilities actually get used and the rest become a liability. The second view argues for simplicity. By keeping a design as simple as possible, we not only shorten the construction phase, we also make it easier for future developers to understand its make-up. The latter is far more valuable than some appreciate. Most practitioners would testify that the biggest hurdle in tweaking a design is to first understand it. Once this is achieved, only a bit of developer creativity is required to work out a way of accommodating something new. JavaGram adheres to the simplicity view. The language helps the developer to express things succinctly and with minimum clutter. Reduced plumbing code means that the vast majority of code actually represents business functionality. This facilitates understanding and makes it easier to work out how to best apply a change. 1.2 Salient Features JavaGram builds on the strengths of Java and closely follows the Java syntax and semantics, including strong type checking. This should make it very easy for a Java programmer to become proficient in JavaGram. This section summarizes those features of JavaGram that characterize it as an agile programming language. While reading this summary, don’t worry if something is not entirely clear to you or sounds too technical. The intent here is to give a flavor of JavaGram in a limited space. Subsequent chapters will explore these topics in greater depth and at a gentler pace. 16 JavaGram Agile Development 1.2.1 Server Centricity Historically, programming languages have been designed with the assumption that application code will need to be installed in its target environment before it can be executed. The World Wide Web has been the most significant departure from this paradigm. Under this model, a web application is not pre-installed on the client side, but is incrementally downloaded in response to actions taken by the user. However, this dynamically-downloaded code (HTML, which may also include JavaScript and the like) is primarily concerned with presentation, and is generated by the actual application code residing on the web server, hence the term thin client. Web page is delivered to client Web Browser Client World-wide-web Web Server Client requests web page http://www.acme.com/products The design of JavaGram was inspired by this model, with a key difference: rather than the server generating presentation code for the client, the server delivers executable code to the client. Like the web model, the client initially contains no code at all, but rather is a shell capable of receiving code and ‘interpreting’ it. In the web model, the client is a web browser; in the JavaGram model, the client is a compact runtime environment called JAG. Introduction 17 Script is delivered to client JAG loads and executes script JAG Client World-wide-web App Server Client attempts to Access a script www.acme.com:443/search.jag A JavaGram client boots itself against a JavaGram server using an initial address (similar to a URL in a web browser), which identifies the server and the initial script. In response to this, the server creates a session thread to handle all subsequent communication with the client. The requested script is then sent to the client, which the latter loads and executes. During the course of execution, the script may refer to other scripts, which are sourced from the server in a similar manner. Therefore, the client code base is built incrementally according to user actions. Like the thin client model, the server-centric model of JavaGram has a number of advantages over the traditional fat client model; namely: There is no need to install an application at the client end. An application can start quickly because the loading process is incremental. Release management is much easier, as a new version of the application doesn’t need to be installed on every client, just on the server(s). The latter is much easier because servers are centralized and there is a lot less of them than clients – which are not only numerous but also often out of reach. The JavaGram model also retains some of the advantages of the traditional fat client model; namely: Unlike the thin client model which is stateless, the JavaGram model is stateful. Because each client is allocated a dedicated session that lasts for the duration of the connection, the session accurately reflects the server-side state of the client. This eliminates the burden of additional programming normally required for thin clients to keep track of state information. JavaGram supports both synchronous and asynchronous requests, as opposed to the asynchronous-only thin client model. The programmer can delegate some of the application’s processing to the client end (e.g., report generation, complex calculations) within the same code base. This 18 JavaGram Agile Development provides more scope for load balancing and making sure that server(s) don’t become a bottleneck. For lack of a better term and to distinguish the JavaGram model from the fat client and thin client models, we’ll subsequently refer to it as the hybrid client model. 1.2.2 Browser-based and Native Desktop Clients A unique feature of JavaGram is that the same application code base can support browser-based as well as native desktop clients. JavaGram achieves this by offering two versions of its runtime environment: The Flash version of the JavaGram runtime (JAG.swf) is written in ActionScript 3 (AS3) and runs within the Flash Player engine. When a browser-based client uses this runtime, it’s automatically downloaded from the web server. The Java version of the JavaGram runtime (JAG.jar) is utilized by native desktop clients. The same runtime is also used by JavaGram servers as well as standalone applications. Both runtimes are very compact and therefore carry little download overheads. 1.2.3 Static and Dynamic Loading JavaGram support static as well as dynamic loading of scripts. Static loading is suitable for specifying cross-script dependencies. Dynamic loading is suitable for situations where we don’t want a script to be loaded until it’s activated by a trigger (e.g., the user performing an action). In both cases, the requested script may be sourced locally or from an application server. The JavaGram application server uses an implicit form of dynamic loading to serve remote calls received from a client. When a remote call is received by a server, the underlying message includes the path of the script that contains the definition of the target class. The server uses this path to dynamically load the script if it’s not already loaded. This ensures that the loading of scripts on the server-side is automatic, demanddriven, and hence not a burden on the programmer. 1.2.4 Code Caching To minimize client-server traffic, JavaGram employs a comprehensive caching mechanism that maintains an active cache on both the client and server ends. On the client side, the cache is somewhat similar to the local cache of a web browser, but is more deterministic. Whereas a web browser refreshes the pages in its cache based on their age, JavaGram requires an exact timestamp match. This is necessary in order to ensure the consistency of the versions of scripts that make up an application. Introduction 19 The server-side cache serves a different purpose. Because a JavaGram script can contain code intended for clients and/or servers, each script is compiled to produce two variants – one for the client-side (from which server-related information is removed) and one for the server-side (from which client-related information is removed). These binary files are deposited into the server-side cache. This cache is automatically updated when a script is modified. 1.2.5 Built-in Types JavaGram provides the following built-in types. Atomic Types: boolean (similar to Java boolean) char (similar to Java char) int (similar to Java long) real (similar to Java double) string (similar to java.lang.String). symbol (like string except that multiple instances having the same representation are stored only once) date (date and time) stream (mechanism for performing IO with respect to a file, buffer, or client- server communication channel) Composite Types: vector (contiguous sequence of values, with random access) list (sequence of values, without random access) map (key-value pairs, with random access) object (opaque instances of user-defined types, similar to java.lang.Object) Pseudo Types: vague (can represent any type) native (Java values with no equivalent type in JavaGram) void (absence of a value) Values for atomic and composite types can be created literally (except for stream) or programmatically. 1.2.6 Object Orientation The OO features of JavaGram are very similar to Java, with the following notable exceptions. 20 JavaGram Agile Development Support for multiple inheritance. Support for remote methods and classes, both of which are managed transparently to the programmer. Support for GUI members (behave like class fields and allow you to define user interface components declaratively and hierarchically). Support for text members (behave like methods and allow you to do advanced text processing). Support for SQL members (behave like methods and allow you to isolate your SQL code). Support for singleton classes. Support for object literals (class instances that are created at load time rather than runtime). Ability to limit the visibility of a class method to client or server side. Ability to specify default argument values for methods. 1.2.7 Multiple Inheritance Multiple inheritance (MI) is a powerful design tool that, when used judiciously, can simplify a design and reduce development effort. Unfortunately, MI has attracted plenty of bad publicity due to complex realizations (e.g., C++) that programmers have struggled with. JavaGram attempts to remedy this using mutual classes: A derived class can have multiple base classes, provided at most one of them is nonmutual. All the base classes of a mutual class (if any) must also be mutual. Mutual classes that are inherited more than once in a class hierarchy (as in the ‘dreaded diamond’ problem) are treated as if they are inherited once. Therefore, an instance of the derived class contains only one instance of the mutual class’s fields. In this sense, mutual classes behave like virtual base classes in C++. 1.2.8 Automatic Remoting Remote methods represent one of the most powerful features of JavaGram. They make the task of writing client-server applications exceptionally easy by removing the burden of having to deal with data communication, synchronization, hand-shaking, error handling, and so on. As a result, invoking a remote method on a server becomes as easy as invoking a local method. Hiding all this complexity has the added benefit of allowing the programmer to easily use the same code in different deployment models (standalone versus distributed). JavaGram also allows you to define an entire class as remote so that all its actual processing is handled on the server-side. When a client obtains a reference to a remote Introduction 21 object, it receives a proxy object instead. Any operation performed on the proxy is transparently applied to the remote object. In practice, a client has no way of knowing whether it’s dealing with a remote method/class or a local one. This ensures that code designed to be deployed as clientserver will also work when run as standalone. The obvious benefit of this is a simplified testing process – developers can develop and test standalone and later switch to clientserver when the code is more fully developed. JavaGram’s exception handling works seamlessly across the client-server boundary. For example, an exception raised (on the server-side) by a remote method is delivered to the caller (on the client-side) as if it were a locally raised exception. 1.2.9 Asynchronous Method Invocation Asynchronous method invocation allows you to call a (local or remote) method such that you don’t have to wait for it to complete. Execution proceeds to the next statement as soon as the call is made. When the call eventually completes, a callback is invoked to complete the processing. If an error callback is also specified and the method throws an exception then the error callback is invoked instead. 1.2.10 Declarative GUIs JavaGram offers a completely different style of GUI programming to Java’s Swing. Whereas GUI programming in Swing is procedural, JavaGram allows you to define a GUI declaratively. This has a number of advantages: you write a lot less code, the code is much more readable, and the code readily portrays the hierarchical structure of the GUI. As a result, creating sophisticated GUIs in JavaGram is much easier than in Java. GUI members are defined using a markup notation. This markup notation can be extended by the programmer to devise new and novel components. 1.2.11 Parameterized Text Programs often have to do some level of text manipulation. In most programming languages, this is done through string concatenation (e.g., using the + operator or the StringBuilder class of Java). The end result is rather messy and difficult to visualize due to the procedural nature of the code. JavaGram offers two facilities to simplify text handling. The first is called delayed strings and is useful for simple text parameterization tasks. For more elaborate tasks, JavaGram provides text class members. A text member is like a method and can be used to handle parameterized text. Like GUI members, text members are defined using a markup notation which is extensible. In fact, JavaGram provides a number of such extensions for SQL handling. 22 JavaGram Agile Development 1.2.12 Database Interaction In JavaGram, interaction with databases is facilitated by the sql pseudo class. Additionally, a number of parameterized text constructs are provided to make SQL formation straightforward and consistent with the declarative style of JavaGram. One of the readability benefits of the JavaGram style of SQL programming is that all SQL commands are localised to <text.sql...> markups and are hence easily identifiable. This is superior to the traditional style where SQL is freely sprinkled throughout the code. 1.2.13 Serialization and Parsing A useful feature of JavaGram is that any value (other than stream and native values, but including class instances) can be serialized to clear text, as well as parsed without any extra programming effort. The resulting benefits are: Complex data structures (such as meta data) can be pre-created in code. This is convenient as well as self-documenting. Programs can be debugged more easily. Composite data can be stored in a single database column and retrieved easily, thus facilitating much simpler data models. The data exchanged between client and server ends can be easily viewed in a readable textual format. The ability to parse data or code ‘on the fly’ can be valuable in some programming situations. It’s especially useful in agile programming, as it can substantially reduce coding and maintenance effort. 1.2.14 Business Objects JavaGram provides a pseudo class (bom) and a library class (Object) that together provide a convenient framework for working with business objects that require persistence. By sub-classing Object, you can quickly develop a persistent business object without writing any SQL. The relationship between the object’s structure and the underlying database table is specified by the programmer as meta data, which enables Object to work out how to map the data. 1.2.15 Java Interoperation JavaGram provides a simple facility for interoperating with its underlying implementation language (Java). The sys.java() method uses reflection to allow any Java class or method be accessed, and automatically maps data between JavaGram types and Java types. This method is rarely used, but is a useful last resort when the programmer needs to do something that JavaGram doesn’t directly support. Introduction 23 1.3 Implementation JavaGram has been developed in pure Java (and AS3) and is therefore platform independent. The implementation is comprised of four parts: JavaGram runtime environment (JAG) in two versions (Java and AS3). JavaGram Development Environment (JADE) JavaGram Server Monitor (JSM) JavaGram standard library scripts The AS3 (Flash) version of JAG comes in three flavor for use in a web browser, Adobe AIR, or Android. The overall architecture of JavaGram is illustrated by the following diagram, and the components described below. 1.3.1 JavaGram Runtime Environment JAG is packaged as a single JAR file (JAG.jar). It’s very compact (currently around 1.78 MB) and provides a complete environment for deploying and running JavaGram applications, including: Parser, analyzer, and evaluator for interpreting JavaGram code Compiler for converting JavaGram code into binary format JavaGram Transfer Protocol (JTP) for client-server messaging (over socket, HTTP, or HTTPS) 24 JavaGram Agile Development Application server (for deploying JavaGram servers) Proxy server (for failover and load balancing application servers) Every JavaGram client or server is deployed using JAG. A server deployment also requires a configuration file which specifies the server settings (for security, data compression, database connection pools, session management, etc.), as well as any other JAR files used (e.g., JDBC drivers, FOP library, etc.) All JavaGram servers are generic and exceptionally easy to setup and deploy (taking only a few minutes). The same application server instance may serve many different applications. JavaGram code is deployed into an application server by simply dropping it into a nominated directory. One of JavaGram’s important promises is that a JAG client (Java version) needs to be installed only once (Flash version is automatically downloaded by the browser). The process is like this. The user downloads and runs a small installation program (which installs JAG.jar, a small Java keystore, and a shortcut to an application’s URL) on the user’s machine. When the user runs the shortcut, the application is automatically booted (scripts are demand-downloaded as necessary and executed). Application maintenance is totally transparent to the user: When a new version of the application is released to a server (i.e., revised scripts), these revisions automatically find their way to the client installations. If a new version of JAG.jar is released to a server, this can be automatically propagated to all clients that depend on the changes in this JAR, causing the new JAR to be automatically downloaded by the affected clients, and replacing the old JAR. Furthermore, application code changes that leave class schemas unchanged (i.e., only affecting the implementation of methods) can be deployed without restarting a server or any client. This allows emergency hot fixes to servers without disruption to users. JavaGram application servers are highly scalable. Multiple servers can be deployed in either a failover or load balanced configuration. Where load balancing is used, traffic is routed via one or more proxy servers, which in turn match clients against server instances based on their load. 1.3.2 Compilation The JavaGram compilation process is straightforward. A script is parsed, analyzed, and converted to an equivalent byte code. When a server internally compiles a script, it produces two compiled versions, one for client-end and one for server-end. Either version excludes information that’s not relevant to its intended target environment. Scripts can also be explicitly compiled, in which case a single version is produced that’s equivalent to the source. Explicit compilation is suitable for cases where an application is to be deployed in binary rather than source format (e.g., for intellectual property reasons). Introduction 25 The JavaGram parser can parse scripts in source or binary format. The latter has the advantage of being more secure and more efficient – the parser has to do a lot less work. The binary codec used by the compiler is version controlled, so if a client tries to load a script that’s been encoded using an outdated codec, this is detected, causing a refresh of that script from the server. 1.3.3 JavaGram IDE The JavaGram IDE (packaged as JADE.jar) provides a productive visual environment for developing, debugging, and running JavaGram applications. Key components include: Projector for setting up and managing the source code for a project Syntax-directed editor, which automatically color-codes your code Code Insight for visual navigation of code elements and auto completion Runner for configuring and running applications as standalone, client, or server Debugger for setting up breakpoints, inspecting the runtime stack, and viewing the values of variables Auto Analyzer which automatically parses and analyzes your project’s code as you type, works out dependencies, and visually highlights errors For a detailed description of JADE, please refer to Chapter 14. 1.3.4 JavaGram Server Monitor The Server Monitor is a companion tool for monitoring deployed servers. This tool is implemented entirely in JavaGram and its source code is included in the JavaGram release. Its functionality includes: Viewing the server sessions and managing them Viewing server memory usage pattern Viewing the server log, where diagnostic information is recorded Uploading and downloading of files for investigation and patching purposes Resetting the server and/or its database connection pools Temporarily suspending user access for maintenance purposes For a detailed description of JSM, please refer to Chapter 7. 1.3.5 JavaGram Standard Library Scripts JavaGram comes with a standard library in source format. The scripts in this library provide you with a set of reusable classes for a variety of tasks, including GUI development, server deployment, SQL handling, business object management, data 26 JavaGram Agile Development export, etc. Many of the examples presented in this book utilize these scripts and illustrate their use. The obvious benefit of using the library is productivity. The standard library scripts are detailed in Chapter 13. 1.4 Download and Installation To setup a working JavaGram environment so that you can do development and try out the examples presented in this book, you need to download and install the following components. Download the latest JRE or JDK from java.sun.com and install it, unless this is preinstalled on your computer. If you intend to use a browser client, download and install the Flash Player from www.adobe.com, unless this is pre-installed on your computer. Similarly, if you intend to use an Adobe AIR client, also download and install Adobe AIR. Download the latest JavaGram release from www.pragsoft.com and install it. This installation is required on any computer where you intend to run a JavaGram program (be it standalone, client, or server). When installing on a computer intended for development work, you should also choose the option for installing JADE. Download the latest transactional MySql release from www.mysql.com and install it. All the SQL examples in this book have been developed with MySql, but you can use any other database engine that supports JDBC. However, if you use another JDBCcompliant product (e.g., Oracle, SqlServer, Firebird), you may need to alter the syntax of some of the SQL examples to conform to that product. Introduction 27 2 Fundamentals JavaGram programs consist of scripts. Each script is a text file whose name ends in .jag (e.g., Test.jag). Compiled script names end in .jax (e.g., Test.jax). Each script defines one or more classes and may refer to classes in other scripts. Such dependencies are specified through the load facility, which enables one script to load others during its own loading or, dynamically, during execution. 2.1 Example The minimal syntax of a script is exemplified below. HelloWorld.jag <jag domain="doc/code/chap2"> class HelloWorld { public static void main () { sys.println("Hello World!"); } } </jag> You can run it from a DOS command line like this: java -cp JAG.jar jag.run.Env C:/JavaGram/doc/code/chap2/HelloWorld.jag It will produce the following output. Hello World! Let’s look at the contents of this script and their meaning. The code for a JavaGram script is always enclosed by a <jag></jag> pair, where <jag> marks the beginning of the script and </jag> marks the end of it. As in HTML and XML, a JavaGram markup can have properties. For example, the <jag> markup here has a domain property. This is somewhat similar to the package construct in Java, in that it defines a namespace for the script. As a result, the class defined in this script has a short name (HelloWorld) and a long name (doc\code\chap2\HelloWorld). The latter is called a qualified class name. Ordinarily, we use the short name of a class, but if two or more classes defined in different domains have the same name, the qualified class name can be used to avoid ambiguity. You may have noticed that we’ve used forward slashes in the domain name and backward slashes in the qualified name (Java uses periods for both). The forward slash notation is actually a convenience. A domain can also be specified using backslashes. For example: 28 JavaGram Agile Development <jag domain="doc\\code\\chap2"> Because a backslash must be escaped inside a string, this notation is a little inconvenient, so the forward slash convention is usually used instead. When using a qualified class name in code however, only the backslash notation is allowed, as forward slashes are treated as the division operator. The rest of the script defines a simple class called HelloWorld. A class represents a userdefined type, and is a way of packaging data (fields) and behavior (methods) so that it can be conveniently used elsewhere in the program. A JavaGram program is simply a collection of classes that refer to one another. It’s worth noting that, unlike Java, the name of a JavaGram class and its script file need not match. You can also put multiple classes in the same script file. For each class, JavaGram internally records the script that contains it and uses this information to locate the right script when needed. The definition of a JavaGram class consists of the keyword class, followed by the class name, followed by the class body. The latter is enclosed by a pair of braces: class HelloWorld { ... } A class name must be a valid identifier (a sequence of letters, digits, or underscores, but not starting with a digit). The recommended convention in JavaGram is to capitalize the first letter of every word of a class name. The HelloWorld class contains just one method called main: public static void main () { sys.println("Hello World!"); } Like a class name, a method name must be a valid identifier. The recommended convention is to write method names in camel case (capitalize the first letter of every word except for the first word). A method is a recipe for computation – a sequence of instructions to do something. The keywords public and static appearing at the beginning of the method are called qualifiers. A qualifier imposes a certain rule on the thing that it qualifies. For example, the public qualifier gives main public visibility, so that it can be accessed outside the class; and the static qualifier makes the method accessible even without having class instances (more on this later). Fundamentals 29 Every method must have a return type (this appears before the method name) which specifies the type of value the method will return when it’s called. The return type void is a pseudo type, implying the absence of a value. In other words, main does not return anything. The empty pair of parentheses appearing after the method name means that main has no parameters. The qualifiers, return type, name, and parameters of a method are collectively called its signature, so main has the following signature: public static void main () Finally, the body of the method appears last and (like a class body) is enclosed by a pair of braces. Generally, the body of a method consists of statements, where each statement specifies a specific instruction or computation. Being a very simple method, main has just one statement: sys.println("Hello World!"); The effect of this statement is to write the string "Hello World!" to standard output. (Standard output is a predefined stream, normally tied to the screen in which you run your program, causing output to be displayed in that screen.) This statement is itself a call to a method (println) of another class (sys). The latter is a pseudo class built into JAG. The term ‘pseudo class’ applies to predefined classes that behave like normal classes but can’t be instantiated or extended. In JavaGram, every statement must be terminated by a semicolon. A semicolon on itself is also considered a statement (an empty statement). As a rule, if a class has a main method that is public static void and with no parameters (as HelloWorld does) then this method is treated specially – when such a class is loaded, this method is automatically called. This is usually the starting point of execution for a program. 2.2 Loading Scripts Before it can be executed, a script must be loaded into JAG. There are three ways for loading a script: As a command line argument to JAG.jar. Use this method to run the initial script of a JavaGram program. Using the <load> markup inside a script. Use this method to document the dependencies of a script on other scripts. This is called static loading – when the enclosing script is loaded, it causes the enclosed scripts to be also loaded. Using the sys.load() function. Use this method for dynamic loading of a script during program execution. The script is loaded only when the sys.load() function is executed. This is useful when you want to delay the loading of a script until it’s 30 JavaGram Agile Development actually needed; for example, in response to the user pressing a button that initiates a calculation, which is to be performed by the nominated script. When you load a script using any of these methods, JavaGram processes the script in three successive stages, as illustrated below. During parsing, the script is checked for syntactic correctness. If syntactically correct, the script is analyzed for semantic validity. Finally, if both these stages succeed, the script is evaluated by evaluating its classes in the order in which they appear. The evaluation of a class causes its static fields and static blocks to be evaluated. Also, if the class has a public static void main method, it may be evaluated, depending on the load configuration (this is always the case when loading from the command line). Here is an example of how to load a script using the <load> markup. CarTest.jag <jag domain="doc/code/chap2"> <load> "doc/code/chap2/Car" </load> class CarTest { public static void main () { Car car = new Car("Toyota", "Camry", 2007); sys.println(car.format()); } } </jag> It refers to another script which defines the class Car. Car.jag <jag domain="doc/code/chap2"> class Car { protected string make; protected string model; protected int year; public Car (string make, string model, int year) { this.make = make; this.model = model; this.year = year; } public string format () { return make + " " + model + " " + year; } Fundamentals 31 } </jag> You can run this program using the command line: java -cp JAG.jar jag.run.Env -root C:/JavaGram doc/code/chap2/CarTest.jag It produces the following output. Toyota Camry 2007 Note how we’ve used the –root option to specify a root directory for scripts. By default, scripts appearing inside the <load> markup are relative to this directory. So doc/code/chap2/Car.jag resolves to C:/JavaGram/doc/code/chap2/Car.jag. You can specify multiple file paths within a <load> markup, each of which must be a string. The strings must be separated by whitespace. The recommended convention is to put each file path on a separate line. Let’s discuss the contents of these two scripts and their meaning. Car is a simple class that has three fields (make, model, year) and two methods (Car, format). Fields are used to hold data. As with methods, fields may have qualifiers – all three fields here are declared to be protected. This qualifiers means that these fields are not visible outside the class; they’re only visible to the class members and members of any classes derived from Car (derived classes are described later). Every field must have a type which specifies the kind of data that the field can hold. Both make and model are specified to be of type string (arbitrary sequence of characters enclosed in double quotes) and year is specified to be of type int (integer number). A field name must be a valid identifier. Like methods, the recommended convention is to write field names in camel case. The first method of the Car class has the same name as the class itself. This method is called a constructor. You never specify a return type for a constructor because the return type is implicit and is the class itself. Constructors are used to create instances of a class. The distinction between a class and its instances is very important and fundamental to understanding object-oriented programming. A class represents a potentially infinite number of possibilities. For example, the Car class represents any car. However a specific car (e.g., a 2009 Nissan Patrol) is represented by an instance of the Car class. Therefore, a class is an abstraction (a concept) whereas its instances are concrete things (also called objects) – hence the term ‘object-oriented programming’. When a class instance (object) is created, a piece of memory is allocated to represent the object. The data for the class fields is actually stored in this piece of memory. Let’s look at the definition of the Car constructor: 32 JavaGram Agile Development public Car (string make, string model, int year) { this.make = make; this.model = model; this.year = year; } This method takes three parameters. These appear after the method name, within a pair of parentheses, and separated by commas. Each parameter consists of its type and an identifier. The job of this constructor is to assign a value to each of the three class fields using a corresponding parameter. The assignments are done using the = operator, which copies the value represented by its right operand to the location represented by its left operand (this is called an lvalue – something that can appear on the left-side of an assignment). Because the parameters have the same name as the class fields, we’ve used the this.id notation to refer to the fields. So, for example, the first assignment this.make = make copies the value of the make parameter to the make field. this is a reserved word that can only be used in non-static methods – it refers to the object on which the method is invoked. As an aside, the initial value of class fields (that haven’t been explicitly initialized at the time of definition) is somewhat different to Java. In Java, an int field, for example, has an initial implicit value of 0, and a String field has an initial value of null. In JavaGram, every field, regardless of its type, has an initial value of null. The second method (format) returns a string representation of a car. public string format () { return make + " " + model + " " + year; } The string is produced using the string concatenation operator +. This operator can take operands of various types (e.g., strings, numbers) and joins together their string equivalent. The return keyword causes the resulting string to be returned as the overall value of the method. The main method of the CarTest class illustrates how the Car class may be used. public static void main () { Car car = new Car("Toyota", "Camry", 2007); sys.println(car.format()); } The first statement in this method uses the new operator to instantiate the Car class to create an object. This operator must always be followed by a call to a constructor method. Because the constructor for Car has three parameters, a call to it must provide three arguments. The types of these arguments must match the types of the corresponding parameters. The newly created object is assigned to a local variable (car). Fundamentals 33 The second statement invokes the format method on the object represented by car. The resulting string is then passed to sys.println to write it to standard output. 2.3 Expressions and Statements The body of a method always consists of zero or more statements. These statements represent the computational steps of the method or, in other words, its implementation. Statements are composed hierarchically – simpler statements may be combined to produce more complex ones. When a statement is executed, it produces a side-effect, such as altering the value of a variable, creating an object, or reading/writing data from/to a stream. Statements utilize another building block called expressions. Expressions also represent computations but they’re different from statements in that they do not produce side effects; they simply produce values. For example, 10 + 20 is an expression (it simply adds two numbers to produce a new value) whereas, int n = 10 + 20; is a statement because it stores the result of adding two numbers (an expression) in a variable, thus altering the value of the variable (a side effect). The reason this distinction is important is that the JavaGram syntax expects you to use expressions in some places (e.g., the logical condition of an ‘if’ statement) and statements in other places (e.g., the ‘then’ part of an ‘if’ statement), so you need to know which one to use where. To make matters more confusing, some constructs can be used in either capacity. For example, a call to a non-void method can be used either as an expression or as a statement. So, in this case at least, the earlier observation that expressions don’t produce side effects is not entirely true. It would be more accurate to say that most expressions don’t cause any side effects. As you read through the rest of this book, if you want to learn more about any topic, look it up in the reference chapters. For example, to find out more about the methods of the sys pseudo class, refer to their specification in Chapter 10. You might also find Chapter 15 useful – it describes the JavaGram syntax. The rest of this chapter introduces the commonly used expressions and statements of JavaGram using some simple examples that illustrate their purpose. 2.4 Types The notion of type is fundamental to a proper understanding of programming. Simplistically, a type represents a set of possible values. For example, the int type covers 34 JavaGram Agile Development all integers. The relationship between a type (something abstract) and a value (something concrete) is the same as the relationship between a class and its instances. JavaGram is a strongly typed language, implying that: Whenever you create a variable, you must specify its type. JavaGram will not allow you to store a value in the variable that doesn’t belong to its type. When you invoke a method, the type of each argument you pass to the method must match the type of the corresponding parameter. JavaGram comes with a number of predefined types (see Chapter 10 for details). These represent types that you need in almost every program, so they’ve been built into the language to ensure an efficient runtime implementation. A JavaGram class is also considered to be a type and is called a user-defined type. In fact, classes are the mechanism through which the JavaGram type system is extended. In this sense, when you write a program, what you’re really doing is expanding the type system. There is also a set of standard library classes (see Chapter 13) that provide various useful types for use in your projects. These not only save you time but also serve as ‘best practice’ because they encourage the use of proven design patterns. If you’re new to object-oriented programming then the concept of type could take some time to get used to, but this investment is vital if you’re to take full advantage of the power of object-oriented programming. We’ll cover this topic in more detail in the next chapter. 2.5 Control Flow Any non-trivial program has to deal with possibilities – if a certain condition holds then do this, otherwise do something else. This type of conditional behavior is realized by statements that allow the program to take different execution paths based on logical conditions. This concept is called control flow (or flow of control). The simplest control flow statement is the if statement. Let’s look at a simple example that illustrates its use. Consider the following class which represents bank accounts. <jag domain="doc/code/chap2"> class BankAcc { real balance; static final real CREDIT_RATE = 0.045; static final real DEBIT_RATE = 0.065; public BankAcc (real balance) { this.balance = balance; } public void applyInterest () { if (balance >= 0) balance += balance * CREDIT_RATE; Fundamentals 35 else balance += balance * DEBIT_RATE; } public static void main () { BankAcc acc1 = new BankAcc(500); BankAcc acc2 = new BankAcc(-200); acc1.applyInterest(); acc2.applyInterest(); sys.println("Acc1 balance is ", acc1.balance); sys.println("Acc2 balance is ", acc2.balance); } } </jag> The applyInterest() method uses an if statement to decide which interest rate to use, based on the account balance. For an account in credit it uses CREDIT_RATE and for one in debit, it uses DEBIT_RATE. The condition appearing after the if keyword must be a boolean condition (something that evaluates to true or false). When it evaluates to true, the statement after it is executed; otherwise, the statement after else is executed (the else part is optional). If you run this program, it will produce the following output. Acc1 balance is 522.5 Acc2 balance is -213.0 Here are a few more observations about this program: The balance field has no explicit visibility qualifier, therefore it’s deemed to have domain visibility – it’s accessible by all classes defined in the same domain as this class. CREDIT_RATE and DEBIT_RATE are both defined to be static and final. This is how you define constants in JavaGram. A final field must be initialized at the time of its definition; its value cannot be subsequently altered. A static field is shared between all instances of its parent class. So each time you create an instance of BankAcc, memory is allocated for the balance field but not for CREDIT_RATE and DEBIT_RATE. The += operator adds its left and right operands and stores the outcome in the left operand (which must be an lvalue). sys.println() accepts a variable number of arguments (zero or more), and sends the string equivalent of each argument to standard output. 36 The text starting with // is called a comment. JavaGram ignores anything appearing after // until the end of the line. Comments are useful for documenting additional information about a program. There is another style of writing comments that can span multiple lines: anything appearing between /* and */ is treated as a comment. JavaGram Agile Development There is also an expression form (called conditional expression) that can be used to rewrite certain if-else statements more succinctly. For example, we can rewrite applyInterest() in the following equivalent style. public void applyInterest () { balance += balance * (balance >= 0 ? CREDIT_RATE : DEBIT_RATE); } In a conditional expression, the condition is always followed by a question mark, followed by the ‘then’ expression, followed by a colon, followed by the ‘else’ expression. The overall value of the expression is the ‘then’ expression if the condition evaluates to true, and the ‘else’ expression otherwise. Another form of conditional statement is when we want to compare a value against a number of different possibilities, and take action accordingly. This is facilitated by the switch statement. To show its use, consider the following class. <jag domain="doc/code/chap2"> class Calculator { static real calc (char op, real val1, real val2) { switch (op) { case '+': return val1 + val2; case '-': return val1 - val2; case '*': return val1 * val2; case '/': return val1 / val2; default: sys.println("Unknown operator: ", op); break; } return null; } static void main () { sys.println(calc('+', 10, 20)); sys.println(calc('*', 10, 2.4)); } } </jag> The calc() method takes three parameters: a character that represents an arithmetic operator, and two real numbers. It uses a switch statement to determine what to do based on the value of op. What appears after the switch keyword (within parentheses) is called a selector. Each possibility is specified by a case, which consists of the keyword case, followed by a literal (of the same type as the selector), followed by a colon, followed by zero or more statements. The selector is compared against the case values, and the statements for the matching case are executed. The final case (default) is optional and, when present, acts as a ‘catch all’ – if none of the earlier cases match the selector then this one is exercised instead. Fundamentals 37 Running this program produces the following output. 30.0 24.0 Some further observations about this program: Because calc() is a static method, it can be invoked without having an instance of the class, as illustrated in main(). A character literal is always delimited by a pair of single quotes. When a matching case of a switch is executed, execution continues to the statements for the next case and so on, unless the switch is exited using a break statement, or the whole method is exited using a return statement. The break statement at the end of the default case is evidently redundant, but is considered good coding practice. This program doesn’t cater for the possibility of division by zero. A call such as calc('/', 10, 0) will result in the following. …/code/chap2/Calculator.jag (line 10 col 25-26) ERROR: division by zero 10 case '/': return val1 / val2; ^ We can cater for this case using an if statement: static real calc2 (char op, real val1, real val2) { switch (op) { case '+': return val1 + val2; case '-': return val1 - val2; case '*': return val1 * val2; case '/': if (val2 == 0) break; return val1 / val2; default: sys.println("Unknown operator: ", op); break; } return null; } It’s worth noting that the type of values you can use for a switch selector (and the literals for the cases) must be integer, character, or symbol. We’ve already seen examples of numbers, characters, and string literals. A symbol literal consists of the $ character followed by an identifier. For example: $name, $address, $id. Here is a revised version of the Calculator class using symbols instead of characters. 38 JavaGram Agile Development static real calc3 (symbol op, real val1, real val2) { switch (op) { case $add: return val1 + val2; case $subtract: return val1 - val2; case $multiply: return val1 * val2; case $divide: if (val2 == 0) break; return val1 / val2; default: sys.println("Unknown operator: ", op); break; } return null; } In many ways, symbols can be thought of as pseudo identifiers, which makes them useful for storing identifiers in variables, objects, etc. For such uses, they’re superior to strings – they consume less memory and they can be more efficiently compared. For example, if a string (such as "name") appears 20 times in a program, JavaGram actually creates 20 separate representations of it in memory. Whereas if a symbol (such as $name) appears 20 times in a program, JavaGram creates only one instance of it, causing all occurrences to point to the same representation. To appreciate the comparison efficiency, consider the following cases. "name" == "name" $name == $name // gives true // gives true The == operator checks for equality. Now consider the identicity operator. "name" === "name" $name === $name // gives false (stored in separate places) // gives true (stored in the same location) This operator returns true only when its two operands point to the same thing. It’s exactly this reason that makes symbols suitable for use in a switch. 2.6 Iteration A common programming task is to repeat a certain computation many times (e.g., calculate the interest for each account held at a bank). This is called iteration and is supported by various forms of loop statements. As an example, let’s consider the task of calculating the factorial of a number. The factorial of a number n (written as n!) is defined by these rules: Factorial of 0 is 1. Factorial of n is n times factorial of n-1. Fundamentals 39 So, for example: 5! = 5 × 4 × 3 × 2 × 1 = 120 Here is a simple class that uses a loop to implement factorial. <jag domain="doc/code/chap2"> class Factorial { public static int factorial (int n) { int f = n == 0 ? 1 : n; while (n > 1) { f *= n - 1; n -= 1; } return f; } public static void main () { sys.println("factorial(5) = ", factorial(5)); } } </jag> The while loop in the factorial() method iterates through decreasing values of n. The loop condition (n > 1) is first evaluated. If this condition evaluates to true then the loop body (appearing within a pair of braces) is executed. Otherwise, the loop is terminated. Each time round the loop, f is multiplied by the next value (n - 1) and n is decremented in preparation for the next iteration. Running this program produces the following output. factorial(5) = 120 Incrementing and decrementing an integer are such common tasks that there are specific operators for them: ++ (auto increment operator) increments an integer by 1 and -- (auto decrement operator) decrements an integer by one. We can use the auto decrement operator to write factorial() more succinctly: public static int factorial2 (int n) { assert n >= 0; int f = n == 0 ? 1 : n; while (n > 1) f *= --n; return f; } The auto increment/decrement operator can appear before or after an lvalue. If it appears before (as in the above example), the lvalue is incremented/decremented first and then its 40 JavaGram Agile Development resulting value is used. Conversely, if it appears after, the lvalue is used and then it’s incremented/decremented. Also note that, because the while loop now has a single statement as its body, there is no need to use braces to enclose it. Finally, we’ve added an assert statement to the beginning of the method to guard against situations where n is negative. An assert is always followed by a logical condition. If the condition evaluates to false, an error is raised. There is a variant of the while loop called the do-while loop. It’s useful for situations where we want the loop body to execute at least once. Unlike the while loop, a do-while loop first executes the loop body and then evaluates the loop condition. Here is a simple class that illustrates its use. <jag domain="doc/code/chap2"> class Prime { public static boolean isPrime (int n) { assert n > 0; int i = 2; do { if (n % i == 0) return false; } while (++i < n/2); return true; } public static void main () { sys.println("isPrime(13) = ", isPrime(13)); sys.println("isPrime(111) = ", isPrime(111)); } } </jag> The method isPrime() returns true when its parameter (n) denotes a prime number. A prime number is one that’s divisible only by itself and 1. We’ve used a very simple algorithm here which iterates i from 2 to n/2, and checks to see if n is divisible by i. The latter is done using the remainder operator (%) which gives the remainder of dividing n by i. Note how we’ve declared the return type of the method to be boolean. This type covers only two possible values: true and false. When run, the program produces the following output. isPrime(13) = true isPrime(111) = false Fundamentals 41 The final form of loop to be discussed here is the for loop. We can rewrite the isPrime() method more succinctly using this loop. public static boolean isPrime2 (int n) { assert n > 0; for (int i = 2; i < n/2; ++i) { if (n % i == 0) return false; } return true; } The parentheses appearing after the for keyword can accommodate three parts (separated by semicolons) every one of which is optional. The first part is performed only once when the loop commences execution. This is typically an assignment, but more commonly a locally declared variable that’s initialized to a value (as is the case in the above loop). The second part is the loop’s logical condition. The last part is a step that’s performed at the end of each iteration (before the loop condition is re-evaluated). Note that because i is declared as the loop’s local variable, it remains only visible within the loop, so it cannot be referred to outside the loop. The simplest form of for loop is one where all the optional parts are missing. for (;;) { //... } The effect of this loop is to iterate indefinitely; it’s equivalent to the following while loop. while (true) { //... } 2.7 Composites The term composite refers to a data structure that can hold multiple values. By contrast, simple data items (such as numbers, strings, symbols) are called atomic because they cannot be broken down into smaller things. We’ve already seen one form of composite data: class instances. The second form is called containers. 2.7.1 Containers Containers are exceptionally useful for everyday programming, so much so that JavaGram has built-in notations to support them. There are three different types of containers: 42 JavaGram Agile Development A list is an ordered sequence of items. Internally, a list is implemented as a linked list of items (i.e., each item has a pointer to the next item in the list). This makes lists unsuitable for random access because, in order to get to an item, you have to iterate through the earlier items in the list. The advantage of lists, however, is that they carry very little storage overheads. That makes them ideal for reasonably short sequences (i.e., up to tens of items). A vector is also an ordered sequence of items but with the added advantage of random access. Given the zero-based position of an item in the vector, you can look it up directly. Vectors are especially suitable for large data sets that require random and/or frequent access. A map is a collection of items where each item (value) is accessed through a key. The items are maintained in ascending lexicographic key sort order. Conceptually, you can think of each entry in the map as a key/value pair. Maps support random access – given a specific key, you can look up the corresponding value. Containers are very flexible in that the items in a container can themselves be containers. So, for example, you can have a map whose keys are symbols and whose values are vectors, or a vector whose items are themselves vectors, and so on. You would have noticed that with the atomic types, you can specify a value either literally (e.g., 10) or programmatically (e.g., i + 10). The same is true of container types. 2.7.2 Lists All lists are of type list. Here is a simple list of three numbers: list nums = $(10, 20, 30); List literals are always delimited by parentheses, and the individual items are separated by commas. However, if a list literal is not inside another literal, its first parenthesis must be preceded by a $ (as in the above example) to avoid ambiguity. To access the items in a list, you can use the sys.head() and sys.tail() methods. For example: sys.head(nums) sys.tail(nums) sys.head(sys.tail(nums)) // gives 10 // gives (20, 30) // gives 20 Alternatively, you can use the [] operator to access list members. For example: nums[1] // gives 20 You can also create a list dynamically using the sys.pair() method. For example: list nums = sys.pair(10, sys.pair(20, sys.pair(30, null))) Fundamentals 43 The inner-most call, sys.pair(20, null), creates the list (30) and the outer calls add 20 and then 10 in front of this list to produce (10, 20, 30). A much easier way of doing the same is to use the list() operator: list nums = list(10, 20, 30); As a rule, the end of a list is always marked by a null (which is also a shorthand for an empty list). Also, for programming convenience, applying sys.head() or sys.tail() to null will produce null. The elements of a list need not be of the same type – they can be anything. For example: list ls = $(1, "man", "bites", ($animal, "dog"), 3, "times") Note how the inner list has no preceding $, because it appears inside another literal. 2.7.3 Vectors The basic type for a vector is vector. Here is a simple vector literal of three numbers: vector nums = [10, 20, 30] Vector literals are always delimited by square brackets, and the individual items are separated by commas. As with lists, the elements of a vector need not be of the same type, but you can enforce a specific element type where desirable. For example, the above list is better written as vector<int> nums = [10, 20, 30]; because it explicitly states that the elements are expected to be of type int. In this case, should the vector contain a non-integer element, an error will be raised. You can access an element of a vector using its zero-based index. For example: nums[1] // gives 20 The sys pseudo class provides a number of methods for inserting, removing, and finding vector elements, as exemplified below. sys.insert(nums, 2, 25) sys.append(nums, 40) sys.find(nums, 30) sys.remove(nums, 1, $at) sys.member(nums, 30) sys.length(nums) sys.clear(nums) 44 // // // // // // // inserts 25 at position 2 appends 40 to the end of the vector returns the index of 30 (ie, 2) removes the element at position 1 gives true gives the number of elements in the vector removes all the vector elements JavaGram Agile Development Let’s look at a real-life example that illustrates the use of both vector and list. The following class is intended to capture directions in order to get from one location to another, in the style: go 5km east, then 7km south, then 12km west, etc. It uses a vector of lists (called legs) to capture the individual legs of a journey, where each leg is of the form (direction, distance). <jag domain="doc/code/chap2"> class Direction { vector<list> legs @= vector(); public void addLeg (symbol dir, real dist) { sys.append(legs, list(dir, dist)); } public real totalDistance () { real total = 0; for (int i = 0, n = sys.length(legs); i < n; ++i) total += legs[i][1] @ real; return total; } public real shortestDistance () { real east = 0; real north = 0; for (list leg in legs) { real dist @= leg[1]; switch (leg[0]@symbol) { case $north: north += dist; break; case $south: north -= dist; break; case $east: east += dist; break; case $west: east -= dist; break; } } return sys.sqrt(east * east + north * north); } public static void main () { Direction d = new Direction(); d.addLeg($north, 12.5); d.addLeg($west, 7.2); d.addLeg($south, 2.6); d.addLeg($east, 20.1); sys.println("Total distance = ", sys.format(d.totalDistance(), "0.00km")); sys.println("Shortest distance = ", sys.format(d.shortestDistance(), "0.00km")); } } </jag> The method addLeg() adds a new leg to the journey by appending a new list to the end of the legs vector. The method totalDistance() uses a for-loop to iterate through the legs vector and adds up the distance of each leg. A couple of points worth noting about this method: Fundamentals 45 Note how two (comma-separated) local variables are defined for the for-loop. This is a useful coding pattern to remember as it avoids the length of the vector being calculated for each iteration, which would have been the case, had it been coded as: for (int i = 0; i < sys.length(legs); ++i). When adding the leg’s distance to total (inside the loop) the value is first converted to real using the type cast operator @. This is necessary because a list’s elements are un-typed. This is a good time to introduce the for-in loop – a variant of the for-loop that’s particularly well suited to iterating through a container. Using it, totalDistance() can be written more elegantly: public real totalDistance2 () { real total = 0; for (list leg in legs) total += leg[1] @ real; return total; } A for-in loop must always have a local variable (e.g., leg) and a container (e.g., legs). The latter must be a vector, map, or GUI container. Each time round the loop, the variable points to the next container element. The shortestDistance() method calculates the shortest distance between the start and end points. It uses a for-in loop and a switch to work out how far (in total) we’ve moved north and east, and then uses the Pythagoras theorem to calculate the shortest distance. The sys.sqrt() method calculates the square root of a number. The line before the switch uses the @= operator to cast the right side of the assignment to the type expected by the lvalue on the left side, before performing the assignment. This is more elegant than writing: real dist = leg[1] @ real; When executed, the program produces the following output. Total distance = 42.400000000000006 Shortest distance = 16.260996279441187 These numbers look a little bit ugly (too many decimal places) and have no unit. We can use the sys.format() method to limit them to, say, two decimal places and express them in kilometers: sys.println("Total distance = ", sys.format(d.totalDistance(), "0.00km")); sys.println("Shortest distance = ", sys.format(d.shortestDistance(), "0.00km")); 46 JavaGram Agile Development This will change the output to something more user friendly: Total distance = 42.40km Shortest distance = 16.26km 2.7.4 Maps The basic type for a map is map. Here is a simple map that associates peoples name with their age: map age = ["Adam"=>20, "Paul"=>28, "Linda"=>18]; Map literals are always delimited by square brackets, and the pairs are separated by commas. The keys (e.g., "Adam") and values (e.g., 20) of a map need not be all of the same type, but you can enforce a specific key and/or value type where desirable. For example, the above map is better written as map<string,int> age = ["Adam"=>20, "Paul"=>28, "Linda"=>18]; because it explicitly states that the keys are expected to be of type string and the values of type int. In this case, should the map contain any other type of key or value, an error will be raised. You can look up a value in a map using its key. For example: age["Linda"] // gives 18 The same notation can be used to store a key/value pair in a map, or to overwrite an existing one: age["Linda"] = 19 age["Jane"] = 30 // changes Linda’s age to 19 // adds a new key/value pair The keys in a map must be atomic, but the values can be anything. If you want to specify the type of keys or values but not the other, use the vague type (which stands for an unspecified type). For example: map<symbol,vague> person = [$name=>"John", $age=>22, $male=>true] In fact, the type map is equivalent to map<vague,vague> and vector is equivalent to vector<vague>. A map whose keys are symbols has some similarity to an object, so JavaGram allows you to use the dot notation instead of [] to access it. For example: person.$name person.$name = "Alan" Fundamentals // same as: person[$name] // same as: person[$name] = "Alan" 47 The sys pseudo class provides a number of methods for use with maps, as exemplified below. sys.remove(person, $age) sys.length(person) sys.member($age, person) sys.mapKeys(person) sys.mapValues(person) sys.clear(person) // // // // // // removes the $age key and its value gives the number of elements in the map gives true if the key is in the map gives: [$age, $male, $name] gives: [22, true, "John"] removes all the map elements Internally, maps are maintained in ascending lexicographic key sort order. So, for example, if you print a map using sys.print(), the elements will appear in ascending key sort order. Similarly, sys.mapKeys() returns the keys in ascending sort order. Let’s look at a program that demonstrates the versatility of maps. The following class scans the file hierarchy in a directory and, for each file, counts the number of lines, words, and characters. It uses a map (counter) to keep track of these statistics for each scanned file. <jag domain="doc/code/chap2"> class WordCount { map<string, map<symbol,int>> counter @= map(); public void scanDir (string path) { for (string file in sys.listDir(path)) { string filePath = sys.pathConc(path, file); if (sys.pathProps(filePath)[$type] == $dir) scanDir(filePath); else scanFile(filePath); } } public void scanFile (string path) { stream s = sys.open(path, "r"); string line; int nLines = 0, nWords = 0, nChars = 0; while ((line = sys.readln(s, false)) != null) { ++nLines; nWords += countWords(line); nChars += sys.length(line); } counter[path] = map($lines=>nLines, $words=>nWords, $chars=>nChars); sys.close(s); } protected int countWords (string line) { int n = 0; for (string str in sys.strSplit(line, " ")) { if (sys.length(sys.strTrim(str, true)) > 0) ++n; 48 JavaGram Agile Development } return n; } public void clear () { sys.clear(counter); } public void output () { vector<string> files @= sys.sort(sys.mapKeys(counter)); map<symbol, int> total = map($lines=>0, $words=>0, $chars=>0); for (string file in files) { map<symbol, int> count = counter[file]; sys.println($"{file}: lines: {count[$lines]}, words: {count[$words]}, chars: {count[$chars]}"); for (symbol s in total) total[s] += count[s]; } sys.println($"Total: lines: {total[$lines]}, words: {total[$words]}, chars: {total[$chars]}"); } public static void main () { WordCount wc = new WordCount(); wc.scanDir(sys.pathConc(sys.root, "doc/code/chap2")); wc.output(); } } </jag> The scanDir() method takes the path of a directory as parameter and uses sys.listDir() to get a list of all files/directories in that directory. sys.pathConc() is used to concatenate the parent directory’s path with each file/directory name to produce an absolute path. sys.pathProps() returns the properties of a path as a map which contains, amongst other things, a $type key that points to $file or $dir, depending on whether it’s a file or directory. For a directory, we call scanDir() recursively; and for a file, we call scanFile(). To scan a file, scanFile() opens the file using sys.open(). The second argument to this method ("r") stands for ‘read mode’. This method returns a stream (a built-in JavaGram type that can be used for I/O with respect to files, buffers, channels). sys.readln() is used to read the next line of the file, which it returns as a string. The second argument to this method indicates whether the end-of-line (EOL) character should be included (set to false to exclude EOL). When we reach the end of the file, this method returns null. For each line, we increment three local counter variables, which are then used after the loop to add a new map to the counter map. Finally, we close the stream using sys.close(). countWords() counts the number of words in a line. We’ve used a very simple algorithm here: sys.strSplit() splits the line into its space-separated sub-strings. A sub-string is considered a word if after trimming it of blanks it remains non-empty. Fundamentals 49 The output() method formats and writes the result of a scan (as denoted by the counter map) to standard output. The files are first sorted in alphabetic order using sys.sort(). A for-loop is then used to iterate through the files, outputting the counters for each file and at the same time building up totals in the totals map. For outputting the file counters as well as the total counters, we’ve used delayed strings. A delayed string is like a normal string but is preceded by a $ character. For example: $"{file}: lines: {count[$lines]}, words: {count[$words]}, chars: {count[$chars]}" Within a delayed string, anything enclosed by a pair of braces is treated as an expression. When the delayed string is evaluated, these expressions are individually evaluated and their values are spliced into the string. Running this program will produce output similar to the following. C:/JavaGram/doc/code/chap2/BankAcc.jag: lines: 27, words: 91, chars: 646 C:/JavaGram/doc/code/chap2/Calculator.jag: lines: 53, words: 173, chars: 1092 C:/JavaGram/doc/code/chap2/Car.jag: lines: 16, words: 53, chars: 296 C:/JavaGram/doc/code/chap2/CarTest.jag: lines: 13, words: 25, chars: 190 C:/JavaGram/doc/code/chap2/Direction.jag: lines: 45, words: 167, chars: 1152 C:/JavaGram/doc/code/chap2/Factorial.jag: lines: 24, words: 87, chars: 420 C:/JavaGram/doc/code/chap2/HelloWorld.jag: lines: 7, words: 16, chars: 115 C:/JavaGram/doc/code/chap2/Prime.jag: lines: 28, words: 96, chars: 557 C:/JavaGram/doc/code/chap2/WordCount.jag: lines: 55, words: 185, chars: 1481 Total: lines: 789, words: 1675, chars: 11467 2.7.5 Object Literals An unusual feature of JavaGram is that it allows class instances to be specified as literals. This is useful when you want to pre-create objects in code (load time) to represent things such as meta data, rather than during execution (run time). Another benefit of this approach is that it makes it very easy to serialize objects (in order to persist them to a file or database) and to subsequently parse them (upon retrieval from the file or database). By the same token, when you write an object to standard output, JavaGram outputs the object as a literal, making it very easy to read and understand. Recall the Direction class presented earlier in this chapter. You can use sys.println() on the sample object to get its literal representation: Direction d = new Direction(); //... sys.println(d); This will display the following: [@doc\code\chap2\Direction legs=>[($north, 12.5), ($west, 7.2), ($south, 2.6), ($east, 20.1)]] 50 JavaGram Agile Development Note the similarity to the map literal notation, except that: The qualified class name appears at the beginning preceded by the @ character to specify the object type. The reason for the qualified rather than short class name is that this notation needs to be transportable. Each object field (there is only one here: legs) is mapped to its literal value. Unlike maps, however, the keys are the actual field identifiers, not literals. Creating objects directly using this notation is easy. For example: Direction dir = [@Direction legs=>[($west, 13.1), ($south, 1.9)]]; We’ve used the short class name here, assuming that the class domain is visible. Upon parsing this code, JavaGram expands the short name to a qualified name. Just as vectors and maps have special operators for dynamic creation, you can use the object operator to create an object dynamically and directly. For example: Direction dir = object(Direction, legs=>[($west, 13.1), ($south, 1.9)]); If you want to get the string equivalent of an object, you can use the sys.serialize() method. For example, string str = sys.serialize(dir); sets str to: "[@doc\code\chap2\Direction legs=>[($west, 13.1), ($south, 1.9)]]" Where explicit parsing is required (e.g., after reading an object literal from persistent storage), you can use the sys.parse() method. For example, sys.parse(str) returns the original object. The Object library class (described in Chapter 13) uses this approach for managing the persistence of business objects. See also the sample application (in Chapter 8) for a real-life example of how persistent business objects are utilized. When writing an object literal, you can leave any of the class fields unspecified – these are automatically set to null. For efficiency and convenience, JavaGram uses a very direct method to manage the creation of object literals. This means that class constructors (if any) are bypassed. Therefore, if your constructors enforce important invariants or perform vital resource management, these will not take place and remain your own responsibility. Fundamentals 51 2.7.6 Literal versus Dynamic As we’ve seen, composite data can be specified either as literals or created dynamically. So what’s the difference? The key difference is creation time. Literals are created when the code in which they appear is loaded (i.e., ‘parse’ and ‘analyze’ steps, according to the diagram at the beginning of this chapter). Dynamic data is created during the course of program execution (i.e., ‘evaluate’ step in the same diagram). As a result, literals are created only once, because their code is loaded only once, whereas dynamic data is created every time the corresponding code is executed. This is a subtle difference that can be easily overlooked by newcomers to JavaGram, with potentially dire consequences. It is somewhat similar to programmers confusing the behavior of static and non-static data. Both these forms have their legitimate place in JavaGram programming, so you must be mindful of using the appropriate form for a given situation. Let’s illustrate the difference (and consequences of misuse) using an example. Recall the definition of the WordCount.output() method: public void output () { vector<string> files @= sys.sort(sys.mapKeys(counter)); map<symbol, int> total = map($lines=>0, $words=>0, $chars=>0); //... } The total map in this method is created dynamically. What would happen if we change this to a map literal? public void output () { vector<string> files @= sys.sort(sys.mapKeys(counter)); map<symbol, int> total = [$lines=>0, $words=>0, $chars=>0]; //... } Running this version will produce the same result as before. However, a subtle defect has been introduced. If you call output() multiple times in the same run, the counters in total will not start from zero, but will retain their value from the last call to output(). Remember, a literal is created once at load time, so in each call to output(), total is set to refer to this same map, not a new copy, causing alterations made to it in the previous call to be retained! When literals are misused, the resulting defects might not be immediately obvious and could require further testing to detect. Literals are generally safe and recommended for these situations: 52 Read-only data JavaGram Agile Development Meta data Persistence 2.8 Exception Handling We’ve already seen how the assert statement can be used to impose invariants (conditions that must hold). However, assertions have limited flexibility in that any violation is reported as an error, with no further opportunity to handle the error. Therefore, its use should be limited to scenarios where you want to guard against blatant misuse. There are other potential error situations that do not represent misuse but rather possible set of circumstances that deserve to be detected and gracefully handled. These are called exceptions. A simple example of this would be dealing with an invalid postcode in a postal address. JavaGram provides an exception handling facility for such situations. Let’s look at an example. Recall the WordCount.scanFile() method from an earlier example in this chapter. This method does not cater for a potential error situation: what if the file is not accessible for reading? In this case, the method sys.open() will fail, causing a runtime failure of the program. To cater for this situation, we can rewrite the method as follows. public void scanFile2 (string path) { stream s = null; try { s = sys.open(path, "r"); string line; int nLines = 0, nWords = 0, nChars = 0; while ((line = sys.readln(s, false)) != null) { ++nLines; nWords += countWords(line); nChars += sys.length(line); } counter[path] = map($lines=>nLines, $words=>nWords, $chars=>nChars); } catch (Exception e) { sys.println(sys.err, "Can't read file: ", path); } finally { if (s != null) sys.close(s); } } We’ve used a try-catch-finally statement to detect such an exception and deal with it. The effect of this is that the statements in the try block are evaluated. If an exception arises, the catch blocks following it are examined. The first catch block whose exception type matches the raised exception is executed. The finally block is executed last, Fundamentals 53 regardless of whether an exception arises or not, and even if a return statement is executed. Note how we’ve set the stream s to null before we enter the try block. If the sys.open() call succeeds then s gets set to a valid stream. Otherwise, an exception is thrown and s will remain null. Therefore, the finally block will only attempt to close the file if s is not null. This simple code pattern ensures that there is no possibility of this method leaving the file open due to an error. Within the catch block, we simply report the fact that the file is not readable by writing a message to standard error. Note how we’ve passed sys.err as the first argument to sys.println(). The former is a predefined stream and denotes the standard error stream. JavaGram provides three predefined streams: sys.out denotes the standard output stream (usually associated with the screen). sys.err denotes the standard error stream (also usually associated with the screen). sys.in denotes the standard input stream (usually associated with the keyboard). In our earlier uses of sys.println(), we never referred to any of these. That’s because when no stream in specified, output methods such as sys.println() assume the standard output stream. So, for example: sys.println("Hello") // is equivalent to: sys.println(sys.out, "Hello") A catch block must always have a signature similar to a method of one parameter, which must be of type Exception or a sub-class of Exception (sub-classes are described in the next chapter). Exception is a predefined JavaGram class, having the following definition. class Exception { protected string message; public Exception () {} public Exception (string msg) {message = msg;} public string getMessage () {return message;} } As well as the exceptions raised by JAG (e.g., division by zero), the programmer can detect error situations and throw an explicit exception. To do this, create an instance of Exception and throw it, for example, like this: throw new Exception("invalid postcode"); The general rule is that when a method throws an exception, it’s the responsibility of the calling code to catch and deal with that exception. 54 JavaGram Agile Development Once an exception has been caught by a catch block, it doesn’t propagate any further. However, sometimes it makes sense to do something in a catch block in response to an exception and then to propagate it by throwing it again. For example: catch (Exception e) { // Do something throw e; } In general, the catch block and the finally block of a try statement are optional – either may be absent but not both! Also, you can have multiple catch blocks, each responsible for catching and handling a different type of exception. We’ll discuss this further after we’ve introduced class inheritance in the next chapter. Fundamentals 55 3 Object-oriented Programming Classes were introduced in the previous chapter. This chapter builds on that foundation and describes how classes can be extended to take advantage of object oriented (OO) features such as inheritance and polymorphism. Simple programming examples are used to illustrate the application of these concepts. 3.1 Inheritance Think of a program that deals with geometric shapes – lines, rectangles, ovals, polygons, etc. Each of these can be represented by a class. Many methods would be common to all these classes, such as: draw(), move(), resize(), fill(). Simplistically, we can implement each shape using a separate class, but later on when we start using these classes, a recurring annoyance emerges: in order to do something to a shape, we need to know what type of shape it is. For example, suppose that we have a Canvas class that can accommodate multiple shapes, having a draw() method that draws the whole canvas by drawing each shape: class Canvas { vector<object> shapes @= vector(); //... public void draw () { for (object shape in shapes) { switch (typeof(shape)) { case $Line: shape@Line.draw(); break; case $Rectangle: shape@Rectangle.draw(); break; case $Oval: shape@Oval.draw(); break; case $Polygon: shape@Polygon.draw(); break; } } } } There are three problems with this design: The coding of Canvas methods, such as draw(), becomes quite tedious, requiring a switch to handle each shape type separately. Adding a new type of shape (e.g., PolyLine) will require changes to many of the Canvas methods to cater for it. This could involve significant effort and is potentially error prone. There are likely to be fields and methods with identical definition for each shape (e.g., color and getColor()). These would need to be redefined for every shape class, resulting in unnecessary duplication of code. 56 JavaGram Agile Development Inheritance provides an elegant solution to this problem, allowing us to write the common parts once and override those methods that are specific to each class. When designing classes, it’s useful to visualize the class hierarchy using a graphical notation such as UML. The following UML diagram shows how we can organize our shape classes to take advantage of inheritance. Shape -color: string Canvas shapes +draw (): void +draw(): void +move(x:int, y:int): void +resize(x:int, y:int): void +fill(color:string): void +getColor(): string +setColor(color:string): void Line Rectangle Oval Polygon +draw (): void +move(x:int, y:int): void +resize(x:int, y:int): void +draw (): void +move(x:int, y:int): void +resize(x:int, y:int): void +fill(color:string): void +draw (): void +move(x:int, y:int): void +resize(x:int, y:int): void +fill(color:string): void +draw (): void +move(x:int, y:int): void +resize(x:int, y:int): void +fill(color:string): void In UML, each class is represented by a box divided into three parts – the top part bears the class name, the middle part lists the class fields, and the bottom part lists its methods. Shape is an abstract class (hence appearing in italics). Abstract classes cannot be instantiated, but rather serve as design elements that other classes can extend. The directed line from the Canvas class to the Shape class represents aggregation, implying that Canvas can refer to multiple shapes. The directed line from each of the bottom classes to the Shape class represents inheritance, implying that these classes inherit members (fields and methods) from the Shape class. Because the bottom classes can be instantiated, they’re said to be concrete. In this diagram, Shape is said to be a base class (also called a super class). Each of Line, Rectangle, Oval, and Polygon is said to be a derived class (also called a subclass). The symbol appearing before each member denotes its visibility: - stands for private # stands for protected + stands for public The methods draw(), move(), and resize() are defined as abstract in Shape (appearing in italics). This means that their definition is deferred to a derived class. Each of the four derived classes provides its own implementation of these methods. The remaining Object-oriented Programming 57 methods and fields of Shape are inherited ‘as is’ by the derived classes, except for fill(). The latter is overridden by the 2D shapes and ignored by Line. Given this design, Canvas can be defined much more elegantly. class Canvas { vector<Shape> shapes @= vector(); //... public void draw () { for (Shape shape in shapes) shape.draw(); } } Because Shape is the base class for all shapes, we can draw the canvas by simply iterating through its shapes and invoking each shape’s draw() method without needing to know what type of shape it is. JavaGram resolves the call shape.draw() at runtime and invokes the draw() method of the relevant concrete class. Therefore, draw() is said to be a polymorphic method. 3.2 Shopping Cart Example This section describes a fairly complete example of how inheritance can be used to develop an OO solution to a problem. The problem is one of developing a shopping cart for an online store. First we need a representation of a customer visiting the store. For this, we’ll use a minimal class, as this is not the focus of our discussion. class Customer { protected getable string name; protected getable string address; //... public Customer (string name, string address) { this.name = name; this.address = address; } } The getable qualifier deserves some explanation. A common coding pattern is to define a get method (e.g., getName()) for private and protected fields of a class that need to be accessed by the class users. Rather than defining such a method explicitly, you can use the getable qualifier to instruct JavaGram to define it implicitly. JavaGram generates the following hidden method for the name field. public string getName () { return name; } 58 JavaGram Agile Development There is also a setable qualifier which generates a hidden method for setting the value of a field (setable also implies getable, so you don’t need to specify both). If used on the name field, JavaGram will also generate the following hidden method. public void setName (string name) { this.name = name; } The online store offers a range of products for sale. We’ll use an abstract class to represents all products. abstract class Product { protected getable string id; // Unique product ID protected getable real price; // Unit price is dollars protected getable real weight; // Unit weight in kilograms abstract public string format (); public void ship (int quantity) { Catalog.singleton.ship(id, quantity); } } The abstract qualifier appearing before the class definition marks Product as abstract, implying that this class can’t be instantiated directly. Each product has a unique numeric ID, a price (expressed in dollars), and a weight (expressed in kilograms). The format() method is intended to produce a string representation of the product. This method is defined as abstract and therefore has no implementation – the implementation is deferred to subclasses. The ship() method causes a given quantity of the product to be shipped. We’ll discuss the implementation of this method later on. Let’s now consider some real products. The first one is called Gadget and represents a manufactured piece of equipment (such as an iPod). class Gadget extends Product { protected getable string make; protected getable string model; protected getable int year; // Year of manufacture public string format () { return $"{id} {make} {model} {year} @{sys.format(price, ShopCart.MONEY)}"; } } Note how Gadget is derived from Product, using the keyword extends. This causes Gadget to inherit everything defined in Product. The fields defined in Gadget are in addition to the fields defined in Product. Any methods defined in the subclass are either in addition to the base class, or override the ones in the base class. In this case, we have one such Object-oriented Programming 59 method, format(), which provides an implementation of the same abstract method in Product. This method uses a delayed string to format its return value. The last expression in this string uses sys.format() to format the price of a gadget (ShopCart.MONEY is a defined constant in another class). This call effectively resolves to this: sys.format(price, "$0,000.00") The format string (the second argument) causes price to be formatted as a dollar figure to two decimal places, with every three whole figures comma separated. For example, 12000.45267 will be formatted as $12,000.45. The next product type is Book. class Book extends Product { protected getable string author; protected getable string title; protected getable string publisher; protected getable int year; protected getable string isbn; // Year of publication // ISBN public string format () { return $"{id} {author}, {title}, {publisher} {year}, ISBN {isbn} @{sys.fo rmat(price, ShopCart.MONEY)}"; } } Like Gadget, Book is a subclass of Product and has a similar definition. The third and final product type to discuss here is an electronic book. Because EBook is really a book, we’ve defined it by subclassing Book. Furthermore, to capture its additional behavior (i.e., the fact that it’s electronic and therefore downloadable), we’ve also subclassed it from another class: Downloadable. This is called multiple inheritance. class EBook extends Book, Downloadable { public real getWeight () { return 0.0; } public string format () { return super@Book.format() + super@Downloadable.format(); } public void ship (Customer cust, int quantity) { create(id, $"{cust.getName()}: {quantity} copies"); } } EBook overrides three methods from its base classes. getWeight() is overridden to return zero, because an ebook is not a physical entity. Note how format() invokes the same method from both base classes. The keyword super refers to a base class. However, 60 JavaGram Agile Development because there are two base classes, we’ve also used the cast operator to indicate which particular base class we’re referring to (this would be unnecessary if there were just one base class). Finally, we’ve also overridden the ship() method so that the downloadable file is generated by this method. The create() method is defined in the Downloadable class. mutual class Downloadable { protected string url; // URL to download from public void create (string prodId, string watermark) { url = Catalog.singleton.createDownloadableFile(prodId, watermark); } public string format () { return url == null ? "" : $" ({url})"; } } The mutual qualifier means that this class will be treated just once, no matter how many times it appears in a derivation hierarchy (more on this in the next section). The create() method refers to another class, Catalog, defined below. Having defined all our product types, we can now define the class that represents the shopping cart. class ShopCart { public static final string MONEY = "$0,000.00"; protected static int lastCartId = 0; // Last allocated cart ID protected int cartId; // Unique cart ID protected map<string,int> items @= map(); // Product ID => quantity protected Customer customer; // The customer who owns the cart protected real totalWeight; // Total weight of cart items protected real totalCost; // Total cost of items public ShopCart () { cartId = ++lastCartId; } public void addItem (string id, int quantity) { items[id] = quantity; } public void checkOut (Customer cust) { customer = cust; totalWeight = 0.0; totalCost = 0.0; for (string id in items) { Product prod = Catalog.singleton.getProduct(id); totalWeight += prod.getWeight(); totalCost += prod.getPrice() * items[id]; prod.ship(items[id]); } Object-oriented Programming 61 totalCost += shippingCost(totalWeight); } public static real shippingCost (real weight) { return weight * 5.5; } public void displayInvoice () { sys.println("Customer: ", customer.getName()); sys.println("Address: ", customer.getAddress()); sys.println("Order:"); int idx = 0; for (string id in items) { Product prod = Catalog.singleton.getProduct(id); sys.print(++idx, ". ", prod.format(), " x ", items[id], ": "); sys.println(sys.format(prod.getPrice() * items[id], MONEY)); } sys.println("Shipping: ", sys.format(shippingCost(totalWeight), MONEY)); sys.println("Total: ", sys.format(totalCost, MONEY)); } } Each ShopCart instance is allocated a unique ID (cartId) that’s generated by incrementing lastCartId. The items in the cart are captured by the items map, which maps selected product IDs to their purchase quantity. The customer, totalWeight, and totalCost fields are set during the check out process. Items are added to the cart using the addItem() method. The checkout() method iterates through the cart items, works out total weight and cost, and ships each item. The shipping cost is calculated by the shippingCost() method, whose value is added to the total cost. displayInvoice() outputs the customer details, selected items and their quantities and cost, shipping cost, and total cost. Note the use of the polymorphic method format() in this method. The design ensures that as future product types are added, there will be no impact on the ShopCart class. The Product and ShopCart classes refer to another class called Catalog. The latter provides an up-to-date catalog of all products available in the online store. singleton class Catalog { protected static int lastDownloadId = 0; protected string file; protected map<string,Product> products @= map(); // Product ID => Product details protected map<string,int> stock @= map(); // Product ID => quantity in stock public Catalog (string file) { this.file = file; stream s = null; try { s = sys.open(file, "r"); vague data = sys.eval(sys.read(s)); 62 JavaGram Agile Development if (!(data instanceof vector<list>)) throw new Exception(file + " has invalid format"); for (list pair in data@vector<list>) { Product prod @= pair[0]; int quantity @= pair[1]; products[prod.getId()] = prod; stock[prod.getId()] = quantity; } } finally { if (s != null) sys.close(s); } } public void save () { vector<list> data @= vector(); for (string id in products) sys.append(data, list(products[id], stock[id])); stream s = null; try { s = sys.open(file, "w"); sys.ppln(s, data); } finally { if (s != null) sys.close(s); } } public void displayCatalog () { sys.println(sys.length(products), " products in catalog:"); for (vague id in sys.sort(sys.mapKeys(products))) sys.println(products[id].format(), " (", stock[id], " available)"); sys.println(); } public Product getProduct (string id) { return products[id]; } public int inStock (string prodId) { int quantity = stock[prodId]; return quantity == null ? 0 : quantity; } public void ship (string prodId, int quantity) { if (inStock(prodId) < quantity) throw new Exception(prodId + " short of stock"); stock[prodId] -= quantity; } public string createDownloadableFile (string prodId, string watermark) { string url = $"https://www.acme.com/download/{prodId}_{++lastDownloadId}.pdf"; // TODO: create a PDF file for the product denoted by prodId, where // each page is watermarked with the string denoted by watermark. return url; Object-oriented Programming 63 } } This class is defined as singleton – JavaGram will ensure that no more than one instance of this class will exist in a running process. Singleton is a commonly used design pattern, so JavaGram provides direct support for it. You can refer to the one-and-only instance of a singleton class using the Class.singleton notation (e.g., Catalog.singleton). If no instance exists yet, JavaGram will create one for you. Otherwise, it will just return the existing instance. Most singleton classes either don’t have a constructor or have a default constructor (i.e., one with no parameters). This enables JavaGram to create an instance implicitly. Where a singleton class’s constructor requires parameters, the programmer must create the instance explicitly. We’ll show later on how to do this for the Catalog class. In a real application, Catalog and ShopCart would store their data in a database. For simplicity however, we’ve chosen to store the catalog data in a file. The path for this file is passed to the constructor. The assumed format for the file is that it contains a vector of lists, where each list consists of a product and its quantity in stock. The constructor reads the data from this file, checks that it’s a vector and then iterates through the vector to populate the products and stock maps. Note the use of sys.read() for reading the file data. Unlike sys.readln() which reads data textually, line by line, sys.read() reads valid JavaGram expressions. We pass this to sys.eval() to also perform any necessary evaluations, though this is not really necessary in this case, as the data is expected to consist of literals only. The save() method writes the data back to the file, ensuring that any changes (new products added, changed quantities) are permanently saved. The ship() method simply reduces the stock quantity for a product as a result of a purchase. The createDownloadableFile() method is intended to generate a PDF file for a given digital product, where each page bears a watermark. It returns the URL of the generated file. A sample catalog file is shown below to illustrate the intended format. Catalog.jag [$([@Gadget id=>"GM001", price=>249.0, weight=>0.14, make=>"Apple", model=>" iPod classic 120GB", year=>2009], 10) ,$([@Gadget id=>"GM002", price=>399.0, weight=>0.115, make=>"Apple", model=> "iPod touch 32GB", year=>2009], 10) ,$([@Gadget id=>"GM003", price=>149.0, weight=>0.0368, make=>"Apple", model= >"iPod nano 8GB", year=>2009], 5) ,$([@Gadget id=>"GM004", price=>199.0, weight=>0.0368, make=>"Apple", model= >"iPod nano 16GB", year=>2009], 12) ,$([@Gadget id=>"GM005", price=>79.0, weight=>0.0107, make=>"Apple", model=> "iPod shuffle 4GB", year=>2009], 30) ,$([@Book id=>"PB001", price=>35.0, weight=>0.45, author=>"Peter Black", tit le=>"Famous Gardens", publisher=>"Lighthouse", year=>2002, isbn=>"0-21217625-9"], 18) 64 JavaGram Agile Development ,$([@Book id=>"PB002", price=>40.0, weight=>1.04, author=>"Mary Adams", titl e=>"Art of Sewing", publisher=>"Acme House", year=>2006, isbn=>"0-143-122312"], 15) ,$([@EBook id=>"EB001", price=>12.0, weight=>0.0, author=>"Jane Cornwall", t itle=>"Child Psychology", publisher=>"Canyon", year=>2008, isbn=>"0-25419826-6"], 10) ] The final class to describe in this section is a simple test driver for the earlier classes. class OnlineStore { static { new Catalog(sys.pathConc(sys.root, "doc/code/chap3/Catalog.jag")); } public static void main () { Catalog.singleton.displayCatalog(); ShopCart cart = new ShopCart(); cart.addItem("GM003", 1); cart.addItem("PB001", 2); cart.addItem("EB001", 1); Customer cust = new Customer("John Smith", "5 Victory Dr, Blacktown, Wind sor 768872-24"); cart.checkOut(cust); cart.displayInvoice(); } } The static block defined in this class deserves some explanation. Sometimes there are initializations that you want to perform before any class instance is created. A static block provides a way of doing this. Code appearing in a static block is executed (only once) when the class is loaded. You can have multiple static blocks in a class. These are executed in the order in which they appear. As states earlier, because Catalog is a singleton class whose constructor takes a parameter, its instance must be created explicitly. The purpose of this static block is to do just that. Note that we don’t need to assign this instance to any variable, because we can subsequently refer to it as Catalog.singleton. The main() method displays the catalog, creates a shopping cart, adds three items to it from the catalog, adds a customer, performs a check out, and displays the invoice. To summarize, the class hierarchy for this program is presented below as a UML diagram. Object-oriented Programming 65 OnlineStore ShopCart Catalog -cartId: int -items: map<string,int> -totalWeight: real -totalCost: real -stock: map<string,int> -file: string -stock: map<string,int> +Catalog(file:string) +save(): void +displayCatalog(): void +getProduct(id:string): Product +inStock(prodId:string): int +ship(prodId:string, quantity:int): void +createDow nloadableFile(prodId:string, w atermark:string): string +addItem(id:string, quanitity:int): void +checkOut(cust:Customer): void +shippingCost(): real +displayInvoice(): void Customer products * -name: string -address: string Product -id: string -price: real -w eight: real +format(): string +ship(cust:Customer, quantity:int): void Gadget -make: string -model: string -year: int +format(): string Book -author: string -title: string -publisher: string -year: int -isbn: string +format(): string «mutual» Downloadable -url: string +create(prodId:string, w atermark:string): void +format(): string EBook +getWeight(): real +format(): string +ship(quantity:int): void When run, the program produces the following output. 8 products in catalog: EB001 Jane Cornwall, Child Psychology, Canyon 2008, ISBN 0-254-19826-6 @$12.00 (0 available) GM001 Apple iPod classic 120GB 2009 @$249.00 (10 available) GM002 Apple iPod touch 32GB 2009 @$399.00 (10 available) GM003 Apple iPod nano 8GB 2009 @$149.00 (5 available) GM004 Apple iPod nano 16GB 2009 @$199.00 (12 available) GM005 Apple iPod shuffle 4GB 2009 @$79.00 (30 available) PB001 Peter Black, Famous Gardens, Lighthouse 2002, ISBN 0-212-17625-9 @$35.00 (18 available) PB002 Mary Adams, Art of Sewing, Acme House 2006, ISBN 0-143-12231-2 @$40.00 (15 available) Customer: John Smith Address: 5 Victory Dr, Blacktown, Windsor 768872-24 Order: 66 JavaGram Agile Development 1. EB001 Jane Cornwall, Child Psychology, Canyon 2008, ISBN 0-254-19826-6 @$12.00 (https://www.acme.com/download/EB001_1.pdf) x 1: $12.00 2. GM003 Apple iPod nano 8GB 2009 @$149.00 x 1: $149.00 3. PB001 Peter Black, Famous Gardens, Lighthouse 2002, ISBN 0-212-17625-9 @$35.00 x 2: $70.00 Shipping: $2.68 Total: $233.68 3.3 Mutual Classes Recall how the Downloadable class in the previous section was defined as mutual. This is a necessary and important consideration when using multiple inheritance. There are two basic rules regarding mutual classes: Where a derived class has multiple base classes, at most one of them can be nonmutual. All the base classes of a mutual class (if any) must also be mutual. These rules allow JavaGram to layout the fields of a derived class instance in a predictable manner and, at the same time, avoid duplication of field instances where a class participates multiple times in a derivation hierarchy. Sounds confusing? Let’s clarify the point using an example. «mutual» NetworkNode -location -bandw idth «mutual» «mutual» Transmitter Receiver -carrier -channels Transceiver Consider two mutual classes Transmitter and Receiver that extend another mutual class NetworkNode. A Transceiver is a device capable of transmission and reception, so it’s best defined as a derivation of Transmitter and Receiver. In OO programming, this kind of class hierarchy is called the dreaded diamond problem, because it creates a problem for the programming language in two respects: How to layout the fields of an instance of Transceiver in memory, given that NetworkNode’s fileds are effectively inherited twice. How to resolve a call to a method that’s defined in both Transmitter and Receiver. Object-oriented Programming 67 JavaGram addresses the first issue by ensuring that no matter how many times a super class is inherited by a subclass, its fields will appear only once in an object of the subclass. So in the above example, Transceiver will have four fields, not six. To address the second problem, JavaGram requires that you explicitly cast to the intended base class when making such calls (as was exemplified in the EBook.format() method of the previous section). 3.4 Final Qualifier Sometimes it’s desirable to prevent a class from being extended. Reasons may include: security concerns, efficiency considerations, or design constraints. To do this, we simply use the final qualifier when defining the class. A simple example would be a User class in a security module that controls access to a system. final class User { //... public void login (string username, string password) { //... } } The intent here is to prevent someone from subclassing User and overriding the login() method in order to avoid authentication. This qualifier can also be used at a method level, so a similar way of enforcing the above measure would be to define the class as: class User { //... public final void login (string username, string password) { //... } } In this case, the class can be extended but the login() method can’t be overridden. 3.5 This and Super When dealing with classes and inheritance, two keywords can come in handy: this and super. We’ve already seen instances of their use in earlier examples, so we’ll just recap their role and use here. Because both these refer to an instance of a class, it would be meaningless to try to use them in static methods. In the implementation of a non-static method, we can use this to refer to the implicit class instance on which the method is invoked. We typically do this to avoid ambiguity. 68 JavaGram Agile Development The most common case is when you have a method parameter or local variable that has the same name as a class field. By using the this.field notation, we avoid this ambiguity and make our intention clear. Another less common use is to invoke a constructor from another constructor. For example, in class Point { int x, y; public Point () { this(0, 0); } public Point (int x, int y) { this.x = x; this.y = y; } } The first constructor uses this to call the second constructor, and the second constructor uses this to overcome the ambiguity of fields and parameters having the same name. The super keyword is only meaningful in a derived class, and can be used to refer to a base class. When there is only one base class, the intention is clear. However, when there are multiple base classes, explicit casting must be used to overcome ambiguity. Earlier we saw an example of this in the EBook.format() method: public string format () { return super@Book.format() + super@Downloadable.format(); } Because EBook has two base classes, both of which have a format() method, we’ve used explicit casting after super to nominate the desired class to which the call should resolve. 3.6 Method Parameters Sometimes it makes sense to define more than one ‘flavor’ of the same method in a class. We saw an example of this in the previous section for the Point class, where two constructors are provided. This is called method overloading and can be applied to constructors as well as any other method. Here is another example: class DataCache { map<symbol,vague> cache @= map(); public void add (symbol key, vague data) { cache[key] = data; } public void clear () { sys.clear(cache); } public void clear (symbol key) { Object-oriented Programming 69 sys.remove(cache, key); } public void clear (vector<symbol> keys) { for (symbol key in keys) sys.remove(cache, key); } //... } This class provides three clear() methods – one that clears the entire cache, another that clears a specific item from the cache, and a third that clears multiple items from the cache. Another way of making a method more versatile is to give it default arguments. For example, the two constructors for the Point class can be written more elegantly as one: class Point { int x, y; public Point (int x = 0, int y = 0) { this.x = x; this.y = y; } } Given this definition, the following constructor invocations are all valid: Point p1 = new Point(); Point p2 = new Point(10, 20); Point p3 = new Point(10); // x == y == 0 // x == 10, y == 20 // x == 10, y == 0 In other words, where a trailing argument is not specified, the default value is used. As a general rule, all default argument values must be trailing. Similarly, we can combine the first two DataCache.clear() methods using a default argument: public void clear (symbol key = null) { if (key == null) sys.clear(cache); else sys.remove(cache, key); } However, this is not recommended as it will not improve the clarity of the code. The recommended rule of thumb is to use the style that delivers the most clarity. 70 JavaGram Agile Development 3.7 Class Variables An unusual but handy feature of JavaGram is that class names can be used as values, and therefore assigned to lvalues or passed as arguments to methods. When you do this, the corresponding lvalue must be of type vague. An actual example of this occurs in the EventInitiator class of lib/lang/Event.jag standard library script, a snippet of which appears below. mutual class EventInitiator { protected map<symbol, vector<EventListener>> eventMap @= map(); public void addListener (vague eventClassName, EventListener listener) { symbol eventName = typeof(eventClassName); vector<EventListener> listeners = eventMap[eventName]; if (listeners == null) eventMap[eventName] = listeners @= vector(); sys.append(listeners, listener); } //... } Here, the first parameter of addListener() is intended to be an event class name. For example, given a BindEvent class, we could write something like this: EventInitiator editor; EventListener screen; //... editor.addListener(BindEvent, screen); Note how addListener() uses the typeof operator to convert the class name to a symbol. This operator returns the qualified class name as a symbol. You can also get the qualified name of a class as a symbol using the notation ClassName.symbol (for example, Point.symbol). But note that typeof and .symbol serve completely different purposes – the former operates on an expression, whereas the latter is applied directly to a class name. Finally, given an expression that evaluates to a class instance, you can use the notation expr.class to get its class name. For example, if pt is of type Point then pt.class gives Point as a vague value. Object-oriented Programming 71 4 GUI Programming Modern applications are generally expected to have a Graphical User Interface (GUI) designed with intuitiveness and ease of use in mind. GUIs, however, tend to be code intensive due to complexities such as event handling, data binding, the need for flexible navigation paths, and so on. One of the design goals of JavaGram is to substantially reduce this complexity by offering a declarative style of programming as opposed to the procedural style of established GUI frameworks and libraries such as Java’s Swing. JavaGram’s promise is that you’ll write a lot less code, your code will be far more readable, and considerably easier to test, making the language particularly well-suited to rapid prototyping and agile development. 4.1 Demo Application To illustrate the many GUI elements to be covered in this chapter, we’ll use a demo application that we’ll gradually build up and add elements to. There is no specific business functionality behind this application other than showing how to code different types of elements and manipulate their properties. The first cut of this application (DemoApp.jag) is shown below. <jag domain="doc/code/chap4"> <load> "lib/gui/GuiApp" </load> singleton class DemoApp extends GuiApp { <App app lookAndFeel=$windows> <Frame frame title="Demo App" width=550 height=400 event=frameHandler/> </App> public DemoApp () { super(frame); } protected void frameHandler (native comp, symbol event) { if (event == $close) exit(); } public static void main () { DemoApp.singleton.run(); } } </jag> The easiest way to define a GUI application is to subclass the lib/gui/GuiApp library class, which is what we’ve done here. Singleton classes come very handy when writing GUI applications because many of the visual components tend to have singleton behavior. We’ve defined DemoApp as a singleton for this reason. 72 JavaGram Agile Development In JavaGram, GUI elements are defined using a markup notation. Such markups are defined inside classes and behave like class fields. The <App> element represents a GUI application. The identifier app names the element so that subsequently we can refer to the element using this identifier. For elements that appear at the same level as class fields and methods (called GUI class members) this is mandatory. For nested elements (such as <Frame> in this example) this is optional, so we name these only when we actually need to refer to them elsewhere in the code. GUI class members can have any of the qualifiers allowed for fields, except for getable and setable. For example, you can specify a GUI member to be protected static. Each element type accepts a certain set of properties. These properties are typed and can be set either at the time of defining the element or later on within the code of a method. For <App>, for instance, we’ve set the lookAndFeel property to $windows. This causes the application to assume a Microsoft Windows look and feel. Many of the GUI elements are containers, allowing you to define your GUI as a hierarchy. For example, we’ve defined <Frame> inside <App>. The former defines a main frame for the application. Some elements support event handlers. An event handler is a method with a predetermined signature that gets called by JavaGram when the element receives certain events. Event handlers are always defined using the event property, which is set to the name of the method that handles the events (e.g., frameHandler() in the above example). An event handler always takes two parameters: the comp parameter is set to the component that has raised the event (in this case the frame itself), and the event parameter is set to a symbol that represents the event (e.g., $close). The reason for having the first parameter is that you can have multiple elements sharing the same event handler, so you can examine comp to determine for which element the event has been raised. Our eventHandler() here is quite simple – when the event is $close (which is raised when the user clicks in the close box of the frame), it exists the application by calling the exit() method of the base class. GUI Programming 73 The main() method boots the application by calling the run() method of the base class, causing the above to be displayed. As we introduce other elements in the course of this chapter, feel free to look them up in Chapter 11 to find out more about their properties. Element types form a hierarchy, whereby one element type inherits the properties of another. As with classes, some element types are abstract, so you can’t actually use them in your code – they serve as abstractions for capturing common properties and behaviors. In general, however, element types are not classes, so you should be mindful not to treat them as such. 4.2 Panels, Layouts, and Fields Let’s extend our demo application by adding a tabbed pane to the main frame. For now, we’ll add just one tab page that represents a person. <App app lookAndFeel=$windows> <Frame frame title="Demo App" width=550 height=400 event=frameHandler> <Pane.tabbed tabs lay=$center> <Tab title="Person" image={sys.use("lib/gifs/Person.gif")}> <Indirect ref={PersonPanel.singleton.panel}/> </Tab> </Pane> </Frame> </App> The <Pane.tabbed> element represents a tabbed pane. The dot in this element’s tag name is a JavaGram convention that implies that there are a number of different pane types. We’ll see another one called <Pane.scroll> shortly. Note how we’ve used </Pane> as the closing tag. We could have equally used </Pane.tabbed>; the latter being the preferred style for its superior readability. Like <Frame>, <Pane.tabbed> is a container. However, the latter can only contain <Tab> elements. 74 JavaGram Agile Development For our tab, we’ve specified both a title and an iconic image, both of which are optional. The image property must be set to the absolute path of a GIF file. However, rather than specifying the image path as a string, we’ve used sys.use() and passed the relative image path to it (this path is relative to sys.root). This is the recommended coding style for specifying image paths, because it ensures that the program will work correctly in both standalone and client-server mode (introduced later in this book). sys.use() returns the absolute path of the image file and, if necessary, downloads the image from a server. Note how the call to sys.use() is enclosed in curly braces. In general, when you specify a value for a property, it’s expected to be a literal. A non-literal value (e.g., an arbitrary expression) must be enclosed in braces. This tells JavaGram that whatever is inside the braces must be evaluated and the resulting value used instead. Rather than coding the contents of the tab directly here, we’ve used an <Indirect> element. This element is very useful when you want to spread the code for a GUI across different classes. The ref property of this element must be set to the GUI component that it represents which, in this case, is a panel specified in the singleton class PersonPanel. <jag domain="doc/code/chap4"> singleton class PersonPanel { static vector<string> STATES = [ "", "ACT", "NT", "NSW", "QLD", "SA", "TAS", "VIC", "WA" ]; <Panel panel type=Person> <Layout.border/> <Panel lay=$north> <Layout.gridBag/> <Lay row=0 col=0 weight=0.0 margin=2 align=$east> <Label title="First Name"/> </Lay> <Lay row=0 col=1 fill=$horizontal margin=2> <Field.text key=$firstName/> </Lay> <Lay row=0 col=2 weight=0.0 margin=2 align=$east> <Label title="Last Name"/> </Lay> <Lay row=0 col=3 fill=$horizontal margin=2> <Field.text key=$lastName/> </Lay> <Lay row=1 col=0 weight=0.0 margin=2 align=$east> <Label title="Sex"/> </Lay> <Lay row=1 col=1 fill=$horizontal margin=2> <Combo key=$sex data=["", "Male", "Female"]/> </Lay> <Lay row=1 col=2 weight=0.0 margin=2 align=$east> <Label title="DOB"/> </Lay> <Lay row=1 col=3 fill=$horizontal margin=2> GUI Programming 75 <Field.date key=$dob format="dd MMM yyyy"/> </Lay> <Lay row=2 col=0 weight=0.0 margin=2 align=$east> <Label title="Occupation"/> </Lay> <Lay row=2 col=1 fill=$horizontal margin=2> <Combo key=$occupation model=occupComboModel/> </Lay> <Lay row=2 col=3 fill=$horizontal margin=2> <Option.tick title="Smoker" key=$smoker/> </Lay> </Panel> <Pane.tabbed lay=$center> <Tab title="Address"> <Panel key=$address type=Address> <Layout.gridBag/> <Lay row=0 col=0 weight=0.0 margin=2 align=$east> <Label title="Street"/> </Lay> <Lay row=0 col=1 colSpan=3 fill=$horizontal margin=2> <Field.text key=$street/> </Lay> <Lay row=0 col=4 weight=0.0 margin=2 align=$east> <Label title="City"/> </Lay> <Lay row=0 col=5 fill=$horizontal margin=2> <Field.text key=$city/> </Lay> <Lay row=1 col=0 weight=0.0 margin=2 align=$east> <Label title="State"/> </Lay> <Lay row=1 col=1 fill=$horizontal margin=2> <Combo key=$state data={STATES}/> </Lay> <Lay row=1 col=2 weight=0.0 margin=2 align=$east> <Label title="Postcode"/> </Lay> <Lay row=1 col=3 fill=$horizontal margin=2> <Field.text key=$postcode/> </Lay> <Lay row=1 col=4 weight=0.0 margin=2 align=$east> <Label title="Country"/> </Lay> <Lay row=1 col=5 fill=$horizontal margin=2> <Field.text key=$country/> </Lay> </Panel> </Tab> <Tab title="Comment"> <Pane.scroll> <Area.text key=$comment/> 76 JavaGram Agile Development </Pane> </Tab> </Pane.tabbed> <Panel lay=$south> <Button title="Bind to Map" image={sys.use("lib/gifs/Chain.gif")} action={bindToMap()}/> <Button title="Bind to Obj" image={sys.use("lib/gifs/Chain.gif")} action={bindToObj()}/> <Button title="Clear" image={sys.use("lib/gifs/Clear.gif")} action={clear()}/> <Button title="Map Binding" action={mapBinding()}/> <Button title="Obj Binding" action={objBinding()}/> </Panel> </Panel> public static final vector<string> OCCUPATIONS @= sys.sort([ "", "Engineer", "Scientist", "Accountant", "Teacher", "Manager", "Administrator", "Health Worker", "Pilot", "Driver", "Mechanic", "Public Servant", "Judge" ]); // Just to illustrates the model style for combos. protected vague occupComboModel (native combo, symbol cmd, int idx) { switch (cmd) { case $count: return sys.length(OCCUPATIONS); case $get: return OCCUPATIONS[idx]; } return ""; } protected void bindToMap () { map record = map( $firstName=>"John", $lastName=>"Smith", $sex=>"Male", $dob=>[#1982-12-22], $occupation=>"Mechanic", $smoker=>true, $comment=>"Sample comment", $address=>[ $street=>"9 Grange St", $city=>"Balwyn", $state=>"VIC", $postcode=>"3103", $country=>"Australia" ] ); gui.bind(panel, record); } protected void bindToObj () { Person record = [@Person firstName=>"Linda", lastName=>"Forbes", sex=>"Female", dob=>[#1987-02-15], occupation=>"Accountant", smoker=>false, comment=>"Another comment", address=>[@Address street=>"2 Smith St", city=>"Kew", state=>"VIC", postcode=>"3101", country=>"Australia" ] ]; gui.bind(panel, record); } protected void clear () { gui.bind(panel, map()); GUI Programming 77 } protected void mapBinding () { map record = map(); gui.save(panel, record); sys.println(record); } protected void objBinding () { Person record = new Person(); gui.save(panel, record); sys.println(record); } } </jag> In this class, a person is represented by a <Panel> element. A <Panel> is by far the most commonly-used container; it organizes its contents according to a specific layout. Where a layout is specified, it should appear first inside the panel. If not specified, the panel layout defaults to <Layout.flow>. The top-level panel in the PersonPanel class is specified to have a border layout. This layout allows you to organize the panel’s children according to the following diagram. For each child, you can specify a lay property, set to one of the above values (defaults to $center if unspecified). The unused parts of a border layout shrink to zero. Also, any unclaimed space is usually taken up by $center. In our example, we have a border-layout panel, where $north is used to display a person’s details, $center is used to show address details and comment, and $south is used to display a set of buttons, as illustrated below. 78 JavaGram Agile Development The northern panel itself is specified to have a grid-bag layout. This layout is useful for organizing a set of fields such that they are neatly aligned. Each child is laid out using a <Lay> element, the properties of which determine where it’s placed, how it’s aligned, etc. For specifying the fields, we’ve used the following elements: <Label> for the text appearing to the left of each field. <Field.text> for textual fields, such as first name. <Field.date> for date fields, such as date of birth. Note the use of the format property, which specifies the preferred format for data entry as well as display. <Combo> for drop-down combo boxes, such as sex. <Option.tick> for check boxes, such as smoker. For elements, such as combo boxes, that can display a multitude of values, there are two ways of specifying these values. The sex combo demonstrates the use of the direct data approach, where the data property is set to the list of values to be displayed in the combo’s drop down box. The occupation combo demonstrates the use of the data model approach, where the model property is set to the name of a method in the same class that provides this data. A combo’s model must have the following signature. vague comboModel (native combo, symbol cmd, int idx) JavaGram calls this method automatically, passing it appropriate arguments, whenever it needs to obtain information on how to display the combo. In the center of the panel, we’ve added a tabbed pane of two tabs – one for address details and one for arbitrary comments. The former is organized using a grid-bag layout similar to the person details panel. The latter uses two new elements: <Area.text> for text boxes that can accommodate multiple lines of text. GUI Programming 79 <Pane.scroll> to provide scrolling functionality for anything that can grow bigger than the physical space it occupies. The buttons in the south panel are for testing purposes. Each is specified as a <Button> element, whose action property is evaluated when the button is pressed. 4.2.1 Data Binding When a user is interacting with a GUI, two common patterns occur: Data entry, whereby the user keys in some data into the fields of a screen. The program then needs to transfer this data to some internal data structure, such as an object, before it can do something useful with it. Lookup, whereby the user retrieves information from, say, a database, and wants to view it in a screen. Again, the data needs to be transferred from the program’s internal data structure onto the screen fields. In most GUI frameworks, these two tasks are the direct responsibility of the programmer and may require much mundane coding. Although you can follow this same approach in JavaGram, there is a much easier way. You might have noticed that in the PersonPanel class, we’ve specified a key property for each field that can hold a value. This property specifies the relationship between a GUI element and its corresponding programmatic data. With this in place, you can bind an entire screen to a corresponding data structure, leaving the field-level detail to JavaGram to work out. In JavaGram, the default data structure for binding is a map. However, you can also use classes. If you refer back to the PersonPanel class, you’ll notice that both the person panel and the address panel have their type property, respectively, set to class names Person and Address. The minimal definition for these two classes is as follows. class Person { string firstName; string lastName; string sex; date dob; string occupation; boolean smoker; string comment; Address address; } class Address { string street; string city; string state; string postcode; string country; 80 JavaGram Agile Development } The key point to note is that the class field names match the key property of the corresponding panel fields (albeit the latter are specified as symbols). In particular, note how the address field of the Person class matches the key=$address property of the address panel. In other words, class aggregation can be mirrored by panel nesting. Now refer to the bindToObj() method which creates a Person object, whose address field refers to an Address object. The gui.bind() call binds the whole person panel to the person object, which then recursively binds the children (gui is a pseudo class – like sys – that provides methods specific to GUI functionality). Pressing the Bind to Obj button causes bindToObj() to be invoked and the binding to take effect, the result of which is displayed below. The objBinding() method does the opposite. It creates an empty Person object and invokes gui.save() on the panel and this object, causing the panel data to be saved into the object. Pressing the Obj Binding button causes objBinding() to be invoked, which produces the following output. [@Person address=>[@Address city=>"Kew", country=>"Australia", postcode=>"3101", state=>"VIC", street=>"2 Smith St"], comment=>"Another comment", dob=>[#1987-0215], firstName=>"Linda", lastName=>"Forbes", occupation=>"Accountant", sex=>"Female", smoker=>false] It’s important to note that the inner Address object here is actually created by JavaGram (as a result of the binding rules) and not the programmer. The Bind to Map and Map Binding buttons, respectively, invoke bindToMap() and mapBinding(). These have the same effect as the corresponding object buttons/methods but use maps for data binding instead. GUI Programming 81 Finally, the Clear button, clears all the panel data by binding the panel to an empty map. An obvious question arises from this discussion: given the choice of objects and maps for data binding, which one is recommended? All things considered, the answer depends on the situation at hand. For example, suppose you have a search panel where you allow the user to specify search criteria using a collection of fields. This is best served by an ad-hoc data structure, so map binding would be ideal. On the other hand, if the search returns a collection of objects (such as products) each of which is already an object, it’s best to use these objects for the data binding of the screen that displays a product. 4.3 Trees Trees are useful for visualizing hierarchical information, where there is parent-child relationship. The root of a tree consists of zero or more nodes, where each node can contain child nodes, and so on. A tree is defined using the <Tree> element and its nodes are defined using the <Node> element. As an example, consider a class that provides a tree view of a task hierarchy, such as how to build a shed. <jag domain="doc/code/chap4"> singleton class TaskTree { <Icon icon image={sys.use("lib/gifs/Hammer.gif")}/> <Tree tree event=treeHandler> <Node title="Building a shed" image={icon}> <Node title="Build foundation" image={icon}> <Node title="Mark base and dig"/> <Node title="Secure wire mesh"/> <Node title="Pour concrete"/> </Node> <Node title="Build frame" image={icon} content="Shed.gif"> <Node title="Measure and cut timber"/> <Node title="Erect and nail walls"/> <Node title="Secure frame to foundation"/> </Node> <Node title="Install roof" image={icon}/> <Node title="Install cladding" image={icon}/> <Node title="Install door" image={icon}/> </Node> </Tree> <Pane.split split divider=150 weight=0.3> <Pane.scroll lay=$west> <Indirect ref={tree}/> </Pane.scroll> <Panel blank lay=$east> <Label title="No Detail"/> </Panel> </Pane.split> <Pane.scroll picView> 82 JavaGram Agile Development <Label picture/> </Pane.scroll> protected void treeHandler (native comp, symbol event) { switch (event) { case $select: int oldDiv @= split.divider; vague node = tree.select; vague content = node@<Node>?.content; if (content instanceof string) { string path = sys.pathConc("doc/code/chap4", content@string); picture.image = sys.use(path); split.east = picView; } else split.east = blank; split.divider = oldDiv; break; case $drill: case $expand: case $collapse: break; } } } </jag> Note how we’ve defined an <Icon> and reused it for specifying the image property of a number of <Node> elements. We could have equally defined the node image properties directly, but this approach is more convenient and efficient, because the image is created only once and subsequently reused. We’ve also used the <Pane.split> element to create a split pane, whose west side contains the tree and whose east side is reserved for displaying the ‘content’ of each tree node. The default content is a blank panel, which simply contains the label “No Detail”. The alternative content is the picView scroll pane, which we’ll later use in the event handler to display an image. The interesting stuff happens in the tree event handling method treeHandler(). A tree can generate four kinds of event: $select is raised when a tree node is selected or deselected. $drill is raised when a tree node is double-clicked. $expand is raised when a parent node is expanded. $collapse is raised when a parent node is collapsed. In this example, we’re only interested in the $select event. Note how one of the tree nodes (Build frame) has a content property. This property can be set to any arbitrary data that we want to associate with a node. For example, in a CRM application, a tree node GUI Programming 83 representing a customer could have its content set to the customer object. We’ve programmed the handling of the $select event such that the currently selected node’s content is displayed in the split pane’s east side. In this example, we’re using an image file name as node content, with the intention that when the node is selected, we’ll display this image in the east side. To do this, the event handler gets the currently selected node using the tree select property. This property is of type native because its underlying object (a tree node) is a Java Swing object. You’ll see this type used extensively when writing GUI code. To get the node’s content property, we use the notation node@<Node>?.content. The latter requires some explanation. Because node is of type native, we must cast it to the correct GUI element type before we can reference any of its properties. We do this using the notation node@<Node>. To get a property, we usually use the dot notation. However, when a node is deselected, node ends up being null, so to guard against this, we use the question-dot (?.) notation instead of the dot notation. This is a JavaGram convenience that you can use when accessing a GUI element’s properties or an object’s members. In other words, vague content = node@<Node>?.content; is equivalent to writing: vague content = node == null ? null : node@<Node>.content; If the content is a string then we treat it as a file name, work out its path, set the image property of picture to it, and set the east side of the split pane to picView (this causes the image to be displayed). Otherwise, we set the east side of split pane to blank. When you change the content of a split pane, it may adjust the divider position to accommodate what you’re displaying. To cancel the effect of this, we get the divider property of the split pane first and restore it last. Adding a tab for TaskTree to our demo app produces the following. 84 JavaGram Agile Development 4.3.1 Using a Tree Model In the above example, we’ve used the direct data approach for specifying the tree’s nodes. You also have the option of using a data model instead. The latter is useful when the tree data is dynamic or so large that we don’t want to pre-create the nodes. For example, if the data is sourced from a database and potentially large, it would be more sensible to use a data model approach. To specify a data model for a tree, set its model property to the name of the tree model method (defined in the same class). Just to illustrate the approach, here is a revised version of the TaskTree class that uses a tree data model. singleton class TaskTree2 { <Icon icon image={sys.use("lib/gifs/Hammer.gif")}/> <Tree tree event=treeHandler model=treeModel> </Tree> <Pane.split split divider=150 weight=0.3> <Pane.scroll lay=$west> <Indirect ref={tree}/> </Pane.scroll> <Panel blank lay=$east> <Label title="No Detail"/> </Panel> </Pane.split> <Pane.scroll picView> <Label picture/> </Pane.scroll> static map<int, map> tasks @= [ 1=>[$name=>"Building a shed", $subs=>[2, 3, 4, 5, 6]] ,2=>[$name=>"Build foundation", $subs=>[7, 8, 9]] GUI Programming 85 ,3=>[$name=>"Build frame", $subs=>[10, 11, 12], $data=>"Shed.gif"] ,4=>[$name=>"Install roof"] ,5=>[$name=>"Install cladding"] ,6=>[$name=>"Install door"] ,7=>[$name=>"Mark base and dig"] ,8=>[$name=>"Secure wire mesh"] ,9=>[$name=>"Pour concrete"] ,10=>[$name=>"Measure and cut timber"] ,11=>[$name=>"Erect and nail walls"] ,12=>[$name=>"Secure frame to foundation"] ]; static int rootId = 1; protected vague treeModel (native tree, symbol cmd, native node, vague subnode) { switch (cmd) { case $count: return node == null ? 1 : sys.length(getSubs(node)); case $get: int id = node == null ? 1 : getSubs(node)[subnode@int]; return getNode(id); case $index: return indexOfSubnode(node, subnode); case $leaf: vector subs = getSubs(node); return subs == null || sys.length(subs) == 0; } return null; } protected vector<int> getSubs (native node) { map content @= node == null ? tasks[rootId] : node@<Node>.content; return (content == null ? null : content[$subs])@vector<int>; } protected native getNode (int id) { map task = tasks[id]; if (task[$node] == null) task[$node] = gui.create($Node, map($title=>task[$name], $content=>task, $image=>icon)); return task[$node]; } protected int indexOfSubnode (native node, native subnode) { if (node != null) { int idx = 0; for (int id in getSubs(node)) { if (tasks[id][$node] == subnode) return idx; ++idx; } } return 0; } protected void treeHandler (native comp, symbol event) { switch (event) { 86 JavaGram Agile Development case $select: int oldDiv @= split.divider; vague node = tree.select; string file @= node@<Node>?.content@map?.$data; if (file != null) { string path = sys.pathConc("doc/code/chap4", file); picture.image = sys.use(path); split.east = picView; } else split.east = blank; split.divider = oldDiv; break; case $drill: case $expand: case $collapse: break; } } } The data that drives the model is denoted by the tasks static map. We’ve modeled this such that it resembles data retrieved from a database. It maps each task ID to its definition. For parent tasks, the latter contains a $subs key that points to a vector of its children task IDs. The tree model is denoted by the treeModel() method, which accepts four possible commands: $count requires the number of children of node to be returned. $get requires the n-th child of node (as denoted by subnode as an integer index) to be returned. $index requires the zero-based index of subnode as a child of node to be returned. $leaf requires true to be returned if node is a leaf node. These are defined using three utility methods, which are self-explanatory, except for getNode(). The latter returns a <Node> for a given task ID. New nodes are created procedurally using the gui.create() method, which takes a GUI element name (as a symbol) and a map that specifies its properties. A newly-created node is cached by storing it in the task definition map under the $node key, so that we don’t need to recreate it every time. Finally, note how we’ve changed our approach to what we store in the content property of each node – the corresponding task definition map. Therefore, treeHandler() is revised accordingly. GUI Programming 87 The above model-based tree produces exactly the same visual result as the earlier direct data version. 4.4 Tables Most applications must deal with tabular data (i.e., data items that conform to the same structure such as objects of the same class, or maps sharing the same keys). A common example is the result of a search. The <Table> element is ideal for visualizing tabular data. Recall the Person class from an earlier section and suppose that we need a table to display persons retrieved from a database. <jag domain="doc/code/chap4"> <load> "doc/code/chap4/PersonPanel" </load> singleton class PersonTable { static final vector<map> TABLE_FORMAT @= [ [$key=>$firstName, $title=>"First Name", $width=>80, $align=>$west] ,[$key=>$lastName, $title=>"Last Name", $width=>100, $align=>$west] ,[$key=>$sex, $title=>"Sex", $width=>40, $align=>$west] ,[$key=>$dob, $title=>"DOB", $width=>80, $align=>$east, $format=>"dd MMM yyyy"] ,[$key=>$occupation, $title=>"Occupation", $width=>60, $align=>$west] ,[$key=>$smoker, $title=>"Smoker", $width=>40, $align=>$center] ]; static vector<Person> persons = [ [@Person firstName=>"John", lastName=>"Smith", sex=>"Male" , dob=>[#1982-12-22], occupation=>"Mechanic", smoker=>true ], [@Person firstName=>"Linda", lastName=>"Forbes", sex=>"Female" , dob=>[#1987-02-15], occupation=>"Accountant", smoker=>false ], [@Person firstName=>"Bob", lastName=>"Smart", sex=>"Male" , dob=>[#1984-07-11], occupation=>"Teacher", smoker=>false ] ]; <Panel panel> <Layout.border/> <Pane.scroll lay=$center> <Table table format={TABLE_FORMAT} data={persons} autoSize=true event=tableHandler/> </Pane.scroll> <Panel lay=$south> <Button title="Dump" action={dump()} enable={canDump()}/> </Panel> </Panel> protected vague tableHandler (native comp, symbol event) { switch (event) { case $select: gui.maintain(panel); 88 JavaGram Agile Development break; case $drill: dump(); break; case $hitCell: break; } return null; } protected void dump () { sys.ppln(persons[table.select@int]); } protected boolean canDump () { return table.select != null; } } </jag> Like trees, tables can accept direct data or a data model. We’ve used the direct data approach, for which we’ve created a static vector of Person objects. In a real application, this data is likely to originate from a data source such as a database. The table’s format property is set to the TABLE_FORMAT vector which specifies the format of each column as a map whose structure should be self-explanatory. In the panel below the table, we’ve created a button for dumping the data for the currently-selected row. We’ve also specified an event handler method for the tree. A tree can raise three kinds of event: $select is raised when a row is selected or deselected. For this event, we’re calling gui.maintain() on the whole panel so that JavaGram can update the visibility of the components. $drill is raised when a row is double-clicked. In response to this event, we dump the row’s object to standard output by calling dump(). $hitCell is raised when a cell is clicked. Adding this to the demo application gives us the following. GUI Programming 89 Note how we’ve used $format for the DOB column to specify a preferred date format. This approach is handy but of limited use. For example, it doesn’t give us a way of changing the format of the Smoker column to use ticks instead of true/false. We’ll present a more general approach shortly. By default, the even and odd rows of a table are drawn with different background colors to aid readability. You can change the row background colors by setting the bgColorOdd and bgColorEven properties. A table can be sorted by any given column. To sort in ascending column order, click on the column heading. To sort by descending column order, shift-click on the column heading. In either case, a small triangle appears in the column heading to indicate the sort order. You can turn off sorting by setting the sortAscend or sortDescend property to null. You can also control the sorting order by right-clicking on a cell or heading and choosing from the resulting popup menu. 4.4.1 Using a Table Model Let’s look at a revised version of the above example to illustrate a few more things – the ability to edit cells, use of a data model, and more elaborate formatting of the cells. singleton class PersonTable2 { static <Field.date dateField/> static <Option.tick smokerField align=$center enable=false/> static <Combo occupCombo data={PersonPanel.OCCUPATIONS}/> static <Icon icon image={sys.use("lib/gifs/Person.gif")}/> static final vector<map> TABLE_FORMAT @= vector( [$key=>$firstName, $title=>"First Name", $width=>70, $align=>$west, $icon=>true] ,[$key=>$lastName, $title=>"Last Name", $width=>80, $align=>$west] ,[$key=>$sex, $title=>"Sex", $width=>40, $align=>$west] ,map($key=>$dob, $title=>"DOB", $width=>80, $align=>$east, 90 JavaGram Agile Development $format=>"dd MMM yyyy", $editor=>dateField) ,map($key=>$occupation, $title=>"Occupation", $width=>80, $align=>$west, $editor=>occupCombo) ,map($key=>$smoker, $title=>"Smoker", $width=>40, $align=>$center, $editor=>smokerField) ); static vector<Person> persons = [ [@Person firstName=>"John", lastName=>"Smith", sex=>"Male" , dob=>[#1982-12-22], occupation=>"Mechanic", smoker=>true ], [@Person firstName=>"Linda", lastName=>"Forbes", sex=>"Female" , dob=>[#1987-02-15], occupation=>"Accountant", smoker=>false ], [@Person firstName=>"Bob", lastName=>"Smart", sex=>"Male" , dob=>[#1984-07-11], occupation=>"Teacher", smoker=>false ] ]; <Panel panel> <Layout.border/> <Pane.scroll lay=$center> <Table table format={TABLE_FORMAT} editable=true autoSize=true styled=true model=tableModel event=tableHandler/> </Pane.scroll> <Panel lay=$south> <Button title="Dump" action={dump()} enable={canDump()}/> </Panel> </Panel> protected vague tableModel (native comp, symbol cmd, int row, int col) { switch (cmd) { case $rows: return sys.length(persons); case $cols: return sys.length(TABLE_FORMAT); case $get: return persons[row][TABLE_FORMAT[col][$key]@symbol]; case $put: persons[row][TABLE_FORMAT[col][$key]@symbol] = table.put; break; case $style: return TABLE_FORMAT[col][$key] == $lastName ? [$bold] : null; case $icon: return TABLE_FORMAT[col][$key] == $firstName ? icon : null; case $sortAscend: case $sortDescend: sys.sort(persons, cmd == $sortAscend, vector(TABLE_FORMAT[col][$key])); break; } return null; } protected vague tableHandler (native comp, symbol event) { switch (event) { GUI Programming 91 case $select: gui.maintain(panel); break; case $drill: dump(); break; case $hitCell: break; } return null; } protected void dump () { int row @= table.hitCell[0]; int col @= table.hitCell[1]; sys.println(tableModel(table, $get, row, col)); } protected boolean canDump () { return table.select != null; } } Here is what we’ve done. We’ve set the editable and styled properties of the table to true, and set its model property to tableModel(). Additionally, we’ve extended the TABLE_FORMAT vector to nominate an editor for the last three columns. This enables the user to directly edit the cells in these columns. A key point to note is how we’ve used map() to specify the format of these columns, because each is referring to a non-literal (e.g., dateField). The interesting stuff happens in tableModel(), which accepts these possible commands: $rows requires the number of table rows to be returned. $cols requires the number table columns to be returned. $get requires the value at the cell denoted by row and col to be returned. $put is required for editable tables and should make the change to a cell permanent. The updated value can be accessed using the put property of the table. $style should return null or a vector of values that specify the font style and/or color of the text in a cell. This has no effect unless the styled property of the table is also set to true. $icon should return null or the icon to be displayed in a cell. This has no effect unless the $icon key of the column format is also set to true. $sortAscend and $sortDescend should sort the underlying data for the model. A minimal data model must implement the $rows, $cols, and $get commands. 92 JavaGram Agile Development Finally, the behavior of an editable table is noticeably different from a read-only table. In the latter, clicking on a row causes the entire row to be selected, whereas in the former, the specific clicked cell is selected. Consequently, the dump() method is revised to behave differently – it writes to standard output the value of a cell, not the entire row! The visual result of the revised table is shown below. 4.4.2 Lists A list is a table that has a single column and no heading. Functionally, however, lists are closer to combo boxes than to tables. Lists are rarely used because combos are just as good and take less real estate. The only situation in which they’re preferred is when you want to select multiple values from the list, which is not possible with combos. There is also a list element that provides a checkbox for each row. This is suitable for situations where the user needs to nominate a number of things from a potentially large set. Here is a class that demonstrates the use of lists. <jag domain="doc/code/chap4"> singleton class ListTest { static final vector<string> CAPITAL_CITIES = [ "Sydney", "Melbourne", "Brisbane", "Perth", "Adelaide", "Canberra", "Hobart", "Darwin" ]; static final vector<list> ACTIVITIES = [ ("Recreation and Sports", true) ,("Sight Seeing", false) ,("Bush Walking", false) ,("Wine Tasting", true) ,("Dining", true) ,("Music and Theatre", true) GUI Programming 93 ]; static final vector<list> LANGUAGES = [ ("English", true) ,("Italian", true) ,("Greek", true) ,("Chinese", true) ,("Vietnamese", false) ,("Japanese", false) ]; <Panel panel> <Layout.horizontal/> <Panel title="Australian Capital Cities"> <Layout.border/> <Pane.scroll lay=$center> <List cities data={CAPITAL_CITIES} multiSelect=true/> </Pane.scroll> <Panel lay=$south> <Button title="Select All" action={cities.selectAll = true}/> <Button title="Dump" action={sys.println(cities.select)}/> </Panel> </Panel> <Panel title="Activities"> <Layout.border/> <Pane.scroll lay=$center> <List.tick acts data={ACTIVITIES}/> </Pane.scroll> <Panel lay=$south> <Button title="Tick All" action={tickAll(acts, true)}/> <Button title="Untick All" action={tickAll(acts, false)}/> <Button title="Dump" action={sys.println(ACTIVITIES)}/> </Panel> </Panel> <Panel title="Languages"> <Layout.border/> <Pane.scroll lay=$center> <List.tick langs model=langModel/> </Pane.scroll> <Panel lay=$south> <Button title="Tick All" action={tickAll(langs, true)}/> <Button title="Untick All" action={tickAll(langs, false)}/> </Panel> </Panel> </Panel> protected void tickAll (native lt, boolean tick) { lt@<List.tick>.tickAll = tick; lt@<List>.refresh = true; } protected vague langModel (native comp, symbol cmd, int idx) { switch (cmd) { case $count: return sys.length(LANGUAGES); 94 JavaGram Agile Development case $get: return LANGUAGES[idx][0]; case $tick: return LANGUAGES[idx][1] == true; case $toggle: return LANGUAGES[idx][1] = !LANGUAGES[idx][1]@boolean; } return null; } } </jag> The class uses a horizontal layout to display three lists side by side. The first list is defined using the <List> element and displays the Australian capital cities. The second list is created using the <List.tick> element and displays a list of tourist activities. Both these lists are defined using direct data. Note that the expected data format for a tick-list is a vector of lists, where each list consists of a string and a boolean. The latter controls the tick state of the item (true means ticked). The last list demonstrates the use of a data model, implemented by the method langModel(). The visual result of this class is shown below. 4.5 Grids One limitation of tables is that all rows need to conform to the same structure (i.e., share the same columns). Some user interface scenarios are better served by allowing different rows to be structured differently. Grids offer this flexibility, as well as the ability to specify dependencies between the cells. Conversely, grids lack some of the strengths of tables – you can’t use a data model because there is no uniform row structure and, GUI Programming 95 consequently, it would be impractical to use grids for very large data sets. The choice, therefore, involves tradeoffs. As an example, the following class uses a grid to implement an online shopping cart. A grid is defined using the <Grid> element, within which you can define <Column> and <Row> elements. The purpose of a <Column> element is to specify the properties of a column, whereas a <Row> element defines a visible row in terms of <Cell> elements that define the data to be displayed inside the cells. Usually, the same property can be defined at cell, column, or row level. If a property is not explicitly defined for a cell, then it’s inherited from the column, the row, or a default cell (in that order). <jag domain="doc/code/chap4"> singleton class CartGrid { static final string HEAD_BG = "0xCCCCCC"; static final string HEAD_FG = "0x990000"; <Font bold style=$bold/> <Grid grid> <Column description size=200 kind=$text lock=true/> <Column quantity size=50 format="0,000" align=$east kind=$number/> <Column weight size=50 format="0,000.00kg" align=$east kind=$number lock=true/> <Column price size=70 format="$0,000.00" align=$east kind=$number lock=true/> <Column delete size=50 align=$center lock=true/> <Row lock=true bgColor={HEAD_BG} fgColor={HEAD_FG}> <Cell value="Shopping Cart" align=$center colSpan=4 font={bold}/> </Row> <Row lock=true bgColor={HEAD_BG} fgColor={HEAD_FG}> <Cell value="Items in Cart" /> <Cell value="Quantity"/> <Cell value="Weight"/> <Cell value="Price"/> </Row> <Row lock=true> <Cell value="Add Item" border=0 link={Catalog.singleton.chooseProducts()}/> <Cell value="Shipping:" border=0/> <Cell value=0/> <Cell calc={shippingCost()}/> </Row> <Row lock=true> <Cell value="Checkout" border=0 link={checkOut()}/> <Cell value="Total:" border=0/> <Cell calc={totalColumn(2)}/> <Cell calc={totalColumn(3)}/> </Row> </Grid> public void addItems (vector<Product> prods) { for (Product prod in prods) { int idx = grid.rows - 2; GridRow row = new GridRow(this, prod); grid += list(row.row, idx); } 96 JavaGram Agile Development grid.recalc = true; } void deleteRow () { int row = grid@<Grid>.hitCell[0]; grid -= row; grid.recalc = true; } protected real totalColumn (int col) { real total = 0.0; for (int row = 2, n = grid.rows - 1; row < n; ++row) { vague val = gui.getCell(grid, row, col)@<Cell>.value; if (val instanceof real) total += val@real; } return total; } protected real shippingCost () { return totalColumn(2) * 5.5; } protected void checkOut () { //gui.toHtml(grid); } } </jag> The properties used in this example are: size specifies the width of a column or cell. format specifies the format for displaying values within a cell. align specifies how a cell’s value is to be aligned. kind specifies the kind of value to be displayed in a cell, and must be one of: $text, $textArea, $combo, $number, $date, $time, or $tick. lock allows a cell to be locked so that its value can’t be changed. fgColor and bgColor control, respectively, the foreground and background color of a cell. value denotes the actual value displayed in a cell. rowSpan and colSpan control the vertical and horizontal span of a cell (both default to 1). font specifies the preferred font for displaying a cell’s value. border specifies the thickness of a cell’s border line. link specifies the action for a ‘hot’ cell (the cell value is underlined and when clicked causes the link action to execute). GUI Programming 97 calc specifies a formula for automatically calculating the value of a cell. Here is what this grid looks like on the screen. Note how the Add Item and Checkout cells are linked to the addItem() and checkout() methods. Also note how the totalColumn() method is used by the calc property of the last two cells. This method uses gui.getCell() to access the value of the cells above it, and adds them up to dynamically update the totals. Certain events (e.g., the user editing a cell’s value) cause JavaGram to automatically process all calc properties in a grid. This can also be done programmatically by setting the grid’s recalc property to true, as done by the addItem() method. JavaGram performs a recalculation of a grid by processing the cells left to right and top to bottom. Therefore, calc properties may contain backward but not forward cell references. The intended behavior for the Add Item link is to display another window, listing the available products for purchase, from which the user can make selections. These selections are then added as rows above the Add Item row. CartGrid.addItem() uses three other classes. Product is a simple class for representing purchasable products. class Product { protected getable string id; protected getable string name; protected getable real weight; protected getable real price; public string format () { return $"{id} {name} @${price}"; } } GridRow is a simple class for adding a new grid row that represents a product added to the cart. 98 JavaGram Agile Development class GridRow { static final string INPUT_BG = "0xFFFFFF"; CartGrid cart; Product product; delayed <Row row> <Cell value={product.format()}/> <Cell quant value=1 bgColor={INPUT_BG}/> <Cell calc={quant.value@int * product.getWeight()}/> <Cell calc={quant.value@int * product.getPrice()}/> <Cell value="Delete" link={cart.deleteRow()}/> </Row> public GridRow (CartGrid cart, Product product) { this.cart = cart; this.product = product; } public native getRow() { return row; } } The constructor records the grid to which the row is to be added and the product that it represents. Note how the row is defined as a delayed class member. The reason is that some of the row’s cell properties refer to class fields, such as cart and product. It’s therefore vital that the row is initialized after the constructor has set these fields to valid values. The third and fourth cell use calc properties to, respectively, calculate the product weight and price, based on the purchase quantity. The last cell has a link property that allows the row to be deleted. Finally, the Catalog class displays a window that lists the available products for the user to choose from. The products are displayed in a table, for which we’ve setup some dummy data. Note how the multiSelect property of table is set to true to allow the user to select multiple rows (by holding the control or shift key down while clicking). This window is created using a <Dialog> element (dialogs are described in the next section). singleton class Catalog { vector<Product> selected; <Dialog dialog title="Choose a Product" parent={CartGrid.singleton.grid} width=300 height=200> <Layout.border/> <Pane.scroll lay=$center> <Table table data={PRODUCTS} format={FORMAT} autoSize=false multiSelect=true event=tableHandler/> </Pane.scroll> <Panel lay=$south> GUI Programming 99 <Button title="OK" action={ok()} enable={canOk()}/> <Button title="Cancel" action={cancel()}/> </Panel> </Dialog> protected vague tableHandler (native comp, symbol event) { switch (event) { case $select: gui.maintain(dialog); break; case $drill: ok(); break; } return null; } protected void cancel () { selected = null; dialog.show = false; } protected void ok () { selected @= vector(); for (int idx in table.select@vector<int>) sys.append(selected, PRODUCTS[idx]); dialog.show = false; CartGrid.singleton.addItems(selected); } protected boolean canOk () { return table.select != null; } public vector<Product> chooseProducts () { table.select = null; gui.maintain(dialog); dialog.show = true; return selected; } static vector<map> FORMAT = [ [$key=>$id, $title=>"ID", $width=>50, $align=>$west] ,[$key=>$name, $title=>"Name", $width=>150, $align=>$west] ,[$key=>$weight, $title=>"Weight", $width=>60, $align=>$east, $format=>"0.00kg"] ,[$key=>$price, $title=>"Price", $width=>80, $align=>$east, $format=>"$0,000.00"] ]; static vector<Product> PRODUCTS = [ [@Product id=>"EG001", name=>"Portable CD Player", weight=>0.12, price=>120.95] ,[@Product id=>"EG002", name=>"Plasma TV", weight=>28.45, price=>2199.0] ,[@Product id=>"EG003", name=>"Coffee Maker", weight=>6.8, price=>175.99] ]; } The chooseProducts() method is the main entry point, which displays the dialog and once the user presses the OK button, returns a vector of products that the user has selected. 100 JavaGram Agile Development We’ve defined the enable property of the OK button such that the button is enabled only when the user has selected at least one row from the table. Here is what the dialog looks like. Referring back to the CartGrid.addItem() method, it calls chooseProducts() and iterates through the returned vector, adding a grid row for each product. A row is added by creating an instance of GridRow and then adding the row to the grid using the notation grid += list(row.row, idx); The right side of this assignment is a list of two elements: a grid row and a zero-based row index. This causes the row to be added to the grid at the nominated row position. Choosing the first two products from the list and setting the quantity of the first row to 2 produces the following. Now, have a look at the method CartGrid.deleteRow(). This method is invoked when the user clicks the Delete hot link of the last cell of a product. It removes the row using the following notation: grid -= grid@<Grid>.hitCell[0]; GUI Programming 101 hitCell is a grid property that denotes the position of the last clicked cell. It’s a list of the form $(row, col). We’ve used it here to determine the row of the clicked cell. If you experiment with this example, you’ll discover that a grid’s border lines can be dragged using the mouse to adjust the row heights and column widths. This can be disabled by setting the fixedWidth and fixedHeight properties of the grid to true. You can define a grid event handler in the same way as we did earlier for tables. However, there was no need for one in this example. You can also define an edit handler for a grid by setting its edit property to the name of a method in the same class. This method must have the following signature. vague editHandler (native grid, int row, int col, vague oldVal, vague newVal) The edit handler is invoked when the user changes the value of a cell. The last four parameters identify the cell and its old and new value. This method should either return the final desired value for the cell, or null (if the change is to be rejected due to, for example, it failing validation requirements). Edit handlers are usually used for complex validation purposes, beyond what can be achieved by cell properties such as min and max. 4.6 Component Status Most GUI components can assume different states, such as editable/read-only, enabled/disabled, visible/invisible. By default, a component is editable, enabled, and visible. To make a text component, numeric component, or combo read-only, set its readOnly property to true. The enable and visible properties of a component can be defined as boolean expressions. These expressions are evaluated by JavaGram at specific points in time to determine if a component should be enabled/visible. A disabled component is dimmed. An invisible component is completely hidden as if it doesn’t exist. Here is a sample class that illustrates these properties: <jag domain="doc/code/chap4"> singleton class Status { static <Field.text nameEditor/> static vector<string> STATES = ["ACT", "NT", "NSW", "QLD", "SA", "TAS", "VIC", "WA"]; static final vector<map> TABLE_FORMAT @= vector( [$key=>$firstName, $title=>"First Name", $width=>80, $align=>$west] ,map($key=>$lastName, $title=>"Last Name", $width=>100, $align=>$west, $editor=>nameEditor) ,[$key=>$sex, $title=>"Sex", $width=>40, $align=>$west] ); static vector<map> persons = [ [$firstName=>"John", $lastName=>"Smith", $sex=>"Male"], [$firstName=>"Linda", $lastName=>"Forbes", $sex=>"Female"], [$firstName=>"Bob", $lastName=>"Smart", $sex=>"Male"] ]; 102 JavaGram Agile Development static final string HEAD_BG = "0xCCCCCC"; static final string HEAD_FG = "0x990000"; <Pane.tabbed tabs align=$west> <Tab title="Status (1)" image={sys.use("lib/gifs/Status.gif")}> <Panel> <Layout.gridBag/> <Lay row=0 col=0 weight=0.0 fill=$horizontal margin=2> <Label title="Enabled" image={sys.use("lib/gifs/Info.gif")}/> </Lay> <Lay row=0 col=1 weight=0.0 fill=$horizontal margin=2> <Label title="ReadOnly" image={sys.use("lib/gifs/Info.gif")}/> </Lay> <Lay row=0 col=2 weight=0.0 fill=$horizontal margin=2> <Label title="Disabled" image={sys.use("lib/gifs/Info.gif")} enable=false/> </Lay> <Lay row=1 col=0 weight=0.0 fill=$horizontal margin=2> <Field.text value="Sample"/> </Lay> <Lay row=1 col=1 weight=0.0 fill=$horizontal margin=2> <Field.text value="Sample" readOnly=true/> </Lay> <Lay row=1 col=2 weight=0.0 fill=$horizontal margin=2> <Field.text value="Sample" enable=false/> </Lay> <Lay row=2 col=0 weight=0.0 fill=$horizontal margin=2> <Field.date value={sys.date()}/> </Lay> <Lay row=2 col=1 weight=0.0 fill=$horizontal margin=2> <Field.date value={sys.date()} readOnly=true/> </Lay> <Lay row=2 col=2 weight=0.0 fill=$horizontal margin=2> <Field.date value={sys.date()} enable=false/> </Lay> <Lay row=3 <Combo </Lay> <Lay row=3 <Combo </Lay> <Lay row=3 <Combo </Lay> col=0 weight=0.0 fill=$horizontal margin=2> data={STATES}/> col=1 weight=0.0 fill=$horizontal margin=2> data={STATES} readOnly=true/> col=2 weight=0.0 fill=$horizontal margin=2> data={STATES} enable=false/> <Lay row=4 col=0 weight=0.0 fill=$horizontal margin=2> <Option.tick title="Smoker" value=true/> </Lay> GUI Programming 103 <Lay row=4 col=2 weight=0.0 fill=$horizontal margin=2> <Option.tick title="Smoker" value=true enable=false/> </Lay> <Lay row=5 col=0 weight=0.0 fill=$horizontal margin=2> <Option.radio title="Smoker" value=true/> </Lay> <Lay row=5 col=2 weight=0.0 fill=$horizontal margin=2> <Option.radio title="Smoker" value=true enable=false/> </Lay> <Lay row=6 col=0 weight=0.0 fill=$horizontal margin=2> <Button title="Button" image={sys.use("lib/gifs/Person.gif")}/> </Lay> <Lay row=6 col=2 weight=0.0 fill=$horizontal margin=2> <Button title="Button" image={sys.use("lib/gifs/Person.gif")} enable=false/> </Lay> <Lay row=7 col=0 weight=0.0 fill=$horizontal margin=2> <Button.toggle title="Toggle" image={sys.use("lib/gifs/Person.gif")}/> </Lay> <Lay row=7 col=2 weight=0.0 fill=$horizontal margin=2> <Button.toggle title="Toggle" image={sys.use("lib/gifs/Person.gif")} enable=false/> </Lay> <Lay row=8 col=0 weight=0.0 fill=$horizontal margin=2> <List data=["Male", "Female"]/> </Lay> <Lay row=8 col=2 weight=0.0 fill=$horizontal margin=2> <List data=["Male", "Female"] enable=false/> </Lay> <Lay row=9 col=0 weight=0.0 fill=$horizontal margin=2> <Area.text value="Sample text\nNext line"/> </Lay> <Lay row=9 col=1 weight=0.0 fill=$horizontal margin=2> <Area.text value="Sample text\nNext line" readOnly=true/> </Lay> <Lay row=9 col=2 weight=0.0 fill=$horizontal margin=2> <Area.text value="Sample text\nNext line" enable=false/> </Lay> </Panel> </Tab> <Tab title="Status (2)" image={sys.use("lib/gifs/Status.gif")}> <Panel> <Layout.gridBag/> <Lay row=0 col=0 weight=0.0 fill=$horizontal margin=2> <Label title="Enabled" image={sys.use("lib/gifs/Info.gif")}/> </Lay> 104 JavaGram Agile Development <Lay row=0 col=1 weight=0.0 fill=$horizontal margin=2> <Label title="Disabled" image={sys.use("lib/gifs/Info.gif")} enable=false/> </Lay> <Lay row=1 col=0 weight=0.0 fill=$horizontal margin=2> <Slider min=0 max=10 value=5/> </Lay> <Lay row=1 col=1 weight=0.0 fill=$horizontal margin=2> <Slider min=0 max=10 value=5 enable=false/> </Lay> <Lay row=2 col=0 <ProgressBar </Lay> <Lay row=2 col=1 <ProgressBar </Lay> weight=0.0 fill=$horizontal margin=2> title="Progress" min=0 max=10 value=5/> weight=0.0 fill=$horizontal margin=2> title="Progress" min=0 max=10 value=5 enable=false/> <Lay row=3 col=0 weight=0.0 fill=$horizontal margin=2> <Panel title="Panel"> <Label title="Name:"/> <Field.text value="Name"/> </Panel> </Lay> <Lay row=3 col=1 weight=0.0 fill=$horizontal margin=2> <Panel title="Panel" enable=false> <Label title="Name:"/> <Field.text value="Name"/> </Panel> </Lay> <Lay row=4 col=0 weight=0.0 fill=$both margin=2> <Tree> <Node title="One" image={sys.use("lib/gifs/Question.gif")} expand=true> <Node title="Two" image={sys.use("lib/gifs/Question.gif")}/> </Node> </Tree> </Lay> <Lay row=4 col=1 weight=0.0 fill=$both margin=2> <Tree enable=false> <Node title="One" image={sys.use("lib/gifs/Question.gif")} expand=true> <Node title="Two" image={sys.use("lib/gifs/Question.gif")}/> </Node> </Tree> </Lay> <Lay row=5 col=0 weight=0.0 fill=$both margin=2> <Table format={TABLE_FORMAT} data={persons} editable=true/> GUI Programming 105 </Lay> <Lay row=5 col=1 weight=0.0 fill=$both margin=2> <Table format={TABLE_FORMAT} data={persons} editable=true enable=false/> </Lay> <Lay row=6 col=0 weight=0.0 fill=$horizontal margin=2> <Grid> <Column description1 size=100 kind=$text lock=true/> <Column quantity1 size=50 format="0,000" align=$east kind=$number/> <Row lock=true bgColor={HEAD_BG} fgColor={HEAD_FG}> <Cell value="Items in Cart" /> <Cell value="Quantity"/> </Row> <Row> <Cell value="Television" /> <Cell value=10/> </Row> </Grid> </Lay> <Lay row=6 col=1 weight=0.0 fill=$horizontal margin=2> <Grid enable=false> <Column description2 size=100 kind=$text lock=true/> <Column quantity2 size=50 format="0,000" align=$east kind=$number/> <Row lock=true bgColor={HEAD_BG} fgColor={HEAD_FG}> <Cell value="Items in Cart" /> <Cell value="Quantity"/> </Row> <Row> <Cell value="Television" /> <Cell value=10/> </Row> </Grid> </Lay> </Panel> </Tab> <Tab title="Disabled" image={sys.use("lib/gifs/Status.gif")} enable=false> <Panel/> </Tab> </Pane.tabbed> } </jag> The first two sub-tabs of Status tab illustrate the appearance of normal, read-only, and disabled components. The third sub-tab illustrates the appearance of a disabled tab. 106 JavaGram Agile Development 4.7 Dialogs and Alerts Dialogs and alerts are windows that are displayed as a result of the user performing an action that requires further information to be collected, or something brought to the attention of the user. Alerts are in fact very simple, predefined dialogs that give the user very limited options, such as a ‘yes’ or ‘no’ response to a question. 4.7.1 Dialogs A dialog is a window that can be displayed or hidden at will during user interaction with an application. The content of a dialog is defined by the programmer, much in the same way as a frame. GUI Programming 107 Dialogs are defined using the <Dialog> element. We saw an example of this in the Catalog class defined in the previous section. Here is another example that defines a dialog for specifying user preferences. <jag domain="doc/code/chap4"> <load> "lib/gui/GuiUtil" </load> singleton class Dialogs { <Dialog dial title="Preferences" parent={GuiUtil.getMainFrame()} width=200 height=130> <Layout.border/> <Panel prefs> <Layout.vertical/> <Option.tick key=$save title="Save changes before existing"/> <Option.tick key=$duplicates title="Allow duplicates"/> <Option.tick key=$validate title="Validate transactions"/> </Panel> <Panel lay=$south> <Button title="OK" action={ok()}/> <Button title="Cancel" action={dial.show = false}/> </Panel> </Dialog> } </jag> It displays the following dialog. Like a frame, a dialog can take properties such as title, width, and height. It’s also very common for a dialog to have one or more buttons. Once the user has finished their interaction with the dialog, they press one of these buttons to dismiss the dialog. A dialog can assume one of two display modes: modal or modeless. Once displayed, a modal dialog limits the user’s interaction to that dialog, making the rest of the application inaccessible, until the dialog is dismissed. A modeless dialog, on the other had, leaves the user free to move back and forth between the dialog and the rest of the application. The mode of a dialog is controlled by the modal property, which can be set to true or false (defaults to true). Two other useful dialog properties are parent and center. The former nominates the dialog’s parent. This can be either a frame or another dialog (defaults to null, meaning 108 JavaGram Agile Development that the dialog has no parent). It’s generally a good idea to always give a modal dialog a valid parent, so that when the application is brought to the front, a visible modal dialog pops to the front of its parent. The center property is usually also set to the parent, causing the dialog to be centered relative to the parent. For convenience, a dialog can be treated as a panel, so you don’t need to define a toplevel panel for it. As illustrated by the above example, we’ve defined a border layout for this implicit panel. 4.7.2 Alerts Occasionally, an application needs to alert the user about something, such as displaying a warning. For example, if the user attempts to save a file and this operation fails for some reason (such as the file being read-only) then the user should be alerted about this failure. Here is a simple alert that warns the user about a save operation failing. <Alert failed title="Failed" message="Can't save read-only file!" kind=$warning owner={GuiUtil.getMainFrame()}/> The kind property may be set to one of: $error (for displaying an error message) $info (for displaying feedback information) $warning (for displaying a warning message) $question (for asking a question) $plain (for any other purpose) The icon displayed inside the alert is dependent on the value of the kind property. The alert is displayed by referring to its show property. This is a read-only value, which returns the zero-based index of the button the user presses to dismiss the dialog (or null for an alert with just one button). The above alert produces the following window. Here is another alert that asks a question. GUI Programming 109 <Alert ask title="Save?" message="Do you want to save your changes?" kind=$question options=["Yes", "No", "Cancel"] enter=0 owner={GuiUtil.getMainFrame()}/> The options property may be set to a vector of up to three strings, which specify the titles of the buttons to be displayed in the alert. The show property returns the zero-based index of the button pressed by the user. The enter property may be set to the index of the default button, so that the user can dismiss the alert by simply pressing the enter key. The above alert displays the following window. Here, for example, if the user presses the No button ask.show returns 1. The Yes/No/Cancel alert is so commonly used that there is a special element for it, so that you don’t need to specify the buttons. The following is equivalent to the above alert. <Alert.ync ask title="Save?" message="Do you want to save your changes?" enter=0 owner={GuiUtil.getMainFrame()}/> The <Alert> element is useful when you’re likely to reuse the alert. For more ad hoc purposes, it’s easier to use the sys.alert() method. For example, gui.alert("Save?", "Do you want to save your changes?", GuiUtil.getMainFrame(), $question) is equivalent to the above alert, but returns a symbol that denotes the pressed button (i.e., one of: $ok, $yes, $no, $cancel). However, gui.alert() is more limited than <Alert> in that, for a question alert, you can’t customize the buttons. 4.7.3 File Chooser Occasionally, an application may need to ask the user to nominate one or more files or a directory for the task at hand. For example, if the user selects a command to save something to a file, the application may ask the user to nominate a directory for the file to be saved to. JavaGram provides a predefined dialog for this purpose which can be accessed using the <FileChooser> element. Here is an example: <FileChooser fc owner={GuiUtil.getMainFrame()} kind=$open title="Choose file(s)" path="C:/JavaGram/" ext=["doc","zip"] 110 JavaGram Agile Development multiSelect=true desc="DOC and ZIP files" label="Do it"/> The kind property may be set to $open (for selecting files for opening) or $save (for selecting files for saving). The dialog doesn’t actually open or save files, but rather this property denotes the intent. The ext property can be set to a vector of permissible file extensions. The multiSelect property controls whether selection should be limited to one or multiple items. As with alerts, the dialog is displayed by accessing its show property, which subsequently returns the selection result, which may be a path (if multiSelect is false), a vector of paths (if multiSelect is true), or null (if nothing is selected). The above example shows the following dialog. 4.7.4 Color Chooser To allow the user to choose a color in an application (e.g., the background color of a report to be generated), use the <ColorChooser> element. Here is an example: <ColorChooser cc owner={GuiUtil.getMainFrame()} title="Choose a color" color="0xAABBFF"/> The color property may be used to nominate the default color, in which case, this color becomes the initial selection when the dialog is displayed. As before, the dialog is displayed by accessing its show property, which subsequently returns the selected color as an integer value, or null if nothing is selected. GUI Programming 111 4.8 Graphs Graphs allow you to visualize numeric data, and are especially useful when you want to present a summary of a potentially large data set to the user. 4.8.1 Pie Charts The simplest type of graph is a pie chart, where a set of numbers (a single series) is presented as a circle divided into ‘pie slice’ segments. Each segment’s size is proportional to the number it represents. A pie chart is defined using the <Graph.pie> element. The graph data can be specified either directly or through a data model. Here is an example that illustrates both styles. <jag domain="doc/code/chap4"> singleton class Graphs { protected vector<map> pieData = [ [$value=>25, $title=>"Software", $color=>"0x77FF77"], [$value=>15, $title=>"Hardware", $color=>"0x7777FF"], [$value=>35, $title=>"Maintenance", $color=>"0xFF7777"] ]; <Pane.tabbed pane align=$south> <Tab title="Pie direct"> <Panel> <Layout.border/> <Graph.pie legend=true legendLay=$south format="0" data={pieData} event=handler/> </Panel> </Tab> <Tab title="Pie model"> <Panel> <Layout.border/> <Graph.pie legend=true legendLay=$east format="0" 112 JavaGram Agile Development model=pieModel event=handler/> </Panel> </Tab> </Pane.tabbed> protected vague pieModel (native pie, symbol cmd, int idx) { switch (cmd) { case $count: return sys.length(pieData); case $value: return pieData[idx][$value]; case $title: return pieData[idx][$title]; case $color: return pieData[idx][$color]; } return null; } protected void handler (native comp, symbol event) { sys.println(gui.tag(comp), " event: ", event); } } </jag> Here is what the pie graph looks like. When using the direct data approach, the data must be a vector of maps, where each map has three keys: $value (the data value). $title (the title of the pie segment). $color (the color of the pie segment). GUI Programming 113 The data model approach specifies the data model as a method in the same class that accepts the above keys as commands, as well as a $count command that returns the number of data values. A pie chart may optionally have a legend. To display the legend, set the legend property to true. The legend may be positioned south or east of the pie chart. This is controlled by the legendLay property. You can also specify a format property to control the way the segment values are formatted. To detect user events, you can specify an event handler. The one we’ve specified in this example, simply prints out the graph type and the event. We’ll reuse this handler for other graphs defined in this section. Another useful pie chart property is drillable. When set to true, the pie segment under the mouse pointer is highlighted and when the user clicks on the pie segment, a $drill event is generated. The program can respond to this by, for example, displaying detailed information for the pie segment. 4.8.2 Line Graphs A line graph is useful for displaying one or more series of data on a two-dimensional plane, where each series consists of a sequence of samples. The graph has two axes: the x-axis represents the sample reference points, and the y-axis the sample values. For example, the x-axis may represent dates and the y-axis, a city’s average temperature for each day. If this data is collected for three cities, then we’ll have three series – one for each city. A line graph is defined using the <Graph.line> element. As before, the graph data can be specified either directly or through a data model. Here is an extension of the earlier Graphs class that uses the direct data approach. singleton class Graphs { //... protected vector<vector> graphData = [ [10, 20, 13.2, 100, 87, 52.7, 11, 28, 50], [89, 2, 872, 883, 22, 91, 263, 228, 127, 399, 222] ]; protected vector<map> graphStyles = [ [$title=>"Age", $color=>"0x77FF77", $dash=>0, $weight=>1, $marker=>$triangle, $fill=>true, $label=>true], [$title=>"Income", $color=>"0x7777FF", $dash=>4, $weight=>1, $marker=>$diamond, $fill=>false, $east=>true, $type=>$line] ]; <Pane.tabbed pane align=$south> //... <Tab title="Graph direct"> <Panel> 114 JavaGram Agile Development <Layout.border/> <Graph.line bgColor="0xFFFFFF" legend=true legendLay=$south editable=true xFormat="0.0" style={graphStyles} data={graphData} event=handler xTitle="Age" yTitle="Icome" xTitle="Age" yTitle="Icome"/> </Panel> </Tab> </Pane.tabbed> //... } A line graph’s direct data is a vector of the data for each series, which is itself a vector of values. Style information is specified using the style property, which must be a vector of maps. Each map specifies the style of its corresponding series. The appearance of our sample line graph is shown below. You can make a line graph editable by setting its editable property to true. As a result, each of the sample points can be dragged by the mouse up or down to change its y-value. The x-values cannot be directly changed. 4.8.3 Bar and Stack Graphs A bar graph is essentially the same as a line graph, except that each y-value is displayed as a vertical bar. All other graph properties are as before. Here is an example, which is equivalent to the line graph described earlier, but is specified using the <Graph.bar> element and uses a data model instead of direct data. GUI Programming 115 <Tab title="Bar & Line Graph"> <Panel> <Layout.border/> <Graph.bar bgColor="0xFFFFFF" legend=true legendLay=$east editable=true xFormat="0.0" style={graphStyles} bands={graphBands} model=graphModel event=handler xTitle="Age" yTitle="Icome"/> </Panel> </Tab> The data model is defined as: protected vague graphModel (native pie, symbol cmd, int series, int idx, real val) { switch (cmd) { case $series: return sys.length(graphData); case $samples: return sys.length(graphData[series]); case $key: return idx; case $value: return graphData[series][idx]; case $putNext: graphData[series][idx] = val; break; } return null; } The $putNext command in the data model is needed only for an editable graph. It’s called when the user drags a bar’s top edge up or down (or a line graph’s control point up or down). Two other related commands are $putable (which can be used to decide whether a graph is editable) and $putLast (which is invoked when the user completes an edit by releasing the mouse button). The resulting bar chart is shown below. 116 JavaGram Agile Development There is also a variant of the bar graph (defined using the <Graph.stack> element) that stacks the bars for each x-axis value on top of each other. Graph elements accept many more properties (see Chapter 11 for details). Some of these properties can be set by right clicking on a graph and selecting from the resulting popup menu. 4.8.4 Bubble Graph A bubble graph is a variant of line graph where the data points are displayed as bubbles (solid circles). Bubble graphs are not editable. Whereas (for each series) a line graph specifies a y-value for a given x-value, a bubble graph specifies a y-value as well as a bubble size and an optional bubble color for a given x-value. Bubble graphs are useful for visualizing multi-faceted data. For example, a line graph may be used to show the GDP (y-axis) of countries (series) over a 10 year period (x-axis). A bubble graph can represent the same, but also show the average per-capita income (bubble size), and the trade balance (bubble color) of each country. The latter can range from green (trade surplus) to red (trade deficit). You should always use a data model (not direct data) for a bubble graph. protected vector<map> bubbleStyles = [ [$title=>"Age", $color=>"0x77FF77", $label=>true], [$title=>"Income", $color=>"0x7777FF", $weight=>0, $east=>true] ]; <Tab title="Bubble Graph"> <Panel> <Layout.border/> GUI Programming 117 <Graph.bubble bgColor="0xFFFFFF" legend=true legendLay=$east xFormat="0.0" style={bubbleStyles} model=graphModel event=handler xTitle="Age" yTitle="Icome"/> </Panel> </Tab> The data model is defined as (note the use of $bubble and $bubbles in the data model): vector<map> BUBBLE_LEGEND = [ [$color=>"#FF0000", $title=>"Deficit"], [$color=>"#00FF00", $title=>"Credit"], [$color=>"#0000FF", $title=>"Balanced Account"] ]; protected vague graphModel (native graph, symbol cmd, int series, int idx, real val) { switch (cmd) { case $series: return sys.length(graphData); case $samples: return sys.length(graphData[series]); case $key: return idx; case $value: return graphData[series][idx]; case $bubble: return bubbleData[series][idx]; case $bubbles: return BUBBLE_LEGEND; case $putNext: graphData[series][idx] = val; break; case $label: return sys.format(graphData[series][idx], "0%"); } return null; } 118 JavaGram Agile Development 4.8.5 Pipe Graph A pipe graph is a graph where data is represented by horizontal pipes, and is especially useful for displaying heat maps. Each pipe may consist of zero or more segments, where the segments are typically drawn in different colors. A pipe graph has a single horizontal axis (x-axis). Pipe graphs are not editable. <Tab title="Pipe Graph"> <Panel bgColor="0xFFFFFF"> <Layout.border/> <Pane.scroll horizontal=false border=$empty> <Graph.pipe bgColor="0xFFFFFF" title="Sample Pipe Graph" legend={pipeLegend} legendLay=$east pipeTitlePos=$north grid=true minValue=0 xTitle="Weight" xFormat="0.0%" model=pipeModel event=pipeHandler/> </Pane.scroll> </Panel> </Tab> The data model and event handler are defined as: protected vague pipeModel (native comp, symbol cmd, int row, int segment) { switch (cmd) { case $count: return row == null ? sys.length(pipeData) : sys.length(pipeData[row]) - 1; case $value: return pipeData[row][segment + 1]@map[$value]; case $color: return pipeData[row][segment + 1]@map[$color]; case $title: return segment == null ? pipeData[row][0] : pipeData[row][segment + 1]@map[$title]; } return null; } protected void pipeHandler (native comp, symbol event) { sys.println(gui.tag(comp), " event: ", event); sys.println(comp@<Graph.pipe>.select); } protected vector<vector> pipeData = [ ["First Bar" , [$value=>20.5, $color=>"0xFF0000", $title=>"A1"] , [$value=>70.5, $color=>"0x0000FF", $title=>"A2"] , [$value=>30.5, $color=>"0x00FF00", $title=>"A3"] ] , ["Second Bar" , [$value=>55.2, $color=>"0xFF0000", $title=>"B1"] , [$value=>86.1, $color=>"0x0000FF", $title=>"B2"] ] , ["Third Bar google" , [$value=>29, $color=>"0xFF0000", $title=>"C1"] , [$value=>45.4, $color=>"0x0000FF", $title=>"Cg2"] , [$value=>65, $color=>"0xFF0000", $title=>"C3"] , [$value=>12, $color=>"0x0000FF", $title=>"C4"] GUI Programming 119 ] ]; 4.8.6 Gauge and Range Bar A gauge is a visual meter, similar to those used in a car dashboard. Use this component to visualize a value and color-code its bands. The gauge value (denoted by its needle) can be accessed via the value property. A range bar is like a slider: it has a minimum and a maximum value, but its thumb denotes two values: start and finish, both of which fall in between min and max, and start is always less than finish. The thumb size is proportional to the ‘distance’ between start and finish. <Tab title="Gauge/Range"> <Panel> <Layout.border/> <Slider gaugeSlider lay=$north min=0 max=100 value=75 action={gaugeSliderAdjusted()}/> <Gauge gauge lay=$center title="Sample Gauge" fgColor="0x0000DD" bgColor="0xFFFFFF" data={gaugeData} value=25/> <RangeBar range lay=$south align=$south pause=1000 min=0 max=5 start=1 finish=3 step=1 fgColor="0x0000DD" bgColor="0xFFFFFF" format="0.00%" action={sys.println("dragged")}/> </Panel> </Tab> protected void gaugeSliderAdjusted () { gauge.value = gaugeSlider.value; 120 JavaGram Agile Development gauge.refresh = true; } protected vector<map> gaugeData = [ [$value=>0.0, $title=>"0%", $color=>"0xFFFFFF"] , [$value=>25.0, $title=>"25%", $color=>"0x990000"] , [$value=>50.0, $title=>"50%", $color=>"0xFF0000"] , [$value=>75.0, $title=>"75%", $color=>"0x0000FF"] , [$value=>100.0, $title=>"100%", $color=>"0x00FF00"] ]; 4.8.7 Flow Graphs A flow graph is a combination of ‘places’ and directed ‘links’ that interconnect the places. It can be used to represent concepts such as dataflow and workflow. Flow graph is currently not available in JAG.swf. Here is an example that depicts a workflow representation. <Tab title="Flow Graph direct"> <Panel> <Layout.border/> <Panel lay=$north> <Button.toggle boxButn title="Create Box" action={createFgPlace($box)}/> <Button.toggle queueButn title="Create Queue" action={createFgPlace($queue)}/> <Button title="Auto Size" action={autoSizeFg()}/> <Button title="Delete" action={deleteFgElement()} enable={flowGraph1.select != null}/> </Panel> <Graph.flow flowGraph1 lay=$center bgColor="0xDDDDDD" editable=true data={fgData} GUI Programming 121 event=fgHandler/> </Panel> </Tab> The graph data is defined as: protected vector<map> fgData = [ [$bounds=>(14, 11, 95, 43), $color=>"0x0000FF", $desc=>"old and new pair", $shape=>$box, $title=>"Select Specification", $type=>$place] , [$bounds=>(117, 92, 73, 28), $color=>"0xFF0000", $shape=>$queue, $title=>"Pending Spec", $type=>$place] , [$bounds=>(174, 12, 126, 43), $color=>"0x0000FF", $desc=>"auto-detect differences", $shape=>$box, $title=>"Analyze Specification", $type=>$place] , [$bounds=>(320, 81, 80, 28), $color=>"0xFF0000", $shape=>$queue, $title=>"Analyzed Spec", $type=>$place] , [$bounds=>(302, 141, 116, 54), $color=>"0x0000FF", $desc=>"update dependent\ndocuments & systems", $shape=>$box, $title=>"Apply Changes", $type=>$place] , [$bounds=>(169, 159, 86, 28), $color=>"0xFF0000", $shape=>$queue, $title=>"Processed Spec", $type=>$place] , [$bounds=>(47, 159, 78, 28), $color=>"0x0000FF", $shape=>$box, $title=>"QC Changes", $type=>$place] , [$bounds=>(54, 245, 61, 28), $color=>"0xFF0000", $shape=>$queue, $title=>"Failed Spec", $type=>$place] , [$bounds=>(248, 244, 68, 28), $color=>"0xFF0000", $shape=>$queue, $title=>"Passed Spec", $type=>$place] , [$color=>null, $ends=>(0, 1), $shape=>$line, $type=>$link] , [$color=>null, $ends=>(1, 2), $shape=>$line, $type=>$link] , [$color=>null, $ends=>(2, 3), $shape=>$line, $type=>$link] , [$color=>null, $ends=>(3, 4), $shape=>$line, $type=>$link] , [$color=>null, $ends=>(4, 5), $shape=>$line, $type=>$link] , [$color=>null, $ends=>(5, 6), $shape=>$line, $type=>$link] , [$color=>null, $ends=>(6, 7), $shape=>$line, $title=>"failed", $type=>$link] , [$color=>null, $ends=>(6, 8), $shape=>$line, $title=>"passed", $type=>$link] ]; The various methods used by the graph definitions are as follows. See Chapter 11 for a full description of flow graph properties and event handling. protected void autoSizeFg () { flowGraph1.autoSize = $all; flowGraph1.refresh = true; } protected void createFgPlace (symbol shape) { flowGraph1.click = true; } protected void deleteFgElement () { int idx @= flowGraph1.select; flowGraph1.select = null; deleteFgElementAux(idx); 122 JavaGram Agile Development flowGraph1.refresh = true; } protected void deleteFgElementAux (int idx) { if (idx == null) return; map elem = fgData[idx]; if (elem[$type] == $place) { vector<int> indices @= vector(idx); // Find connected links: for (int i = 0, n = sys.length(fgData); i < n; ++i) { map elem = fgData[i]; if (elem[$type] == $link) { list ends @= elem[$ends]; if (ends[0] == idx || ends[1] == idx) sys.append(indices, i); } } sys.sort(indices, false); for (int i in indices) { renumberElementsAbove(i, -1); sys.remove(fgData, i, $at); } } else { renumberElementsAbove(idx, -1); sys.remove(fgData, idx, $at); } } protected void renumberElementsAbove (int idx, int by) { for (int i = 0, n = sys.length(fgData); i < n; ++i) { map elem = fgData[i]; if (elem[$type] == $link) { list ends @= elem[$ends]; if (ends[0]@int > idx) ends[0] = ends[0]@int + by; if (ends[1]@int > idx) ends[1] = ends[1]@int + by; } } } protected void fgHandler (native comp, symbol event) { list click @= comp@<Graph.flow>.click; switch (event) { case $create: symbol shape = boxButn.select ? $box : $queue; map place = map ( $type => $place, $title => "New", $shape => shape, $color => shape == $queue ? "0xFF0000" : "0x0000FF", $bounds => list(click[0]@int - 30, click[1]@int - 10, 60, 20) ); GUI Programming 123 sys.append(fgData, place); boxButn.select = false; queueButn.select = false; comp@<Graph.flow>.refresh = true; break; case $link: map newLink = map ( $type => $link, $title => "new", $shape => $line, $color => "0x000000", $ends => list(click[0], click[1]) ); sys.append(fgData, newLink); comp@<Graph.flow>.refresh = true; break; case $relink: map oldLink = fgData[comp@<Graph.flow>.select@int]; list ends @= oldLink[$ends]; ends[click[0]@int == ends[0] ? 0 : 1] = click[1]@int; comp@<Graph.flow>.refresh = true; break; case $select: case $drill: sys.println(event); break; } } The resulting flow graph is shown below. 124 JavaGram Agile Development As before, a flow graph can be defined using a model instead of direct data. For example: protected vague fgModel (native comp, symbol cmd, int idx, map edit) { switch (cmd) { case $count: return sys.length(fgData); case $edit: fgData[idx] += edit; break; default: return fgData[idx][cmd]; } return null; } 4.9 Menus and Toolbars Most applications organize their user-accessible commands in menus, toolbars, or both. The normal convention is for these to appear at the top of an application’s frame for easy access. 4.9.1 Pull-down Menus Pull-down menus are accessed via a menu bar that appears at the very top of an application frame. The menu bar is defined using the <MenuBar> element, within which you can define menus using the <Menu> element. A menu itself may contain <MenuItem> or <Seperator> elements. The latter is for separating logical groups of menu items using a horizontal bar. Let’s add a menu bar to our demo application. GUI Programming 125 <jag domain="doc/code/chap4"> <load> "lib/gui/GuiUtil" "lib/gui/Html" "lib/io/Export" "lib/lang/Common" "doc/code/chap4/DemoApp" </load> singleton class Menus { <MenuBar menuBar> <Menu title="File"> <MenuItem title="Print Preview" image={sys.use("lib/gifs/DrillDown.gif")} action={previewHtml()} accelerator="control P"/> <Separator/> <MenuItem title="Exit" image={GuiUtil.blankIcon} action={sys.exit()}/> </Menu> <Menu title="View"> <MenuItem.tick view0 title="Person" action={selectTab(0)}/> <MenuItem.tick view1 title="Tree" action={selectTab(1)}/> <MenuItem.tick view2 title="Table" action={selectTab(2)}/> <MenuItem.tick view3 title="Lists" action={selectTab(3)}/> <MenuItem.tick view4 title="Grid" action={selectTab(4)}/> <MenuItem.tick view5 title="Dialog" action={selectTab(5)}/> <MenuItem.tick view6 title="Graphs" action={selectTab(6)}/> </Menu> </MenuBar> static final string GEN_DIR = sys.pathConc(sys.root, "generated/"); protected void selectTab (int idx) { DemoApp.singleton.selectTab(idx); view0.select = idx == 0; view1.select = idx == 1; view2.select = idx == 2; view3.select = idx == 3; view4.select = idx == 4; view5.select = idx == 5; view6.select = idx == 6; } protected void previewHtml () { preview($html, "Preview.html"); } protected void preview (symbol syntax, string fileName) { string path = null; stream file = null; try { sys.createPath(GEN_DIR); path = sys.pathConc(GEN_DIR, fileName); file = sys.open(path, "w"); switch (syntax) { case $html: sys.println(file, HtmlGenerator.htmlHeader("Demo")); 126 JavaGram Agile Development gui.toHtml(DemoApp.singleton.frontTab(), file, GEN_DIR); sys.println(file, HtmlGenerator.htmlTrailer()); break; case $xml: case $jag: map rec = map(); gui.save(DemoApp.singleton.frontTab(), rec); Export.singleton.toStream(rec, syntax, file); break; } } catch (Exception e) { gui.alert("Error", e.getMessage(), GuiUtil.getMainFrame(), $error); } finally { if (file != null) { sys.close(file); Desktop.open(path); } } } protected void help () { gui.alert("Help", "This command not yet implemented!", GuiUtil.getMainFrame(), $warning); } } </jag> The menu bar in this class defines two menus. The File menu has two simple commands – one for print-previewing the current tab and one for exiting the application. Note the similarity between a menu item and a button’s properties, such as title, image, and action. The accelerator property allows you to define keyboard shortcuts for menu items. The View menu demonstrates tick-able menu items. The action handler for these items calls selectTab() which in turn brings the relevant application tab to the front and sets the select (tick) state of all the items in this menu so that only the selected one it ticked. Our sample menu bar is easily added to the application frame using the <Indirect> element. <App app lookAndFeel=$windows> <Frame frame title="Demo App" width=550 height=400 event=frameHandler> <Indirect ref={Menus.singleton.menuBar}/> //... </Frame> </App> Here is the appearance of the application with the added menu bar. GUI Programming 127 4.9.2 Popup Menus A popup menu is a menu that remains hidden until the user presses the right mouse button, and is useful for implementing context-sensitive commands. A popup menu is always attached to another GUI element (its owner). The same popup menu may be attached to multiple owners. You define a popup menu using the <Menu.popup> element and set its owner property to either a GUI element or a vector of GUI elements. Here is a simple example that defines a popup menu for the demo application’s menu bar, containing two dummy commands and a separator (this is just an illustrative example – we don’t really recommend attaching popup menus to menu bars!) <Menu.popup popup title="Popup Example" owner={menuBar}> <MenuItem title="Command A"/> <Separator/> <MenuItem title="Command B"/> </Menu.popup> To access the popup menu, position the mouse pointer on the menu bar (but not on a menu title) and hold the right mouse button down. Because popup menus can be shared by different GUI elements, when processing a menu command, you might want to know its source (i.e., the actual owner on which the rightclick has taken place). You can get the relevant owner using the invoker property of the popup menu. 4.9.3 Toolbars A toolbar is structurally very similar to a menu, except that instead of menu items, it contains buttons. We’ll add a simple toolbar to our demo application that has just four 128 JavaGram Agile Development buttons. The first button is equivalent to the first command in the File menu. The next two buttons are intended to display the data binding for the current application tab using XML or JAG syntax. All three are implemented using the preview() method, which writes the relevant data to a file and uses the Desktop.open() library method to open and display the file. The last button simply displays an alert box to state that it’s not yet implemented. <ToolBar toolBar floatable=true border=$empty> <Button tooltip="Print Preview" image={sys.use("lib/gifs/DrillDown.gif")} action={previewHtml()}/> <Separator/> <Button tooltip="Show XML Data" image={sys.use("lib/gifs/ViewXml.gif")} action={preview($xml, "XmlData.txt")}/> <Button tooltip="Show JAG Data" image={sys.use("lib/gifs/ViewJAG.gif")} action={preview($jag, "JagData.txt")}/> <Separator/> <Button tooltip="Help" image={sys.use("lib/gifs/Help.gif")} action={help()}/> <Separator/> <Option.tick clock title="Show Clock" action={DemoApp.singleton.showClock(clock.select)}/> </ToolBar> Adding the toolbar to our demo application is easy. <App app lookAndFeel=$windows> <Frame frame title="Demo App" width=550 height=400 event=frameHandler> //... <Panel> <Layout.border/> <Indirect ref={Menus.singleton.toolBar} lay=$north/> <Pane.tabbed tabs lay=$center> //... </Pane> <Panel clockPanel lay=$south visible=false> <Field.text clock readOnly=true width=150 align=$center/> </Panel> </Panel> </Frame> </App> Here is the appearance of the application with the added toolbar. GUI Programming 129 4.10 Google Map This component provides access to Google map functionality. To use Google maps, you must obtain a map API key from Google and assign it to the key property. The following example illustrates the use of Google map, map markers, and geocoding. <jag domain="doc/code/chap4"> <load> "lib/lang/Common" </load> singleton class Map { <GoogleMap gmap key="..." navControl=true typeControl=true event=mapHandler/> protected void mapHandler (native comp, symbol event) { sys.println(event); if (event == $ready) { gmap.at = $(48.85, 2.34); gmap.zoom = 13; gmap.geocode = "Canterbury Melbourne AU"; native marker; marker = gui.create($MapMarker, map( $at=>$(48.86, 2.35), $event=>Util.strToId("markerHandler"), $radius=>2000, $fgColor=>"0xFF0000", $bgColor=>"0x22FFAAAA" ), this); gmap += marker; marker = gui.create($MapMarker, map( $at=>$(48.85, 2.34), $image=>sys.use("lib/gifs/House.gif"), $event=>Util.strToId("markerHandler"), $draggable=>true ), this); gmap += marker; marker = gui.create($MapMarker, map( $at=>$(48.86, 2.35), $event=>Util.strToId("markerHandler"), 130 JavaGram Agile Development $title=>"A", $tooltip=>"My Tooltip" ), this); gmap += marker; } else if (event == $geocode) { sys.println(gmap.geocode); } } protected void markerHandler (native comp, symbol event) { //sys.println(event); switch (event) { case $rollOver: comp@<MapMarker>.info = "<html>Sample info window<br><b>Name:</b> John Smit h<br><b>Occupation:</b> Civil Engineer<br>Another line</html>"; break; case $rollOut: comp@<MapMarker>.info = null; break; case $drag: break; } } } </jag> Specific locations on a map can be marked using the <MapMarker> component. However, because a marker can’t be added to a map until the map is ready, it’s best to create a marker using gui.create() after the map becomes ready. To add a marker to a map, we use the syntax map += marker. To remove it, we use the syntax map -= marker. To find out the geo-coordinate of a location (e.g., country, state, suburb, address), we set geocode property to a string representation of it. This property should not be accessed until the map is ready. The response (reported via the $geocode event) can be accessed by getting this property, which will be a map having these keys: $request (denotes the original request string), $error (denotes an error string when an error occurs), $response (denotes a vector when the request is successful). Each element of the vector denoted by $response is a list of three elements: (resolvedAddress, latitude, longitude). The sample code displays the following map. GUI Programming 131 4.11 Code Editor In some situations, you might find it useful to display JavaGram code in a panel, or even allow the user to edit such code. Imagine an advanced feature that allows the user to customize certain application behavior by providing JavaGram classes that subclass existing classes. A good example is allowing the user to define customized menu items that perform user-defined tasks such as customized reporting. JavaGram provides a GUI element for this purpose, called <Jade.editor>, which is in fact a wrapping of the syntax highlighting editor utilized by the JavaGram IDE. Let’s add a tab to our demo application that uses the code editor component for viewing and editing JavaGram files. <jag domain="doc/code/chap4"> <load> "lib/gui/GuiUtil" </load> singleton class Editor { <Panel panel> <Layout.border/> <Panel lay=$north> <Button title="Open" action={fc.show}/> <Button title="Save" action={save()} enable={canSave()}/> </Panel> <Jade.editor edit lay=$center validation=$semantics event=editHandler/> </Panel> <FileChooser fc owner={GuiUtil.getMainFrame()} kind=$open title="Open" 132 JavaGram Agile Development ext=["jag"] desc="JavaGram files"/> protected string path; protected void fcHandler (vague res) { if (res instanceof string) edit.open = path = res @ string; } protected void save () { edit.save = path; } protected boolean canSave () { return path != null && edit.modified; } protected void editHandler (native comp, symbol event) { if (event == $modified) gui.maintain(panel); } } </jag> <Jade.editor> supports many properties, of which we’ve only illustrated a few here. The validation property denotes the level of validation performed by the editor (defaults to $syntax). We’ve set this to $semantics for the highest level of validation. We’ve defined two buttons for opening and saving files. The former uses the file chooser element to prompt the user to choose a file for opening. The file is opened by setting the open property of the editor to the file path. Conversely, the file is saved by setting the save property to the target file’s path. We’ve also defined an event handler which reacts to a $modified event (this is generated each time a change is made to the editor’s content) by maintaining the state of the panel. Here is what the editor looks like after opening a file. GUI Programming 133 4.12 Working with HTML HTML is an important and useful notation in modern GUI programming. Its many uses include: providing online documentation and help pages, generating reports, providing linkage to world-wide web content, and as a GUI navigation component in its own right. 4.12.1 Displaying HTML JavaGram provides a GUI element, called <Area.url>, for displaying HTML content. This is somewhat similar to <Area.text> except that the content can be specified using a url property. Let’s add a tab to our demo application to demonstrate. <jag domain="doc/code/chap4"> <load> "lib/gui/GuiUtil" </load> singleton class HTML { <Panel panel> <Layout.border/> <Panel lay=$north> <Button title="Open File" action={fc.show}/> <Label title="URL:"/> <Field.text addr cols=20 value="http://"/> <Button title="Go" action={go()}/> </Panel> 134 JavaGram Agile Development <Pane.scroll lay=$center> <Area.url area readOnly=true event=anchorHandler/> </Pane.scroll> </Panel> <FileChooser fc owner={GuiUtil.getMainFrame()} kind=$open title="Open" ext=["html"] desc="HTML files" result=fcHandler/> protected void fcHandler (vague res) { if (res instanceof string) area.url = "file:/" + res @ string } protected void go () { string url @= addr.value; if (url != "") { try { area.url = url; } catch (Exception e) { gui.alert("Failed", e.getMessage(), GuiUtil.getMainFrame(), $error); } } } protected void anchorHandler (native comp, string url) { if (sys.strChar(url, '#') != null) { vector<string> parts = sys.strSplit(url, "#"); if (sys.length(parts) > 1) area.scrollTo = parts[1]; } else area.url = url; } } </jag> Although a url area can be editable, it’s often better to set the readOnly property to true so that the user doesn’t accidentally alter the content. Note the different between the signature of the event handler for this component, anchorHandler(), compared to other components: the second argument is a URL string rather than an event symbol. The handler is called when the user clicks on an anchor. If url contains a # then the reference is a local one (i.e., in the same file), in which case we set the scrollTo property of the component to the tag after the # character. This causes an automatic scroll to the target of the anchor. Otherwise, url is deemed to be an external reference, so we simply set the url property of the component to it. We’ve provided two ways of ‘opening’ HTML content. The Open button displays a file chooser so that the user can nominate a local HTML file. For such files, we always prepend the file path with the "file:/" string so that it’s treated as a local file reference. The second method consists of a text field for specifying a URL and a Go button that attempts to open that URL. There is always a possibility that the nominated URL can’t be GUI Programming 135 opened due to, for example, being invalid or no network connection being available. So we’ve added some exception handling code to cater for such cases. Here is what the new tab looks like after opening a local HTML file. JavaGram supports a third style of anchor that consists of an arbitrary JavaGram expression enclosed in braces. This can be used to tie anchors to programmer-defined actions, such as jumping to a different area of the application. This type of anchor is supported by the lib/gui/Html.jag library script. For examples of how it’s used, please refer to the Quest sample application in Chapter 8. 4.12.2 Generating HTML Reports Most applications have some level of report generation capability. A common requirement is the ability to generate a report that captures what the user sees on the screen. For example, if the user has performed a search and is viewing the search result, they might want to produce a printable report that captures the search result. JavaGram provides a method, gui.toHtml(), for converting the logical data displayed in a GUI component to HTML. It has the following signature. void gui.toHtml (native comp, stream out = null, string dir = null) This method writes an HTML representation of comp (and the data it contains) to the stream denoted by out, or sys.out when out is null. It ignores containers whose report property is set to false. The dir parameter specifies the directory into which the caller intends to place the HTML file (defaults to the current user directory) – any generated image files are placed in this directory. 136 JavaGram Agile Development If you add HTML header and trailer information to what this method produces then you’ll have a complete HTML report. For an example, see the Menus.preview() method described earlier in this chapter. Here is an example of a report generated by this method for the first tab of the demo application. The HtmlGenerator class in the lib/gui/Html.jag library script provides a more generic framework for HTML report generation. Its use is demonstrated by the Quest sample application in Chapter 8. 4.12.3 Generating Images Another useful method of capturing the information presented by a GUI is to save it in an image file. JavaGram provides the gui.saveImage() method for this purpose. This method is also internally used by gui.toHtml() to save things like graphs in separate files that are then referenced by the HTML file. Another useful method for image capture is gui.imageAsHex() which, rather than saving the image in a file, returns a hexadecimal string representation of it so that, for example, it can be stored in a database. 4.13 Tips and Tricks This section describes a number of programming tips that will come handy when developing GUIs for real-life applications. 4.13.1 Using Boiler Plates Most GUI elements extend <Comp> (see Chapter 11) which has a large number of properties. If you encounter a situation where you’re setting these properties (e.g., GUI Programming 137 fgColor) to the same value for many elements then you can simplify your code by factoring out the commonalities. To do this, set the relevant properties to the desired values in a <Plate> element and then use this boiler plate when creating other elements. For example, suppose that you have a panel that displays various fields for the user to fill, where some of these fields are required and some are optional. You might decide to highlight the required fields by setting their foreground color to red and assigning a ‘required’ tooltip to all such fields. This is nicely captured by a boiler plate. <Plate Req fgColor="0xFF0000" tooltip="Required"/> Now, when defining a component, you can base it on this boiler plate like this: <Req:Label title="Surname"/> //... <Req:Field.date dob/> For a good example of using boiler plates, see the Screen class in the lib/gui/Screen.jag library script, where you’ll find this definition: <Plate Hot enable={hotEnable()} event=hotHandler/> This provides a very easy way of nominating the ‘hot’ fields in a screen. A change to any such field results in gui.maintain() being invoked by the event handler to maintain the state of the screen. 4.13.2 Managing Component Visibility In most GUI frameworks (including Java’s Swing), the state of a component is managed by procedural code. For example, if a search button is supposed to remain disabled until the user specifies some search criteria, the program will contain explicit code that sets the state of the button in response to events arising from search criteria specification. In JavaGram, a declarative style is used instead – when defining the button, we state the conditions under which it should be enabled/disabled and leave the rest to the framework. The advantages should be obvious: less coding and a far more readable outcome. Every element that extends <Comp> can have an enable and/or visible property. These two properties can be set to arbitrary expressions. JavaGram evaluates these expressions in response to certain events and, based on the outcome of the evaluation, changes the visual state of the component accordingly. The enable property determines whether the component should be displayed as enabled or disabled (dimmed). If unspecified, the component is always enabled. The visible property determines whether the component should be shown or hidden. If unspecified, the component is always visible. 138 JavaGram Agile Development Internally, JavaGram uses certain GUI events as trigger for evaluating these two properties. Typical triggers include: the user pressing a button, clicking in a check box, choosing something from a combo box, and so on. In response to any such event, JavaGram propagates the event from its origin, up the GUI hierarchy until it can go no further, and then down the hierarchy (evaluating these two properties wherever relevant) until it covers all the children. This algorithm delivers the desired outcome in almost all cases. If you encounter a situation where the framework’s behavior is insufficient, you can force an explicit evaluation of these two properties by calling the gui.maintain() method. Here is the signature of this method: void gui.maintain (native comp, boolean force = false) The component you pass as the first argument and all its direct and indirect descendents will be processed. This method uses optimization measures when deciding whether to evaluate the enable and visible property of each component. For example, for a disabled container, it won’t attempt to process its descendents. You can override this optimization by passing true for the force parameter. 4.13.3 Correct Use of Layouts Layouts are fundamental to the way components are organized in a container. An important benefit of layouts is that when a container is resized, its children are also resized according to the properties of the container’s layout. Choosing the right layout ensures that resizing results in the contents preserving their logical structure and visual appeal. We’ve seen the use of a number of layouts in this chapter. There are also a few that we haven’t covered yet. Here is a list of all the layouts supported by JavaGram and a summary description of their purpose. Layout <Layout.flow/> <Layout.border> <Layout.card/> <Layout.grid/> <Layout.gridBag/> GUI Programming Purpose For organizing components left-to-right. This is the default layout for a panel whose layout is unspecified. Use this if you want to display a collection of buttons organized horizontally. For organizing components in up to five groups, consisting of north, south, east, west, and center. The center component is the dominant one and suitable for a scroll pane containing a table, a list, or the like. When you add a component to a container with border layout, you can specify its destination group using the lay property of the component. If you don’t, $center will be assumed. For organizing components on top of each other, such that only one is visible (at the top) at a time. Useful when you want a collection of pre-loaded panels, but the user is intended to see only one of them. For organizing components in a matrix, especially a group of very similar components (e.g., buttons, images, sometimes even panels). The grid must have a predetermined number of rows and columns. Components are added in left-to-right and top-to-bottom order. This is like the grid layout but much more flexible. The components 139 are not added directly but through <Lay> elements that determine the position of each element as well as other properties such as amount of space to take up. One of the flexibilities of a grid-bag layout is that a component can span across grid boundaries and occupy multiple rows and/or columns. <Layout.horizontal/> For organizing components along a horizontal axis. <Layout.vertical/> For organizing components along a vertical axis. 4.13.4 Managing Periodic Tasks If your application needs to do something periodically then a timer is required. JavaGram provides two types of timers for such cases: The Timer and TimerTask classes defined in lib/lang/Common.jag and described in Chapter 6 are intended for anything that is not GUI related. This makes it suitable for server-side usage, such as polling a table in a database and processing any new data that may have been deposited there since the last poll. <Timer> is intended for doing periodic tasks that are GUI related (e.g., causing changes to the visual appearance of the GUI). An example would be polling the state of the files in a directory and updating a tree that provides a visual representation of the directory structure. The reason for distinguishing between GUI and non-GUI periodic tasks is the underlying design of JavaGram’s GUI framework which is based on Java’s Swing. Swing requires any code that accesses GUI components to run on Swing’s event dispatching thread. The <Timer> element conforms to this requirement, but Timer doesn’t. So if you want to avoid obscure threading and event handling issues, use the appropriate type of timer for the task at hand. To illustrate the use of <Timer>, we’ll add a small date and time panel to the bottom of our demo application and refresh it every second. <Panel clockPanel lay=$south visible=false> <Field.text clock readOnly=true width=150 align=$center/> </Panel> Here is the timer and its event handler. <Timer timer event=timerHandler delay=1000/> protected void timerHandler (native comp, symbol event) { if (event == $do) clock.value = sys.format(sys.date(), "dd MMM yyyy, HH:mm:ss"); } public void showClock (boolean show) { clockPanel.visible = show; timer.start = show; 140 JavaGram Agile Development timer.stop = !show; } The timer’s delay property is specified in milliseconds and specifies how often it should fire. The timer doesn’t start until its start property is set to true, and eventually stops when its stop property is set to true. When the timer fires, it raises a $do event, in response to which we update the clock field value. We’ve also added a check box to the application’s toolbar to control the visibility of the clock (initially not visible). <Option.tick clock title="Show Clock" action={DemoApp.singleton.showClock(clock.select)}/> Here is the application appearance when the clock is active. 4.13.5 Using Worker Threads Performing a lengthy task within the even handler of a component will cause the GUI to freeze until the task is completed. For example, if the user presses a ‘search’ button to perform a potentially long database query, the application will become unresponsive until the button’s action handler returns. Whilst a delay of a few seconds is tolerable, longer delays, especially running into minutes, will degrade the user experience. The freezing of the application during lengthy operations can be avoided by handing over the lengthy task to a separate thread (threads are introduced in Chapter 6). However, as with Timer, normal threads are not suitable for tasks that may access the GUI GUI Programming 141 components. The <Worker> element provides the right solution for such cases, because it executes on Swing’s event dispatcher thread. As an example, we refer to the login procedure of the Quest sample application described in Chapter 8. When this application is run, it displays a small frame containing the authentication fields. Once login is completed, this frame is replaced by the actual application frame. When the user enters their username and password and presses the Login button, we use a worker thread to perform the login, because it can potentially be a lengthy operation. While login is in progress, we want to show a progress bar and also keep the frame responsive so that, for example, the user can cancel the login. The worker thread makes all this possible. Here is how the worker thread is defined in Quest. <Worker login worker=loginWorker/> The worker property denotes the name of a method in the same class (similar to an event handler) that’s internally called by JavaGram. protected vague loginWorker (symbol cmd, vague val) { switch (cmd) { case $do: setLoggedIn($pending); progress.indefinite = true; try { setCurrUser(User.login(username.value@string, password.value@string)); setLoggedIn($yes); return getCurrUser(); } catch (Exception e) { setLoggedIn($no); progress.indefinite = false; gui.alert("Login Failed", e.getMessage(), frame, $warning); } break; case $done: if (loggedIn == $yes) { // Replace the login panel with application panel: frame.visible = false; frame -= panel; sys.call(Util.getSingleton("quest/gui/AppPanel"), $addToFrame, frame); } break; case $progress: break; case $process: break; } 142 JavaGram Agile Development return null; } The worker commences execution when its run property is set to true. The $do command is raised as a result, allowing loginWorker() to react – we handle this by activating the progress bar and sending a login request to the application server. When the worker completes execution, the $done command is raised. In response to this, we check if the login has been successful and, if so, reconfigure the frame to show the application. If required, the worker can be easily cancelled by setting its cancel property to true. The $progress command is raised when a value is assigned to the progress property of the worker, in which case val denotes the progress value. This gives you the opportunity to visually display the level of progress so far (which we haven’t done here because we’ve used an indefinite progress bar). The $process command is raised when a value is assigned to the publish property of the worker. It’s possible that $process is raised once for multiple assignments. Therefore, val is always a vector of the latest assigned values. This gives you the opportunity to process data published so far (again, we haven’t used this is our example). 4.13.6 Procedural Creation of Components As stated at the beginning of this chapter, JavaGram provides a declarative style of GUI programming. This is very convenient when you can determine the structure of your user interface in advance and define it accordingly. But what about situations where the components can’t be determined in advance and are instead determined through user actions? We’ve already seen one example of this in our discussion of grids, where the user causes a new row to be added to the grid by clicking on a link. Our approach there was to capture the definition of the new row in a separate class and then add it to the grid using the += operator. This approach is recommended in situations where it delivers a clean solution (as was the case in our grid example). There is, however, the odd situation where a procedural approach is preferred because it involves the least amount of mocking around. For example, if you have an application that’s supposed to read some meta data and use it to build a customized panel then a procedural approach is likely to produce a simpler solution. JavaGram allows you to define a GUI component procedurally using the gui.create() method, whose signature is as follows. native gui.create (symbol tag, map<symbol,vague> props = null) The first parameter denotes the GUI element’s name as a symbol (e.g., $Node). If the name is qualified, you should escape the symbol (e.g., ${Pane.scroll}). The second GUI Programming 143 parameter is optional and can be used to specify the element’s properties as a collection that maps property names (each specified as a symbol) to their values. A very common use of gui.create() is for dynamically adding nodes to a tree. For example, to add a child node titled ‘Sample’ to an existing node denoted by the node variable, you can write: node += gui.create($Node, [$title=>"Sample"]); There are also two companion methods for getting and setting the properties of GUI components dynamically. For example, to get/set the title of a tree node, you can write: string title = gui.get(node, $title); gui.set(node, $title, "New Title"); // title = node.title // node.title = "New Title" 4.14 Browser-based GUIs The GUI elements described in this chapter (with the exception of <Graph.flow> and <Jade.editor>) are also supported by the Flash version of the JavaGram runtime. However, please note that because Flash is asynchronous and single-threaded, a few features behave slightly differently: <Worker> executes in the same thread, not a separate thread. In <Alert>, <Alert.ync>, <FileChooser>, <ColorChooser>, the show property always returns null, rather than the result of the selection. Similarly, gui.alert() returns null. For these cases, you can set the result property of the element to a callback method that will be passed the result when it becomes available. <Area.url> provides very basic HTML support. The Demo application presented in this chapter will run in both the Java and the Flash runtime. Here is what it looks like when run in a browser. 144 JavaGram Agile Development 4.15 Summary of Elements We’ll end this chapter by providing a visual summary of JavaGram’s built-in GUI elements as a UML diagram. For a detailed description of each element, please refer to Chapter 11. GUI Programming 145 146 JavaGram Agile Development 5 SQL Programming A common requirement of most applications is the persistence of objects to external storage so that they can be reconstructed in a subsequent run. For some objects, persistence to a local file would be adequate. For example, user ‘preferences’ can be saved to a file on the user’s machine and retrieved the next time the application is run. For objects that can be shared by multiple users, however, this approach is inadequate as it doesn’t deal with concurrency issues. It’s also inefficient when a large number of objects are involved. A relational database is typically used in such cases, where objects are stored in indexed tables and accessed through the SQL language. This chapter describes the JavaGram features aimed at SQL programming. For ease of understanding, we’ll use simplistic examples that illustrate the approach rather than provide an exhaustive treatment. You should study this chapter in conjunction with the SQL Reference presented in Chapter 12. Also, the Quest sample application presented in Chapter 8 provides more elaborate examples of the concepts presented here. In JavaGram, all database access is through Java DataBase Connectivity (JDBC). For ease of use and productivity, JavaGram hides the low-levels of JDBC. The sql and bom pseudo classes provide access points that blend well with JavaGram features such as containers. The Object library class provides a further level of abstraction that enables you to quickly implement persistence through inheritance. 5.1 Working with Databases An application that performs database interaction must perform these steps: Load a JDBC driver for the database Connect to the database Perform any number of queries and/or transactions Disconnect from the database This section describes the first two and the last step. The third step is described in Sections 5.3 and 5.4. The database engine we’ve used throughout this book is MySql. You should download it from www.mysql.com, install, and configure it before attempting the examples presented in this chapter. For simplicity, our examples assume that you’ve configured the password for the ‘root’ account to be ‘password’. SQL Programming 147 5.1.1 Explicit Connection A database connection can be managed in one of two ways: explicitly by the programmer or implicitly by JavaGram. We strongly recommend the latter, as it involves less coding and is much easier to manage. The explicit style is described here for completeness. The following class illustrates the explicit style of managing a database connection. <jag domain="doc/code/chap5"> class ExplicitStyle { static final string DB_URL = "jdbc:mysql://localhost/mysql"; public static void main () { sql.loadDriver("com.mysql.jdbc.Driver"); native conn = sql.connect(DB_URL, "root", "password"); query (conn) { native rs = sql.execQuery(conn, "select user, host from user"); while (sql.next(rs)) sys.println(sql.getRow@map(rs)); } sql.close(conn); } } </jag> The sql.loadDriver() method takes a qualified Java class name denoting the JDBC driver and attempts to load it. The driver’s JAR file must be in your Java CLASSPATH for this to work. For example, you can specify the driver’s JAR file along with JAG.jar in the command line for running a program: java -cp "C:/JavaGram/jar/JAG.jar;C:/JavaGram/jar/mysql-connector.jar" jag.run.Env -root "C:/JavaGram" "doc/code/chap5/ExplicitStyle.jag" A connection is created by calling sql.connect(). The first argument to this method is the URL of the database you want to connect to. For this, we’ve used the ‘mysql’ database (MySql keeps a database about itself to store configuration information). The second and third arguments specify a valid username and password for the connection. Next, we perform a query using the query statement (don’t worry about the details for now; they’ll be explained later), and finally close the connection by calling sql.close(). The query in this example retrieves the user and host attributes from the user table and displays them, producing the following output. [$host=>"localhost", $user=>"root"] 5.1.2 Implicit Connection In the implicit connection style, details such as database driver, URL, and login information are centrally stored in an application configuration file. This configuration 148 JavaGram Agile Development file is nominated in the command line for running the program using the -config option. Application configuration files are described in Chapter 7, and can hold many other pieces of information (all specified as a map) about the application. Here is a minimal configuration file for capturing database information. [ $database=> [ $mySqlDb=>[ , , , , , ] ] ] $name=>"MySql Database" $url=>"jdbc:mysql://localhost/mysql" $user=>"root" $password=>"password" $timeout=>[$login=>30, $query=>20] $driver=>"com.mysql.jdbc.Driver" The $database key in the configuration map points to a collection that maps a key (e.g., $mySqlDb) for each database used by the application to its configuration. The latter is itself a map that captures things like the driver class name, database URL, login information, etc. The example here is intended to give us access to the same database we used for the explicit connection style. Using this configuration file, the ExplicitStyle class can be re-written as: <jag domain="doc/code/chap5"> class ImplicitStyle { public static void main () { query ($mySqlDb) { native rs = sql.execQuery($mySqlDb, "select user, host from user"); while (sql.next(rs)) sys.println(sql.getRow@map(rs)); } } } </jag> As you can see, we no longer have to explicitly load a driver or open/close a connection – all that is managed by JavaGram. Note that in the query statement and the call to sql.execQuery(), we pass the database key specified in the configuration file (i.e., $mySqlDb) rather than a connection. An added benefit of this approach is that, because database information is centrally captured, making changes to it becomes trivial and eliminates any potential impact on the application code. Here is a sample command line for running this program: SQL Programming 149 java -cp "C:/JavaGram/jar/JAG.jar;C:/JavaGram/jar/mysql-connector.jar" jag.run.Env -root "C:/JavaGram" –config "doc/code/chap5/FirstConfig.jag" "doc/code/chap5/ImplicitStyle.jag" The program produces exactly the same output as ExplicitStyle. 5.2 Text Members Before we get into transactions and queries, we’d like to introduce a JavaGram feature that comes very handy for composing SQL commands. You’ve already seen three kinds of class members: fields, methods, and GUI members. There is also a fourth kind called text members. Like GUI members, these are defined using a markup notation, but unlike them, text members behave like methods. You can think of a text member as a parameterized string, but with the added flexibility that the string can span across multiple lines and not requiring double-quote characters to be escaped. Examples of situations where text members prove useful include: report generation and SQL composition. Of course, the same outcome can be achieved by concatenating strings, but text members are superior because they’re far more readable and give you more control. Their use for formulating SQL has the added benefit of separating SQL code from normal method code, which further simplifies future maintenance. 5.2.1 Text A text member is defined using the <text> markup. For example, suppose that you have a program that creates user accounts and emails the account details to each new user. The email text can be composed by a text member. <jag domain="doc/code/chap5"> class Emailer { static <text string newUserEmail (string name, string username, string password)> Dear {name}, Please be advised that your new user account has been setup for accessing our web site. Your account details are: Username: {username} Password: {password} Please visit www.acme.com and login with these details. Upon login, you'll be asked to change your password. </text> public static void main () { sys.println(newUserEmail("John Smith", "john.smith", "secret")); } } </jag> Note how the newUserEmail() text member is defined to return a string. This return type is mandatory because a text member always returns a string value. Also note how 150 JavaGram Agile Development references to method parameters within the text are enclosed in braces. This follows the same splicing rules as in delayed strings, so you can enclose any valid expression in braces. The semantics of a text member is very simple. It’s invoked like a normal method, text substitution is performed, and the resulting text is returned as the final value. When run, this program produces the following output. Dear John Smith, Please be advised that your new user account has been setup for accessing our web site. Your account details are: Username: john.smith Password: secret Please visit www.acme.com and login with these details. Upon login, you'll be asked to change your password. Like GUI members, text members accept properties; these should appear after the method signature. For example, if you set the indent property to true, the text indentation (as appearing in the code) is preserved. 5.2.2 Text.sql JavaGram provides a specialization of <text> for the specific purpose of composing SQL. The markup tag for this is <text.sql>. It behaves the same as <text> but supports these conventions for escaping spliced values. A backquote (`) before a spliced expression means that the resulting value should be escaped as required by SQL (e.g., {`username}). An equal symbol (=) before a spliced expression means that the resulting value should be used verbatim (e.g., {=tableName}). Other than the above two scenarios, the {...} syntax is not allowed in order to avoid inadvertent misuse. There are also a number of specializations of <text.sql> that target specific types of SQL operations: <text.sql.query> composes and performs a query, returning a native result set. <text.sql.update> composes and performs an update, returning an integer that denotes the number of rows affected. <text.sql.prepare> creates a parameterized prepared statement that can later be instantiated multiple types. It returns the native prepared statement. <text.sql.callable> creates a callable statement for invoking a stored procedure. It returns the native statement. SQL Programming 151 We’ll see examples of their use later in this chapter. 5.3 Transactions A transaction is an operation that alters the information held by a database. Examples include: creating a new table, inserting rows into a table, updating an existing table row, or deleting table rows. One of the benefits of using a database engine is the predictability of transactions. If multiple programs attempt to update the same information, the database isolates these updates so that they don’t trip on one another. 5.3.1 Creating a Database For the rest of this chapter, we’ll use a test database for the purpose of the sample codes provided. To create this database, run the MySql Query Browser, right-click on the Schemata tab, and choose Create New Schema from the popup menu. A dialog appears; enter ‘test’ and press OK. At this point, the test database is empty, but we’ll run transactions to populate it shortly. 5.3.2 Creating Tables Let’s create our first table. Here is a modified version of the Person class introduced earlier in the book, but now written with persistence in mind. class Person { int id; string firstName; string lastName; string sex; date dob; boolean married; public static void createTable () { string str = "create table if not exists person(" + "id integer not null," + "first_name varchar(16)," + "last_name varchar(16)," + "sex varchar(8)," + "dob date," + "married bit," + "primary key(id), index(last_name))"; transaction ($testDb) { sql.execUpdate($testDb, str); } } public static void main () { createTable(); } 152 JavaGram Agile Development } The createTable() method composes a SQL string for creating a person table. The attributes of this table are defined to match the Person class fields. We’ve also defined two indices for the table – one for the id column (defined as a primary key) and one for the last_name column. The benefit of an index is that it enables fast lookup, so it’s typically defined for columns that are frequently accessed. However, indices also come at a cost – they consume additional storage and the database has to keep them up-to-date when transactions are performed. So their use must be treated as a tradeoff. To create the table, we pass the SQL string to sql.execUpdate(). Given that this is a transaction, we must perform the call inside a transaction statement. The latter marks the transaction boundary and ensures that within that boundary a valid database connection is obtained, used, and released. Once you’ve run this program, use the MySql Query Browser to check that the person table has been added to the test database. Returning to the createTable() method, the way the SQL string is composed is typical of the way it’s done in most programming languages (including Java). This approach is crude and problematic. For someone reading a program, it makes it rather difficult to locate SQL code (because it can potentially be anywhere) and to readily make sense of it (because it’s built procedurally out of smaller fragments). The JavaGram <text.sql> element provides a neat solution to this problem, as evidenced by the following re-write of the Person class. class Person { //... public static void createTable () { transaction ($testDb) { sql.execUpdate($testDb, createTableSql()); } } static <text.sql string createTableSql()> create table if not exists person( id integer not null, first_name varchar(16), last_name varchar(16), sex varchar(8), dob date, married bit, primary key(id), index(last_name)) </text.sql> public static void main () { createTable(); } } SQL Programming 153 Organizing all your SQL like this will totally decouple it from normal program code, thus enhancing the clarity of your design. Subsequently, a simple global search on ‘<text.sql’ will pinpoint all SQL code in your program. For this example, we can go one step further and use <text.sql.update>. class Person { //... static <text.sql.update native createTable () db=$testDb> create table if not exists person( id integer not null, first_name varchar(16), last_name varchar(16), sex varchar(8), dob date, married bit, primary key(id), index(last_name)) </text.sql.update> public static void main () { transaction ($testDb) { createTable(); } } } Here, createTable() not only composes the SQL but also executes it. Therefore, it needs to have a database connection, which is provided via the db property. 5.3.3 Dropping Tables Dropping a table causes it to be permanently removed from the database, including any data it’s holding. This is easily expressed using a <text.sql.update> method. For example, for the Person table, we can write: static <text.sql.update native dropTable () db=$testDb> drop table if exists person </text.sql.update> In fact, it’s just as easy to write a generic method that will work for any table by passing the table name as a parameter: static <text.sql.update native dropTable(string table) db=$testDb> drop table if exists {=table} </text.sql.update> 5.3.4 Inserting Rows The following method inserts a row into the person table. 154 JavaGram Agile Development <text.sql.update int insert () db=$testDb> insert into person (id, first_name, last_name, sex, dob, married) values ({`id}, {`firstName}, {`lastName}, {`sex}, {`sys.format(dob)}, {`married}) </text.sql.update> Because this method is not static, it has access to Person fields. Note how the reference to each field has been expressed as {`field}, instructing JavaGram to escape the value appropriately. For the dob field, we’ve used the sys.format() method to format the date according to the syntax required by MySql (e.g., '1982-2-23'). Here is a sample call to this method: transaction ($testDb) { Person p = [@Person id=>1, firstName=>"John", lastName=>"Smith", sex=>"Male", dob=>[#1982-2-23], married=>false]; p.insert(); } The call to insert() returns 1, which is the number of rows inserted. If you want to insert many rows into a table, you might want to consider using a prepared statement. The advantage of a prepared statement is efficiency – it’s parsed only once by the database engine and reused as often as required. Here is an example: singleton class People { Person p; int count; <text.sql.prepare native insertPrep () db=$testDb> insert into person (id, first_name, last_name, sex, dob, married) values ({?p.id}, {?p.firstName}, {?p.lastName}, {?p.sex}, {?sys.format(p.dob)}, {?p.married}) </text.sql.prepare> public void personTest () { transaction ($testDb) { native insert = insertPrep(); p = [@Person id=>1, firstName=>"Linda", lastName=>"Black", sex=>"Female", dob=>[#1980-12-20], married=>true]; sql.execUpdate(insert); p = [@Person id=>2, firstName=>"Mark", lastName=>"Adams", sex=>"Male", dob=>[#1988-02-13], married=>false]; sql.execUpdate(insert); } public static void main () { People.singleton.personTest(); } } SQL Programming 155 Take note of the way the SQL in insertPrep() is formulated. The notation {?expr} is specific to prepared statements – it specifies placeholders that are substituted for each time the statement is reused. When insertPrep() is called, SQL is formulated and returned as a prepared statement but not executed. The execution occurs later on when sql.execUpdate() is invoked on the prepared statement. The code therefore is written such that, for example, {?p.id} is valid at the time of execution, not preparation. Understanding this subtle point is vital to the proper use of prepared statements. The personTest() method exercises the prepared statement by first creating it and then using it twice. Before each update, we set p to the Person object to be inserted and then call sql.execUpdate(), passing just the prepared statement to the latter. An alternative to using <text.sql.prepare> is to use sql.prepare(). To illustrate its use, here is an alternative method of inserting a Person: public static void insertPersonDynamic (Person p) { string str = "insert into person (id, first_name, last_name, sex, dob, married) " + "values (?, ?, ?, ?, ?, ?)"; native ps = sql.prepare($testDb, str); sql.set(ps, 1, p.id); sql.set(ps, 2, p.firstName); sql.set(ps, 3, p.lastName); sql.set(ps, 4, p.sex); sql.set(ps, 5, p.dob); sql.set(ps, 6, p.married); sql.exec(ps); } Note how the ? placeholders are later set using sql.set() and the prepared statement executed using sql.exec(). In general, <text.sql.prepare> is preferred due to its conciseness and simplicity, but it’s less flexible. Therefore, use sql.prepare() only for cases where you need to form the SQL dynamically. 5.3.5 Updating Rows Once a row has been inserted into a table, it may subsequently need to be updated. For example, if a person’s marital status changes then we’ll need to update the married column. Here is how we could define Person.update(): <text.sql.update int update () db=$testDb> update person set first_name = {`firstName}, last_name = {`lastName}, sex = {`sex}, dob = {`sys.format(dob)}, married = {`married} where id = {`id} </text.sql.update> 156 JavaGram Agile Development For example, to change a person’s last name, we would write something like: transaction ($testDb) { //... p.lastName = "Anderson"; p.update(); } Again, if we were to update many rows of the person table, it would be wise to implement this as a prepared statement. <text.sql.prepare native updatePrep () db=$testDb> update person set first_name = {?p.firstName}, last_name = {?p.lastName}, sex = {?p.sex}, dob = {?sys.format(p.dob)}, married = {?p.married} where id = {?p.id} </text.sql.prepare> This would be used, for instance, like this: transaction ($testDb) { native update = updatePrep(); //... p.lastName = "Anderson"; sql.execUpdate(update); } 5.3.6 Deleting Rows Deleting a table row is straightforward. Like an update, we need to specify the criteria for identifying the rows that need to be deleted. For example, the following method deletes a person row via its unique id. static <text.sql.update int deleteById (int id) db=$testDb> delete from person where id = {`id} </text.sql.update> As before, the same can be expressed as a prepared statement if it’s likely to be used often. 5.4 Queries A database query is a read-only operation. It typically involves searching one or more tables, and will ultimately return a result set. The result set is then processed by, for SQL Programming 157 example, iterating through it, processing each result (which is often the equivalent of a row), and converting it to a JavaGram data type, such as a map or object. The easiest way to write a query is to express it as a <text.sql> or <text.sql.query> class member. 5.4.1 Retrieving Rows The most common type of query involves the retrieval of table rows. As a simple example, here is a method, Person.find(), that retrieves a Person using its unique ID. public static Person find (int id) { native rs = _findById(id); return sql.next(rs) ? sql.getRow@Person(rs) : null; } protected static <text.sql.query native _findById (int id) db=$testDb> select * from person where id = {`id} </text.sql.query> We’ve implemented this as two methods: _findById() provides and executes the SQL, which is in turn called by find(). The former returns a result set, which the latter examines by calling sql.next() to see if it has any data, in which case sql.getRow() is called on the result set to retrieve the row. Note the strange casting syntax we’ve used here rather than the normal casting syntax. Here is the difference: sql.getRow(rs)@Person is the normal casting syntax but not suitable for this situation – the cast happens too late. sql.getRow@Person(rs) is the syntax we want here because it tells sql.getRow() that it should retrieve the row as a Person object. This is called method casting and can be applied to any pseudo class method that returns a non-void type. However, it’s up to the method to decide what to do with it (most methods treat it the same way as normal casting). Method casting provides an easy way of specifying the desired syntax of a result. For example, we can get the result as a map instead by writing: sql.getRow@map(rs). The call Person.find(1) will return the following: [@Person dob=>[#1980-12-20], firstName=>"Linda", id=>1, lastName=>"Black", married=>true, sex=>"Female"] Note how underscores in table column names have been automatically translated (e.g., first_name becomes firstName). JavaGram does this as a rule because underscores are commonly used in data models, whereas in JavaGram we prefer to use camel-case identifiers. Here is another example, which returns a vector of all persons in the person table. 158 JavaGram Agile Development public static vector<Person> find () { native rs = _findAll(); vector<Person> vec @= vector(); while (sql.next(rs)) sys.append(vec, sql.getRow@Person(rs)); return vec; } protected static <text.sql.query native _findAll () db=$testDb> select * from person </text.sql.query> Because of the wildcard query, we expect the result set to potentially contain multiple rows, so we’ve used a while-loop to iterate through the result set and incrementally add each result to a vector. The call Person.find() will return the following: [ [@doc\code\chap5\Person dob=>[#1980-12-20], firstName=>"Linda", id=>1 ,lastName=>"Black", married=>true, sex=>"Female"] , [@doc\code\chap5\Person dob=>[#1988-02-13], firstName=>"Mark", id=>2 ,lastName=>"Adams", married=>false, sex=>"Male"] ] As with updates, frequently used queries would be better expressed using prepared statements, preferably using <text.sql.prepare> or, where the SQL must be formed dynamically, using sql.prepare(). 5.4.2 Processing a Result Set As shown in earlier examples, to process a result set returned by a query, we typically use a loop and keep calling sql.next() until it returns false. There are three other related methods that allow you to move through the result set: sql.previous() moves to the previous record in the result set. sql.first() moves to the first record in the result set. sql.last() moves to the last record in the result set. All these methods return true when successful and false otherwise. Use them to process query results in your preferred order. For example, if you’re searching a large result set for a specific record and expect it to be in the tail end of the result set, it makes sense to move to the end of it by calling sql.last() and then iterate back by calling sql.previous(). 5.4.3 Retrieving Attributes If you need to retrieve a single attribute of a table then sql.getRow() is overkill and rather wasteful. You can use sql.get() for such cases instead. It allows you to retrieve an attribute using its column name or its one-based column index. The following method, SQL Programming 159 Person.testGet(), demonstrates this by retrieving last_name (via its column name) and dob (via its column index): public static void testGet () { native rs = _findById(1); if (sql.next(rs)) { sys.println("Last name: ", sql.get(rs, $last_name)); sys.println("DOB: ", sql.get(rs, 5)); // Fifth column is dob } } Note that, unlike sql.getRow(), sql.get() does not perform any column name translation. This example produces the following output: Last name: Black DOB: 1980-12-20 Of course, if your intention is to retrieve just one column then the SQL can be better written to reflect your intention. For example: select last_name from person where id = 1 5.4.4 Counting Rows To efficiently count the number of rows in a table, use the MySql count function, as illustrated by this method: public static int count () { native rs = sql.execQuery($testDb, "select count(*) from person"); return sql.next(rs) ? sql.get@int(rs, 1) : 0; } Note how we’ve used method casting on sql.get() because this method has a vague return type. In this case, normal casting would have worked just as well. 5.4.5 Performing Joins A join is a query that operates on two or more tables, exercising a relationship between the two tables. For example, suppose that we have an Account class defined as follows. <jag domain="doc/code/chap5"> class Account { string accNo; string accType; int owner; // ID of Person who owns the account static <text.sql.update native createTable() db=$testDb> create table if not exists account( acc_no varchar(16) not null, acc_type varchar(16), 160 JavaGram Agile Development owner integer not null, primary key(acc_no), index(owner)) </text.sql.update> static <text.sql.update native dropTable() db=$testDb> drop table if exists account </text.sql.update> <text.sql.update int insert () db=$testDb> insert into account (acc_no, acc_type, owner) values ({`accNo}, {`accType}, {`owner}) </text.sql.update> } </jag> The account.owner column refers to the person.id column. Therefore, there is a one-tomany relationship between person and account tables (a person can have multiple accounts). The owner column is said to be a foreign key, because it refers to the key of another table. To illustrate the use of joins, let’s populate the account table with some data first. public static void populateAccount () transaction ($testDb) { vector<Account> accounts = [ [@Account accNo=>"0001", ,[@Account accNo=>"0002", ,[@Account accNo=>"0003", ]; for (Account acc in accounts) acc.insert(); } } { accType=>"Cheque", owner=>1] accType=>"Saving", owner=>1] accType=>"Saving", owner=>2] Now, suppose that we want to retrieve the name of every person who has an account, as well as the account number and type. Here is how we’d express such a query. protected static <text.sql.query native join() db=$testDb> select first_name, last_name, acc_no, acc_type from person, account where person.id = account.owner </text.sql.query> The following method exercises the join. public static void joinTest () { query ($testDb) { native rs = join(); while (sql.next(rs)) sys.println(sql.getRow@map(rs)); } } SQL Programming 161 It produces the following output. [$accNo=>"0001", $accType=>"Cheque", $firstName=>"Linda", $lastName=>"Black"] [$accNo=>"0002", $accType=>"Saving", $firstName=>"Linda", $lastName=>"Black"] [$accNo=>"0003", $accType=>"Saving", $firstName=>"Mark", $lastName=>"Adams"] 5.5 Calling Stored Procedures Most database servers (including MySql, as of version 5.0) support stored procedures (and stored functions). A stored procedure is a set of SQL statements stored in the database server for subsequent reuse. The advantage is runtime efficiency – once a procedure is defined, you no longer have to issue its SQL to the server, but simply refer to the stored procedure instead. To illustrate the creation and calling of stored procedure, let’s define a simple stored procedure named count_people() that counts the number of rows in the person table and returns this in an out parameter. We’ll also define a method for dropping the stored procedure. Both these are defined as members of the Person class: static <text.sql.update native createCountProc () db=$testDb> create procedure count_people (out rows int) begin select count(*) into rows from person; end </text.sql.update> static <text.sql.update native dropProc () db=$testDb> drop procedure if exists count_people </text.sql.update> To call the stored procedure, we can use a <test.sql.callable>, as shown below. singleton class People { int count; //... public void procCallTest () { query ($testDb) { native callable = callCountPeople(); sql.registerOutParam(callable, 1, "INTEGER"); sql.exec(callable); sys.println("Person count: ", count); } } <text.sql.callable native callCountPeople () db=$testDb> call count_people({?count}) </text.sql.callable> public static void main () { transaction ($testDb) { 162 JavaGram Agile Development //... Person.dropProc(); Person.createCountProc(); } People.singleton.procCallTest(); } } callCountPeople() returns a callable statement. We use sql.registerOutParam() to register the output parameter of count_people(), using its one-based index and expected type. Finally, we call sql.exec() to perform the call. 5.6 Streamlined Data Modeling Earlier, we looked at examples of providing persistence for the Person class. As you analyze your problem domain, other classes emerge, some of which would have relationships with Person. We’ve already seen an example of this (Account). Another obvious one would be Address to capture a person’s address information. If we were to continue with traditional entity relationship (ER) modeling, we would also provide a table for Address and one for capturing the one-to-many relationship between Person and Address, or have a foreign key in the address table to depict a person (as we did with Account) This style of ER modeling, while offering the greatest SQL composition flexibility, is rather time consuming and maintenance intensive – changes in the object model (e.g., adding a new field to Person) would typically require changes to the data model. Agile development requires a smarter approach. Suppose that our problem domain is a CRM application. We might conclude that the only retrieval scenarios required for a customer are via ID, last name, or date of birth. We might also conclude that addresses never need to be handled outside the context of a customer. This enables us to capture customer data in just one very simple table. <jag domain="doc/code/chap5"> class Address { string street; string town; string state; string zip; } class Customer { int id; string firstName; string lastName; date dob; string sex; vector<Address> addresses @= vector(); public static <text.sql.update int createTable () db=$testDb> create table if not exists customer ( SQL Programming 163 id integer not null, last_name varchar(16) not null, dob date, details text, primary key (id), index (last_name, dob) ) </text.sql> static <text.sql.update native dropTable() db=$testDb> drop table if exists customer </text.sql.update> public <text.sql.update int insert () db=$testDb> insert into customer (id, last_name, dob, details) values ({`id}, {`lastName}, {`sys.format(dob)}, {`sys.serialize(this)}) </text.sql.update> public void addAddress (Address addr) { sys.append(addresses, addr); } static public Customer getById (int id) { query ($testDb) { native rs = _getById(id); if (sql.next(rs)) return sys.parse(sql.get(rs, $details)@string) @ Customer; } return null; } private static <text.sql.query native _getById (int id) db=$testDb> select details from customer where id = {`id} </text.sql.query> static public void main () { transaction ($testDb) { dropTable(); createTable(); Customer cust = [@Customer id=>1, firstName=>"John", lastName=>"Smith", dob=>[#1992-10-22], sex=>"Male", addresses=>[]]; cust.addAddress([@Address street=>"24 Mark St", town=>"Balwyn", state=>"Vic", zip=>"3103"]); cust.addAddress([@Address street=>"15 High St", town=>"Kew", state=>"Vic", zip=>"3105"]); cust.insert(); } sys.println(getById(1)); } } </jag> The createTable() method creates a table where the first three columns capture the indexed attributes. insert() inserts a Customer instance into the table. Note how sys.serialize(this) is used to serialize the entire object into the last column. 164 JavaGram Agile Development Conversely, getByID() uses sys.parse() to reconstruct the Customer object by parsing the text in the last column. The program produces the following output: [@Customer addresses=>[[@Address state=>"Vic", street=>"24 Mark St", town=>"Balwyn", zip=>"3103"], [@Address state=>"Vic", street=>"15 High St", town=>"Kew", zip=>"3105"]], dob=>[#1992-10-22], firstName=>"John", id=>1, lastName=>"Smith", sex=>"Male"] The maintenance advantage of this approach should be obvious: adding new fields to the Customer class or the Address class will not affect the data model and will have minimal impact on the code. For lack of a better term, we call this approach streamlined data modelling. The initial reaction of experienced ER practitioners to this approach is often dismissive: ‘this goes against normalization and won’t perform.’ Analysing user requirements will quickly reveal where the performance hot spots are, and these can be easily accommodated through traditional ER modelling. For everything else, a streamlined approach will deliver a rapid, inexpensive, and easy-to-maintain solution. We can quantify this in rough terms. If you have a class that is likely to have millions of persistent instances that require to be retrieved in a number of different ways involving its relationships to other classes (i.e., needing SQL joins) then traditional ER modelling is your best bet. On the other hand, a class whose instances are in the thousands, or one that doesn’t involve complex relationship-based retrievals, is adequately served by the streamlined approach. There is another angle to the performance argument that often gets overlooked. A traditional, narrow view of performance sees it purely in terms of code execution speed. A broader, agile view would see it in terms of both development cycle and execution speed. Programs are not things that are developed once and used for years to come. They take much time to develop and even greater time and effort to keep operational while accommodating evolving requirements. Ignoring development speed in the overall equation is therefore unrealistic. 5.7 Business Object Model The Business Object Model (bom) pseudo class provides a convenient mechanism for defining simple business objects that require persistence in a database. To define a persistent business object, you subclass the generic library class lib/bom/Object and override some of its method. The persistence mechanics are managed by Object, eliminating the need for you to implement them directly and thus facilitating significant development productivity. 5.7.1 Subclassing Object Let’s redefine the Customer class of Section 5.6 as a subclass of Object. SQL Programming 165 <jag domain="doc/code/chap5"> <load> "lib/bom/Object" </load> class Address { setable string street; setable string town; setable string state; setable string zip; } class CustDetails { getable int id; setable string firstName; setable string lastName; setable date dob; setable string sex; } class Customer extends CustDetails, Object { vector<Address> addresses @= vector(); static { Object.defineTable(Customer, [ $db => $testDb , $table => $customer , $columns => [ $id => $id , $last_name => $lastName , $dob => $dob , $rest => [$fields=>[$firstName, $sex, $addresses], $compress=>false] ] ] ); } public Customer (CustDetails cust) { sys.assign(this, cust, CustDetails); } public static <text.sql.update int createTable () db=$testDb> create table if not exists customer ( id integer not null auto_increment, last_name varchar(16) not null, dob date, rest text, primary key (id), index (last_name, dob) ) </text.sql> public void addAddress (Address addr) { sys.append(addresses, addr); } } </jag> 166 JavaGram Agile Development For convenience, we’ve specified the customer fields in a separate class (CustDetails) so that we can easily pass it to the Customer constructor. Also, we’ve defined the table schema slightly differently this time (see the createTable() method). The id column is now defined as ‘auto increment’, which means that it’s controlled by the database server rather than the programmer – each time a new row is added this value is incremented. Note how Customer extends CustDetails and Object. The meta data required by Object is specified in a static block by calling Object.defineTable(). This meta data is specified as a map, which defines the database, the table, and the column mapping between Customer and its table. We’ve used the same streamlined data modeling approach as introduced in Section 5.6, by storing firstName, sex, and addresses fields in a single table column (rest). Here is a test method that exercises the persistence and searching functionality of Customer as supported by Object. static public void main () { transaction ($testDb) { dropTable(Customer, $testDb); createTable(); } Customer cust = new Customer([@CustDetails firstName=>"John", lastName=>"Smith", dob=>[#1992-10-22], sex=>"Male"]); cust.addAddress([@Address street=>"24 Mark St", town=>"Balwyn", state=>"Vic", zip=>"3103"]); cust.addAddress([@Address street=>"15 High St", town=>"Kew", state=>"Vic", zip=>"3105"]); cust.persist(); sys.println(Object.find(Customer)); cust.setFirstName("Peter"); cust.persist(); sys.println(Object.find(Customer, [$like=>[$lastName=>"Sm%"], $exact=>[$dob=>[#1992-10-22]]])); cust.setFirstName("John"); cust.refresh(); // Discard changes sys.println(Object.findOne(Customer, $id, 1)); cust.delete(); sys.println(Object.findOne(Customer, $lastName, "Smith")); } The persist() method writes the object to the database according to the meta data rules. The refresh() method does the opposite – it refreshes the object by reading it back from the database, causing any unsaved changes made to the object since the last save to be discarded. SQL Programming 167 Object provides these search methods: find(ClassName) performs a wildcard search, returning all instances of ClassName stored in its underlying table, as a vector. find(ClassName, criteria) returns all instances of ClassName that match the specified criteria, as a vector. The latter is specified as a map that may contain a $like and/or $exact submap, specifying field values that must match partially and/or exactly. find(ClassName, field, value) returns all instances of ClassName whose field has the specified value, or null if no match is found. findOne(ClassName, field, value) returns the first matching instance of ClassName whose field has the specified value, or null if no match is found. The delete() method permanently deletes the object from the database. 5.7.2 File Handling Sometimes, it makes sense to persist files to a database in the same way we persistent other data. For example, in an administration system, customer requests (such as application forms) may be scanned and the resulting images stored along with the customer data, or in a call centre, recorded conversations (i.e., audio files) may be stored with normal customer data. Storing these ‘attachments’ in a database is a far better approach than keeping them on a file system, especially in a distributed system where there may be many physical servers and a variety of file systems. The Object class provides support for this type of file persistence. We’ll present a sample class (called Document) that illustrates how you can easily implement this type of functionality. <jag domain="quest/bom"> <load> "lib/lang/Common" "lib/bom/Object" "lib/gui/GuiApp" "lib/gui/GuiUtil" </load> class User { string username; //... setable static User currUser; public User (string username) { this.username = username; } } class Document extends Object { getable int id; // Internal unique numeric document ID getable string name; // Document name setable string path; // Document path 168 JavaGram Agile Development static { defineTable(Document, [ $db => $testDb , $table => $document , $columns => [ $id => $id , $name => $name , $content => [$fields=>$path, $viaFile=>$binary, $extend=>true] ] ] ); } public static <text.sql.update int createTable () db=$testDb> create table if not exists document ( id integer not null auto_increment, name varchar(64), content mediumblob, primary key (id) ) </text.sql> public Document (string name, string path) { this.name = name; this.path = path; } protected slocal string getViaFilePath (symbol dbColumn, symbol field, int rowNum) { string dir @= sys.pathConc(sys.root, "temp/"); sys.createPath(dir); return sys.pathConc(dir, "Doc" + rowNum + "_" + name); } public static Document findById (vague id) { return Object.findOne(Document, $id, id)@Document; } public void save () { if (path == null) throw new Exception("no file path specified for " + name); if (!sys.pathExists(path)) throw new Exception(path + " doesn't exist"); try { // Save all fields, including file to 'content' column marked 'extend': setExtendOn(true); persist(); } finally { setExtendOn(false); } path = null; } public string retrieve () { try { // Get all fields, including file to 'content' column marked 'extend': setExtendOn(true); SQL Programming 169 refresh(); } finally { setExtendOn(false); } return path; } } </jag> The underlying document table has three fields: a primary id field, a name field, and a content blob field for storing binary document data. The important point to note is how the object schema specifies the content field in the static class initializer block: $content => [$fields=>$path, $viaFile=>$binary, $extend=>true] This specification states that content represents binary data sourced from a file, whose path is denoted by the path field of Document. It also specifies that content is not retrieved or stored unless the ‘extend’ feature of Object is switched on. The reason for the latter is that often when you perform a query on this table (e.g., to get a list of all the documents in the table), you don’t want the binary data to be also retrieved, as this data won’t be needed and will carry a significant overhead. In some other cases (e.g., to view a document), you want to retrieve this binary data. The Object.setExtendOn() method allows you to exercise this control, and the Object.isExtendOn() method allows you to get its current state (defaults to false). Note how the save() and retrieve() methods of Document ensure that ‘extend’ is on before they do their work and reset it back to off when they finish. This ensures that for normal queries, such as Object.find(), ‘extend’ is always off and therefore not retrieving any binary data. Another important point to note is the getViaFilePath() method. When you store a document in the database, its original path becomes irrelevant because we may be later retrieving the file on another computer with a completely different file system. Therefore, we don’t even store the file path – we just store the file name. The role of the path field of Document is to denote the original file path at the time of persistence. During retrieval, getViaFilePath() is internally called to determine the target path for the retrieved file. You must override this method and ensure that it returns a sensible file path. The three parameters of this method provide you with relevant information that you might want to use to construct a unique path name. In our example, we’ve used the JavaGram root, the document name, and the row number to construct a unique path. The method also needs to ensure that the directory of this path exists. Otherwise, JavaGram won’t be able to create the file later on. Here is a simple GUI application that exercises Document. 170 JavaGram Agile Development singleton class DocApp extends GuiApp { static final vector<map> TABLE_FORMAT @= [ [$key=>$id, $title=>"ID", $width=>20, $align=>$east] ,[$key=>$name, $title=>"Name", $width=>100, $align=>$west] ,[$key=>$path, $title=>"Retrieved Path", $width=>300, $align=>$west] ]; <App app lookAndFeel=$windows> <Frame frame title="Document App" width=400 height=200 event=frameHandler> <Panel> <Layout.border/> <Panel lay=$north> <Layout.flow align=$west/> <Button title="Add File..." action={addFile()}/> <Button title="View File" enable={table.select != null} action={viewFile()}/> <Button title="Remove File" enable={table.select != null} action={removeFile()}/> </Panel> <Pane.scroll> <Table table format={TABLE_FORMAT} autoSize=true event=tableHandler/> </Pane.scroll> </Panel> </Frame> </App> <FileChooser fc owner={GuiUtil.getMainFrame()} kind=$open title="Choose file(s)" path="C:/JavaGram/" label="Open"/> public DocApp () { super(frame); populate(); } protected void frameHandler (native comp, symbol event) { if (event == $close) exit(); } protected vague tableHandler (native comp, symbol event) { switch (event) { case $select: gui.maintain(frame); break; case $drill: viewFile(); break; } return null; } protected void addFile () { string path @= fc.show; if (path != null) { map<symbol, string> parts = sys.pathParts(path); Document doc = new Document(parts[$name] + '.' + parts[$ext], path); doc.save(); SQL Programming 171 populate(); } } protected void viewFile () { Document doc @= table.data[table.select@int]; string path = doc.retrieve(); table.refresh = true; Desktop.open(path); } protected void removeFile () { Document doc @= table.data[table.select@int]; doc.delete(); populate(); } protected void populate () { vector<Document> vec @= Object.find(Document); table.data = vec; table.refresh = true; } public static void main () { User.setCurrUser(new User("john")); transaction ($testDb) { //Object.dropTable(Document, $testDb); Document.createTable(); } DocApp.singleton.run(); } } When run, it displays the following frame (where we’ve already added two files). You add a file by pressing the Add File button, browsing to the desired file, and pressing Open. To view a file, select it in the table and press View File, or simply double-click its row. When a file is retrieved, the Retrieved Path column shows the path into which it’s been retrieved (as shown for ‘Test.doc’ in the above example). To remove a file, select its row and press Remove File. 172 JavaGram Agile Development The implementation of Document is intentionally minimal and intended to convey the concept rather than serve as a model implementation. Other considerations that a more complete implementation would need to deal with include: Client-server deployment. In this case, the file typically originates from the clientside, and needs to be sent to the server-side first and then stored in the database. Upon retrieval, the file needs to be sent back to the client side. Compression. Before storing a file in the database, we may want to compress it to minimize its size. After retrieving it, we would need to decompress it. Locking. For editable files, a locking mechanism would be required so that multiple users cannot concurrently modify the same file. The Quest sample application described in Chapter 8 implements all the above for attachments. 5.7.3 Locking Persisted objects are often subject to concurrent access. Where an application retrieves an object for read-only purposes, this poses no issue. However, where users are allowed to modify objects, some form of locking is required. For example, suppose that in a CRM application, a user retrieves a customer and attempts to update the details. If two users do this concurrently for the same customer, the outcome would be potentially inconsistent, as illustrated by the following scenario: User A retrieves customer C. User B retrieves customer C. User A changes the customer’s title from ‘Mrs’ to ‘Ms’, and saves the change. User B changes the customer’s marital status from ‘Married’ to ‘Divorced’ and saves the change. As a result of this sequence, the customer’s title remains incorrectly as ‘Mrs’ To avoid this, the application should lock the object as soon as the user attempts to modify it. Furthermore, locking should cause the object to be refreshed from the database, to ensure that the user is working on an up-to-date copy. Once the user has saved the changes, the object should be unlocked, so that it becomes updatable by other users. If a user attempts to update an object that’s currently locked by another user, the application should detect and block the attempt. SQL Programming 173 This scheme is implemented by the UserLockableObject library class, which in turn extends Object. Therefore, if you need a locking mechanism for a BO, you should derive it from this class instead of Object. Additionally, you should define a ‘lock’ column for the underlying table. This should have a primitive type (e.g., integer, string, date) – UserLockableObject treats this as a value of type vague. To illustrate how this works, let’s redefine the Document class of the previous section so that it supports locking. We’ll use a dummy User class to represent users. <jag domain="quest/bom"> <load> "lib/lang/Common" "lib/bom/Object" "lib/bom/UserLockableObject" "lib/gui/GuiApp" "lib/gui/GuiUtil" </load> class User { getable string username; //... setable static User currUser; public User (string username) { this.username = username; } } class Document extends UserLockableObject { getable int id; // Internal unique numeric document ID getable string name; // Document name setable string path; // Document path static { defineTable(Document, [ $db => $testDb , $table => $document , $columns => [ $id => $id , $name => $name , $content => [$fields=>$path, $viaFile=>$binary, $extend=>true] ] , $lock => [$column=>$locked_by, $style=>$byUser] ] ); } public static <text.sql.update int createTable () db=$testDb> create table if not exists document ( id integer not null auto_increment, name varchar(64), content mediumblob, locked_by varchar(16), 174 JavaGram Agile Development primary key (id) ) </text.sql> public Document (string name, string path) { this.name = name; this.path = path; } protected slocal string getViaFilePath (symbol dbColumn, symbol field, int rowNum) { string dir @= sys.pathConc(sys.root, "temp/"); sys.createPath(dir); return sys.pathConc(dir, "Doc" + rowNum + "_" + name); } public static Document findById (vague id) { return Object.findOne(Document, $id, id)@Document; } public void save () { if (path == null) throw new Exception("No file path specified for " + name); if (!sys.pathExists(path)) throw new Exception(path + " doesn't exist"); string lockedBy @= lockedBy(); if (lockedBy != null && lockedBy != User.getCurrUser().getUsername()) throw new Exception("Document is locked by " + lockedBy); try { // Save fields, including file to 'content' column which is marked 'extend': setExtendOn(true); persist(); if (lockedBy != null) unlock(lockedBy); } finally { setExtendOn(false); } path = null; } public string retrieve () { try { // Get fields, including file to 'content' column which is marked 'extend': setExtendOn(true); refresh(); } finally { setExtendOn(false); } return path; } } </jag> Note how we’ve added the locked_by column to the document table, and specified it in the object schema as being used for a ‘lock by user’ style of object locking: SQL Programming 175 $lock => [$column=>$locked_by, $style=>$byUser] The only other major changes are to the save() method, where we first check that the object hasn’t been locked by another user and, after persisting the object to the database, we release our lock on it, if any. Let’s also extend our GUI test program to exercise the locking mechanism. singleton class DocApp extends GuiApp { static final vector<map> TABLE_FORMAT @= [ [$key=>$id, $title=>"ID", $width=>20, $align=>$east] ,[$key=>$name, $title=>"Name", $width=>100, $align=>$west] ,[$key=>$path, $title=>"Retrieved Path", $width=>300, $align=>$west] ]; static User user1 = new User("john"); static User user2 = new User("linda"); <App app lookAndFeel=$windows> <Frame frame width=450 height=200 event=frameHandler> <Panel> <Layout.border/> <Panel lay=$north> <Layout.flow align=$west/> <Button title="Add File..." action={addFile()}/> <Button title="View File" enable={table.select != null} action={viewFile(false)}/> <Button title="Edit File" enable={table.select != null} action={viewFile(true)}/> <Button title="Save File" enable={table.select != null} action={saveFile()}/> <Button title="Remove File" enable={table.select != null} action={removeFile()}/> <Button title="Change User" action={changeUser()}/> </Panel> <Pane.scroll> <Table table format={TABLE_FORMAT} autoSize=true event=tableHandler/> </Pane.scroll> </Panel> </Frame> </App> <FileChooser fc owner={GuiUtil.getMainFrame()} kind=$open title="Choose file(s)" path="C:/JavaGram/" label="Open"/> public DocApp () { super(frame); changeUser(); populate(); } protected void frameHandler (native comp, symbol event) { if (event == $close) exit(); 176 JavaGram Agile Development } protected vague tableHandler (native comp, symbol event) { switch (event) { case $select: gui.maintain(frame); break; case $drill: viewFile(false); break; } return null; } protected void addFile () { string path @= fc.show; if (path != null) { map<symbol, string> parts = sys.pathParts(path); Document doc = new Document(parts[$name] + '.' + parts[$ext], path); doc.save(); populate(); } } protected void viewFile (boolean edit) { Document doc @= table.data[table.select@int]; string path = doc.retrieve(); table.refresh = true; if (edit) { string lockedBy @= doc.lockedBy(false); if (lockedBy == null) { doc.lock(User.getCurrUser().getUsername()); } else if (lockedBy != User.getCurrUser().getUsername()) { gui.alert("Can't Edit", "File locked by " + lockedBy, GuiUtil.getMainFrame(), $error); return; } } Desktop.open(path); } protected void saveFile () { Document doc @= table.data[table.select@int]; try { doc.save(); } catch (Exception e) { gui.alert("Can't Save", e.getMessage(), GuiUtil.getMainFrame(), $error); } } protected void removeFile () { Document doc @= table.data[table.select@int]; doc.delete(); populate(); } SQL Programming 177 protected void changeUser () { User.setCurrUser(User.getCurrUser() == user1 ? user2 : user1); frame.title = "Doc App - User: " + User.getCurrUser().getUsername(); } protected void populate () { vector<Document> vec @= Object.find(Document); table.data = vec; table.refresh = true; } public static void main () { transaction ($testDb) { //Object.dropTable(Document, $testDb); Document.createTable(); } DocApp.singleton.run(); } } The new user interface looks like this: The Edit File button calls viewFile(true) which attempts to obtain a lock on the object if it’s not already locked. Conversely, the Save File button calls the save() method of the object, which persists it and releases the lock only if the object is locked by the current user. To properly test all conditions, we’ve also added a Change User button which toggles the current user between two pre-created users (John and Linda). When you first run the application, the current user is set to John. At this point, edit a file, but don’t press the Save File button, so that the object remains locked. Now press the Change User button to switch the current user to Linda. Now attempt to edit the file again – you’ll notice that this will be blocked and the application will complain that the object is locked by John. You can test other conditions in a similar manner to verify that locking behaves as expected. 178 JavaGram Agile Development 6 Advanced Topics This chapter describes a number of advanced programming concepts, some of which are unique to JavaGram. These concepts are specifically aimed at the development of 3-tier distributed applications. The tiers being: data tier (embodied by data sources, such as database servers), business functionality tier (embodied by application servers), and presentation tier (embodied by clients, whose code are dynamically delivered by application servers). The actual deployment options and models for distributed applications are discussed in the next chapter. This chapter focuses on the underlying programming techniques that make such deployments possible. 6.1 Client-Server Communication The examples we’ve seen so far in this book deal with monolithic applications, where the application runs as a single process. We call such applications standalone because they can run on a single machine. Standalone applications can support a limited client-server model, where the application is deployed on a number of machines, each of which interacts via SQL with a database server running on yet another machine. This is called a 2-tier client-server model, because of the physical separation of the database server and the application – the latter is often called a fat client. This model offers limited scalability because each fat client instance is limited by the resources available on its host machine. The 3-tier model introduces a middle-tier – called an application server – that hosts the bulk of the business logic, thus reducing the load on each client instance. The JavaGram code residing in the middle tier contains both the business logic (which will run in the middle tier) and the presentation code (which will be transferred to and run in each client). The application server dynamically compiles these two types of binaries from a common source code – hence the term hybrid client. The client instances communicate with the application server via JavaGram Transfer Protocol (JTP), which is somewhat similar to HTTP. Advanced Topics 179 The important point to note here is that there is one overall source code for the application, and this code is released only to the middle tier. JavaGram manages the rest. This is fundamentally different from other client-server programming paradigms, where the middle tier and the client are coded separately, which in turn creates the additional overhead of keeping the two in sync for the rest of the program’s operational lifetime. JavaGram completely hides the JTP client-server communication details from the programmer. The programmer works in terms of a much simpler conceptual model that deals with remoting – the ability to distinguish between what runs locally and what runs remotely – in a declarative style. 6.1.1 Remote Methods When you define a method in a class, you can use the remote qualifier to specify that it should run on a server. The significance of this qualifier depends directly on how the program is deployed. There are three possibilities: If the program is deployed to run as standalone, then this qualifier has no significance. If the program is deployed as client-server and the method’s class is sourced locally then the qualifier has no significance. If the program is deployed as client-server and the method’s class is sourced from a server then the qualifier ensures that the method will execute on that server, not locally. To avoid confusion, note that ‘client’ and ‘server’ are relative terms. For example, consider the following diagram. Suppose that Client obtains its code from Server A, which in turn obtains its code from Server B. In this scenario, Server A is a server for Client, but a client of Server B. Therefore, Server A is both a server and a client. As a general rule, if a method foo() is sourced from a server, it will run on that server regardless of whether it’s called from that server or elsewhere. Recall the Person class from the previous chapter. We’ll rewrite this class with the intention of running it in client-server mode. 180 JavaGram Agile Development <jag domain="doc/code/chap6"> class Person { protected getable int id; protected setable string firstName; protected setable string lastName; protected setable string sex; protected setable date dob; protected setable boolean married; public remote void insert () { transaction ($testDb) { _insert(); native rs = _uniqueId(); id @= sql.next(rs) ? sql.get(rs, $id) : 0; } } public remote void update () { transaction ($testDb) { _update(); } } public remote void delete () { transaction ($testDb) { _delete(id); id = null; } } public remote void refresh () { sys.assign(this, find(id), Person); } public remote static Person find (int id) { query ($testDb) { native rs = _find(id); return sql.next(rs) ? sql.getRow@Person(rs) : null; } } public remote static vector<Person> find () { query ($testDb) { native rs = _find(); vector<Person> vec @= vector(); while (sql.next(rs)) sys.append(vec, sql.getRow@Person(rs)); return vec; } } public remote static void resetTable () { // Because this is a test, we drop the tables and re-create them: transaction ($testDb) { Person.dropTable(); Person.createTable(); Advanced Topics 181 } } protected <text.sql.update int _insert () db=$testDb> insert into person (first_name, last_name, sex, dob, married) values ({`firstName}, {`lastName}, {`sex}, {`sys.format(dob)}, {`married}) </text.sql.update> protected <text.sql.update int _update () db=$testDb> update person set first_name = {`firstName}, last_name = {`lastName}, sex = {`sex}, dob = {`sys.format(dob)}, married = {`married} where id = {`id} </text.sql.update> protected static <text.sql.update int _delete (int id) db=$testDb> delete from person where id = {`id} </text.sql.update> protected static <text.sql.query native _uniqueId () db=$testDb> select id from person where id is null </text.sql.query> protected static <text.sql.query native _find (int id) db=$testDb> select * from person where id = {`id} </text.sql.query> protected static <text.sql.query native _find () db=$testDb> select * from person </text.sql.query> static <text.sql.update native createTable() db=$testDb> create table if not exists person( id integer not null auto_increment, first_name varchar(16), last_name varchar(16), sex varchar(8), dob date, married bit, primary key(id), index(last_name)) </text.sql.update> static <text.sql.update native dropTable() db=$testDb> drop table if exists person </text.sql.update> } </jag> We’ve defined the table schema slightly differently this time (see the createTable() method). The id column is now defined as ‘auto increment’, which means that it’s controlled by the database server rather than the programmer – each time a new row is added this value is incremented. The class contains these remote methods: 182 JavaGram Agile Development insert() calls _insert() to perform the SQL insertion and then calls _uniqueId() to obtain the ID allocated by the database, to update the object’s id. update() calls _update() to perform the SQL to store the current state of the object in the database. delete() calls _delete() to issue the SQL for deleting the object from the database. refresh() refreshes the object from the database by finding it and using sys.assign() to copy its fields to the object. The class also contains these static remote methods: find(id) finds a Person using its unique ID. find() finds all instances of Person in the database. resetTable() deletes and recreates the person table for testing purposes. Here is a test program for exercising this class. singleton class People { public static void populatePerson () { vector<Person> persons = [ [@Person firstName=>"Linda", lastName=>"Black", sex=>"Female", dob=>[#1980-12-20], married=>true] ,[@Person firstName=>"Mark", lastName=>"Adams", sex=>"Male", dob=>[#1988-02-13], married=>false] ]; for (Person p in persons) p.insert(); } public static void main () { Person.resetTable(); populatePerson(); Person p = Person.find(1); sys.println(p); p.setMarried(false); p.update(); sys.println(p); p.setFirstName("Belinda"); p.refresh(); // Discard changes sys.println(p); p.delete(); sys.println(p); p = Person.find(1); sys.println(p); } } The program produces the same output whether run in standalone or client-server mode: Advanced Topics 183 [@doc\code\chap6\Person dob=>[#1980-12-20], firstName=>"Linda", lastName=>"Black", married=>true, sex=>"Female"] [@doc\code\chap6\Person dob=>[#1980-12-20], firstName=>"Linda", lastName=>"Black", married=>false, sex=>"Female"] [@doc\code\chap6\Person dob=>[#1980-12-20], firstName=>"Linda", lastName=>"Black", married=>false, sex=>"Female"] [@doc\code\chap6\Person dob=>[#1980-12-20], firstName=>"Linda", lastName=>"Black", married=>false, sex=>"Female"] Null id=>1, id=>1, id=>1, id=>null, To run the program in client-server mode, you need to first define a configuration file for the server. Here is a minimal server configuration file. [ $download=>[ $default=>["jag"=>$private, "*"=>$public] , $cache=>"mycache/" ] , $database=>[ $testDb=>[ $name=>"Test Database" , $url=>"jdbc:mysql://localhost/test" , $user=>"root" , $password=>"password" , $timeout=>[$login=>30, $query=>20] , $driver=>"com.mysql.jdbc.Driver" ] ] ] The $database part is as explained in the previous chapter. The $download part specifies two things: The $default download rule states that source files (.jag) are private and therefore not downloadable, but everything else (including compiled .jax files) is public and therefore downloadable. The $cache key nominates the server’s cache directory. This is where the server places compiled files for client and server ends. The standard method of running an application server is to use the lib/svr/AppServer.jag library script. For example: java -cp "C:/JavaGram/jar/JAG.jar;C:/JavaGram/src/jar/mysql-connector.jar" jag.run.svr.Server -root "C:/JavaGram/src" -config "doc/code/chap6/Config.jag" host "localhost:443" "lib/svr/AppServer.jag" Note how we’ve specified the server’s URL using the –host option. This states that the server will run on the localhost and listen to client requests on port 433. When you run this server, you should initially get the following output. [2009-08-07 11:19:16] <main> Root set to: C:/JavaGram/src [2009-08-07 11:19:16] <main> Config file set to: C:/JavaGram/src/doc/code/chap6/Config.jag 184 JavaGram Agile Development =============== APP SERVER STARTING =============== [2009-08-07 11:19:16] <main> Synchronous server started on localhost:443 [2009-08-07 11:19:20] <Session-1> Session started: id=1, alive=1, client=/127.0.0.1:1804 [2009-08-07 11:19:20] <Session-1> Session closed: id=1, alive=0, client=/127.0.0.1:1804 The server is now ready to receive client connections. You boot a client against a server by nominating the server’s URL and the initial script to be executed. For example: java -cp "C:/JavaGram/jar/JAG.jar" jag.run.Env -root "C:/JagClient" -host "localhost:443" -stage prod -boot "doc/code/chap6/Person" The initial script is specified using the –boot option. Note that we don’t need to specify the file’s extension. The client requests the Person script from the server. If this is the first time the server has received such a request, it will locate and compile Person.jag to produce two Person.jax compiled versions: one for client’s use (from which all remote method implementations have been stripped) and one for the server’s use (from which all GUI implementations have been stripped). The former is sent to the client which the client then loads and executes. Now consider the call Person.find(1). Because this is a remote call, it’s not executed on the client-side. The client sends a request to the server to perform this call. In response, the server loads its own version of Person.jax and then performs the call. The return value is a Person object which the server serializes and sends back to the client. The client converts this serialized data into a Person object which it then uses as the return value of the call. Now consider the call p.update(). This is also a remote call, but to a non-static method. The protocol is as before, plus two additional steps: The object p is serialized and sent to the server along with the call. The server converts this serialized data into a Person object and makes the call on that object. When the call returns, the server serializes the object and returns it back to the client. The client converts this serialized data into a Person object and uses it to update the original object. In other words, JavaGram manages calls to remote methods (be they static or non-static) such that the semantics of the call are honored, regardless of the deployment model. This level of consistency is essential in ensuring that the programmer is not overburdened by different rules for different circumstances. 6.1.2 Remote Classes As mentioned earlier, non-static remote calls involve the transportation of the object between the client and the server ends. Sometimes this is not desirable and we prefer to Advanced Topics 185 keep an object permanently on the server side whilst still wanting to make its methods accessible to the client. Reasons may include: The large size of the object might make it unsuitable for timely transportation. There may be security concerns around the private information held within the object. The object may have resource dependencies that tie it to the server end. The object may be a singleton. Remote classes provide an elegant solution for such scenarios. Let’s look at an example. Imagine an online chat-room where individuals can jump in freely and take part in textbased conversations. First, we need a class to represent a chat room member. class Member { getable int id; getable string name; getable date joined = sys.date(); setable int refreshLen = 0; static int lastId = 0; public Member (string name) { id = ++lastId; this.name = name; } public string toString () { return name + "#" + id; } } Each member has a name and is allocated a unique numeric ID, and a record of their joining time. The chat room itself is defined as a remote class. remote class ChatRoom { string name; map<int, Member> people @= map(); vector<string> chat @= vector(); static map<string, ChatRoom> rooms @= map(); // // // // Room name Member ID => Member Everything spoken so far All the chatrooms public ChatRoom (string name) { this.name = name; } public void release () { sys.println("ChatRoom.release() called"); sys.remove(rooms, name); } public int join (string name) { Member mem = new Member(name); people[mem.getId()] = mem; say(mem.getId(), $"<joined {this.name}>"); 186 JavaGram Agile Development return mem.getId(); } public void leave (int id) { say(id, $"<left {name}>"); sys.remove(people, id); } public void say (int id, string msg) { Member mem = people[id]; string ts = sys.format(sys.date(), "dd MMM, HH:mm:ss"); sys.append(chat, $"[{ts} {mem.toString()}]: {msg}"); } public vector<string> refresh (int id) { Member mem = people[id]; int len = sys.length(chat); if (mem.getRefreshLen() >= len) return null; vector<string> vec @= vector($"Refresh for {mem.getName()}:"); for (int i = mem.getRefreshLen(); i < len; ++i) sys.append(vec, chat[i]); mem.setRefreshLen(len); return vec; } public static ChatRoom getRoom (string name) { ChatRoom room = rooms[name]; if (room == null) room = rooms[name] = new ChatRoom(name); return room; } } Note that this class can potentially contain a lot of data – it keeps track of everyone who joins as well as a complete record of all conversations. If we were to allow an instance of this class to move between client and server ends, it would create a performance overhead that would deteriorate over time. Every method of a remote class is, by definition, remote. The join() method allows a person to join a chat room as a member. Conversely, leave() removes the member. The say() method is invoked when the individual wants to say something. This is timestamped and simply added to the chat trail. As a chat progresses, each individual would want to see what’s been said by others. The refresh() method does this optimally. It keeps track of the last refresh point for the member and returns everything said after that point. Finally, a chat room is initially accessed by calling getRoom(), which returns the room if it exists, or creates a new one. An important point to bear in mind is that a remote class cannot be instantiated on the client side because such instances can only reside on the server side. Therefore, you always need a means of getting a remote class instance from Advanced Topics 187 the server itself. This can be either a static method in the remote class itself (as we’ve done her) or a remote method of another class. Here is a sample program for exercising our remote class. class TestChatRoom { static ChatRoom room = ChatRoom.getRoom("blue"); public static void showRefresh (int id) { for (string str in room.refresh(id)) sys.println(str); } public static void main () { int john = room.join("john"); int anne = room.join("anne"); room.say(john, "What a beautiful day"); int sarah = room.join("sarah"); room.say(anne, "Hmmm, not here - it's hailing!"); showRefresh(anne); room.say(sarah, "Can we stop chatting about the weather?"); room.say(anne, "OK, let's talk politics"); showRefresh(john); room.leave(sarah); showRefresh(anne); sys.release(room); } } It produces the following output. Refresh for anne: [07 Aug, 16:29:55 [07 Aug, 16:29:55 [07 Aug, 16:29:55 [07 Aug, 16:29:55 [07 Aug, 16:29:55 Refresh for john: [07 Aug, 16:29:55 [07 Aug, 16:29:55 [07 Aug, 16:29:55 [07 Aug, 16:29:55 [07 Aug, 16:29:55 [07 Aug, 16:29:55 [07 Aug, 16:29:55 Refresh for anne: [07 Aug, 16:29:55 [07 Aug, 16:29:55 [07 Aug, 16:29:56 john#1]: <joined blue> anne#2]: <joined blue> john#1]: What a beautiful day sarah#3]: <joined blue> anne#2]: Hmmm, not here - it's hailing! john#1]: <joined blue> anne#2]: <joined blue> john#1]: What a beautiful day sarah#3]: <joined blue> anne#2]: Hmmm, not here - it's hailing! sarah#3]: Can we stop chatting about the weather? anne#2]: OK, let's talk politics sarah#3]: Can we stop chatting about the weather? anne#2]: OK, let's talk politics sarah#3]: <left blue> Internally, JavaGram manages remote objects according to these rules: 188 JavaGram Agile Development A remote object is allocated a unique ID. When a client obtains a reference to a remote object, it gets a proxy object instead. Any operation applied to the proxy object on the client side is communicated to the server side and applied to the remote object instead. Remote objects are reference-counted on the server side. When the reference count reaches zero, the server automatically calls the release() method of the remote class (if defined). This gives the class the opportunity to release any resources tied up by the object. At the same time, the object is permanently removed from the server cache. JavaGram keeps track of the remote objects accessed by a client (within the client’s server-side session). When the client exits, the reference counts of these objects are automatically decremented. Additionally, a client can call sys.release() on a remote object that it no longer needs, causing the object’s reference count to be decremented on the server side. Once the client releases a proxy object, it can no longer use it. Remote objects can be shared by server-side client sessions, so it’s possible for multiple client sessions to concurrently access the same remote object. In such cases, you should use synchronization (described later in the chapter) to avoid concurrency issues. 6.1.3 Exception Handling When working with remote methods or classes, the normal exception handling rules apply. For example, if you call a remote method and it fails on the server-side, the exception will be automatically transferred to the client-side and delivered to the caller. In other words, the programmer doesn’t need to take any special steps to handle potential exceptions for remote calls. If a client process is terminated abnormally, JavaGram will detect this and terminate the corresponding server-side session. This prevents server-side resources from being unnecessarily tied up. Conversely, if a server-side session terminates abnormally, the client will detect this and prompt the user. 6.1.4 Targeted Remote Calls A client can connect to multiple servers. Of these, one is the default server – the one that the client was originally booted against – whose connection is always denoted by sys.loader. As a general rule, when you make a remote call, JavaGram will send the call to the server from which the method’s class was originally sourced – we call this the class’s source server which, in most cases, is the same as the default server. There may be legitimate cases where you want to source your code from one server and use that code to issue calls against other servers. You can do this using a targeted remote call. This is just like a normal remote call, but explicitly specifies the target server using this syntax: Advanced Topics 189 TargetServerStream::MethodName (Arguments) For example, suppose that you have a distributed application for recording and reporting summary sales results of a company. For ease of understanding, we’ve represented this as a very simple class below. The getSummary() remote method fakes its data so that we can easily get different data on different servers. To try this example, you need to run three servers, all running on localhost using ports 443, 444, and 446, and a client against localhost:443. <jag domain="doc/code/chap6"> class Summary { string lob; // Line of business date from; // Start date date to; // End date real inflows; real outflows; public static remote Summary getSummary (date from, date to) { string lob = sys.subStr(sys.host, 5); // Fake a LOB name int n = sys.date()@int; real inflows = (n % 80) * 400; // Fake a figure real outflows = (n % 30) * 300; // Fake a figure return object(Summary, lob=>lob, from=>from, to=>to, inflows=>inflows, outflows=>outflows); } public static void main () { vector<stream> hosts = vector( sys.loader ,sys.client("localhost", 444) ,sys.client("localhost", 446) ); date from = [#2009-8-1]; date to = [#2009-8-31]; sys.println("Sales summary for ", sys.format(from), " to ", sys.format(to)); sys.println("LOB\t\t\tINFLOW \tOUTFLOW\tNET"); real inTotal = 0.0; real outTotal = 0.0; for (stream host in hosts) { Summary s = host::getSummary(from, to); inTotal += s.inflows; outTotal += s.outflows; sys.println(s.lob, '\t', s.inflows, '\t', s.outflows, '\t', s.inflows - s.outflows); } sys.println("\t\t\t-------\t-------\t-------"); sys.println("TOTAL:\t\t", inTotal, '\t', outTotal, '\t', inTotal - outTotal); for (stream s in hosts) { if (s != sys.loader) sys.close(s); } 190 JavaGram Agile Development } } </jag> In the main() method, we create explicit client connections to localhost:444 and localhost:446 using sys.client(). These two connections plus the default connection give us three servers to issue requests against. We then loop through these and invoke getSummary() against each server, and output the result returned by each server, which looks something like this: LOB host:443 host:444 host:446 TOTAL: INFLOW 29200.0 3600.0 9600.0 ------42400.0 OUTFLOW 3900.0 8700.0 4200.0 ------16800.0 NET 25300.0 -5100.0 5400.0 ------25600.0 Finally, we close the explicit server connections by calling sys.close(). 6.1.5 Clocal, slocal, and side We’ve already seen how private, protected, and public qualifiers can be used to specify the visibility of a class’s members with respect to other classes. There are two more qualifiers that control visibility, but with respect to the client-server boundary: The clocal qualifier can be applied to a class or its methods to make them local to a client, thus making them inaccessible to servers. Conversely, the slocal qualifier can be applied to a class or its methods to make them local to a server, thus making them inaccessible to clients. These two qualifiers do not alter the semantics of a class or its members, but provide useful protection against inadvertent access outside their intended environment. You can also limit an entire script’s visibility to the client or server side by setting its side property in the <jag> element to $client or $server (defaults to $both). Obviously, clocal and slocal are only meaningful for scripts that are available on both sides. If such a script, for instance, has methods that refer to GUI elements, it would be sensible to define them as clocal. On the other hand, methods that are intended to perform SQL on the server side would be obvious candidates for slocal qualification. For example, the Account class (defined in Chapter 5) would be better defined as: class Account { string accNo; string accType; int owner; // ID of Person who owns the account slocal static <text.sql.update native createTable() db=$testDb> Advanced Topics 191 create table if not exists account( acc_no varchar(16) not null, acc_type varchar(16), owner integer not null, primary key(acc_no), index(owner)) </text.sql.update> slocal static <text.sql.update native dropTable() db=$testDb> drop table if exists account </text.sql.update> slocal <text.sql.update int insert () db=$testDb> insert into account (acc_no, acc_type, owner) values ({`accNo}, {`accType}, {`owner}) </text.sql.update> } 6.2 Threads A JavaGram thread (a thread of execution within a program) is a wrapping of its underlying Java thread and follows similar implementation rules. Multiple threads can execute concurrently within the same process, each having its own stack, but all sharing the same static data. A running program contains a main thread and potentially other threads for managing ancillary activities, such as garbage collection and GUI event dispatching. An application can create additional threads for handling application specific tasks, such as bulk reporting. 6.2.1 Working with Threads To create a thread, subclass the Thread library class, defined in lib/lang/Common.jag. This class has an abstract run() method that you need to implement. abstract class Thread { public Thread (string name, boolean daemon = false) { //... } abstract protected vague run (); //... } As suggested by the constructor, all threads are named, and can be either defined as daemon or user thread. The Java Virtual Machine continues running until all remaining threads are daemon threads. Here is a simple program that demonstrates the concurrent behavior of threads. It creates two threads and starts them. The run() method iterates three times, each time outputting a message about the thread, and finally returns the thread name as a symbol. <jag domain="doc/code/chap6"> <load> "lib/lang/Common" 192 JavaGram Agile Development </load> class MyThread extends Thread { public MyThread (string name) { super(name, false); } protected vague run () { for (int i = 1; i <= 3; ++i) sys.println("In thread: " + getName()); return sys.strSym(getName()); } public static void main () { MyThread mt1 = new MyThread("first"); MyThread mt2 = new MyThread("second"); mt1.start(); mt2.start(); mt1.join(500); mt2.join(500); sys.println($"mt1 result = {mt1.getResult()}"); sys.println($"mt2 result = {mt2.getResult()}"); } } </jag> A thread is started by calling its start() method. Once the run() method of the thread returns, the thread dies. The join() method waits for up to a given length of time (specified in milliseconds) until the thread dies. The getResult() method returns the final result returned by the thread’s run() method, or null if it’s still running. The output of this program will look something like this: In thread: In thread: In thread: In thread: In thread: In thread: mt1 result mt2 result second first first second first second = $first = $second You can interrupt a running thread by calling its interrupt() method. To find out if a thread is still running, call its isAlive() method. Within the run() method, if you want to give other threads execution priority, call the yield() method. It’s important to note that once a thread dies, it can’t be reused. If you want to rerun the thread, you must create another instance of the class and call start() on it. Within a JavaGram application server, each client session is represented by a thread. The run() method of this thread is an infinite loop that receives client requests and responds to them, exiting only when the client terminates. Advanced Topics 193 6.2.2 Synchronization Because threads can concurrently access static data, care needs to be taken to avoid unwanted concurrency issues. For example, if multiple threads modify a static vector, you need to make sure that only one thread at a time is allowed to do so. Otherwise, the result will be unpredictable or may even cause runtime errors. You can protect a section of code (called critical section) from concurrent access using the synchronized statement: synchronized (obj) { //...critical section... } You can use any JavaGram value for obj. When a thread attempts to execute this statement, it first requests a lock on obj. This lock is granted only if no other thread is executing the critical section. Once the successful thread has executed the critical section, this lock is released so that other threads can go through the same process. Alternatively, you can make the body of an entire non-static method synchronized using the synchronized qualifier: synchronized void foo () { //... } This is equivalent to writing: void foo () { synchronized (this) { //... } } Note that in both cases, this is used for locking purposes, which is effective only if the threads operate on the same object. Here is a modified version of our earlier example, where two threads use synchronization to append data to a static vector. class MyThread extends Thread { static vector vec = vector(); //... protected vague run () { for (int i = 1; i <= 3; ++i) { synchronized (vec) { sys.append(vec, getName()); 194 JavaGram Agile Development } } return sys.strSym(getName()); } } When using synchronization, you must be careful not to introduce a deadlock situation. A deadlock occurs when two threads are waiting for each other to release locks so that the other can continue, causing the program to hang indefinitely. Here is a contrived example to illustrate the point. void foo1 () { synchronized (one) { if (!b) foo2(); } } void foo2 () { synchronized (two) { if (b) foo1(); } } Suppose that in thread A, b is false and a call is made to foo1(). Also suppose that in thread B, b is true and a call is made to foo2(). Here is what may happen next: Thread A gets a lock on one. Thread B gets a lock on two. Thread A executes foo2() inside the if statement. Thread B executes foo1() inside the if statement. Thread A is blocked on two, because B has a lock on it. Thread B is blocked on one, because A has a lock on it. The only practical remedy to a deadlock is to recode it such that the deadlock situation is avoided. 6.2.3 ThreadLocal Fields Sometimes it’s desirable to have a static field whose value is local to each thread. In other words, you want the value to behave statically within each thread, but you don’t want it shared across threads. The ThreadLocal class defined in lib/lang/Common.jag supports this behavior. To illustrate its effect, let’s use it in our thread example. Advanced Topics 195 class MyThread extends Thread { static vector vec = vector(); static ThreadLocal tl = new ThreadLocal(null); //... protected vague run () { tl.set(getName()); for (int i = 1; i <= 3; ++i) { synchronized (vec) { sys.append(vec, getName()); sys.println(tl.get()); } } return sys.strSym(getName()); } Here, we’re setting the tl value before the loop and outputting it inside the loop. The program output will look something like this: first second second second first first mt1 result = $first mt2 result = $second vec = ["first", "second", "second", "second", "first", "first"] As demonstrated by the output, each thread is keeping a separate value for tl, even though it’s static. 6.2.4 Timer Class We saw in Chapter 4 how the <Timer> GUI element can be used to manage periodic tasks that access GUI components. There is also a Timer class defined in lib/lang/Common.jag that can be used for non-GUI purposes. This is often used on the server side to manage periodic tasks. Internally, both <Timer> and Timer use a separate thread that sleeps until the timer fires. Timer has an associated class called TimerTask. To define a periodic task, you subclass TimerTask and override its run() method. The task can then be scheduled using the timer. The same timer can be used to schedule any number of tasks. As an example of how to use Timer, suppose that you have a table where draft transactions are written, with the expectation that another thread will periodically poll this table, perform the recent draft transactions, and remove them from the table. Given that none of this involves a GUI, we can implement the scheme using a Timer. 196 JavaGram Agile Development <jag domain="doc/code/chap6"> <load> "lib/lang/Common" </load> class PollTask extends TimerTask { public PollTask (string name) { super(name); } protected vague run () { sys.println("Running task: " + getName()); // TODO: poll a transaction table and act on it. return null; } public static void main () { Timer timer = new Timer("polling timer"); PollTask task = new PollTask("txn polling"); timer.schedule(task, sys.date(), 1000); sys.sleep(2000); sys.println(sys.date(task.scheduledExecTime())); timer.cancel(); sys.println("Done"); } } </jag> To keep this example simple, we’ve excluded the internal implementation of run() for polling the transaction table, and instead just output a message. The main() method creates an instance of Timer and an instance of PollTask, and register’s the latter by invoking the schedule() method of the former. The second argument of schedule() specifies the start time of the scheduled task (or an initial delay in milliseconds), and the final argument specifies the timer’s interval expressed in milliseconds for running the task. So, here, we’ve specified the task to start immediately and then repeat every second. You can find out the next scheduled time for a task’s execution by calling its scheduledExecTime(). Finally, you can cancel a task or the timer itself by calling its cancel() method. This program will produce the following output. Running task: txn polling Running task: txn polling Running task: txn polling 2009-08-12 15:07:59.453000000 Done 6.3 Asynchronous Behavior By default, a method call (be it local or remote) is handled synchronously – the calling thread blocks until the called method returns. JavaGram also allows you to call a method asynchronously. In this case, the calling thread continues its execution immediately after Advanced Topics 197 the async call and does not wait for the method to return. When the method finally returns, a callback is invoked to do post processing. The typical format of an async call is MethodCall -> Callback -> ErrCallback where ErrCallback is optional and, if present, is invoked instead of Callback when the async call fails. Otherwise, Callback is deemed to be also responsible for exception handling. A callback can be any valid expression, statement, or even a block. Async calls are useful when you don’t want a thread to block while a potentially lengthy task is executing. You can achieve the same effect by creating your own thread and managing the call in that thread, but the async call syntax is an elegant alternative that can save you unnecessary coding. Recall how the <Worker> element described in Chapter 4 executes on the Swing’s event dispatcher thread, so that it can safety update the GUI. Because async callbacks can potentially update the GUI, they also execute on the Swing’s event dispatcher thread. 6.3.1 Local Asynchronous Call Here is an example to illustrate the use of a local async call. <jag domain="doc/code/chap6"> class AsyncTest { static vector<map> data = [ [$name=>"John", $age=>20, $sex=>"Male"] ,[$name=>"Linda", $age=>22, $sex=>"Female"] ,[$name=>"Albert", $age=>18, $sex=>"Male"] ,[$name=>"Fiona", $age=>27, $sex=>"Female"] ]; public remote vector search (map criteria) { vector result = vector(); for (map m in data) { //10 / 0; // Intentionally cause divide by zero error boolean match = true; for (vague key in criteria) { if (m[key] != criteria[key]) { match = false; break; } } if (match) sys.append(result, m); } return result; } public void show (vector result) { 198 JavaGram Agile Development sys.println(result); } public void error () { sys.println("error() called"); } public static void main () { AsyncTest test = new AsyncTest(); vector res = test.search([$sex=>"Male"]) -> test.show(res) -> test.error(); sys.println("After async call"); } } </jag> Within the main() method, search() is called asynchronously. Note that the return value of this call (res) is accessible to the callback method. If you run this program, it will produce the following output. After async call [[$age=>20, $name=>"John", $sex=>"Male"], [$age=>18, $name=>"Albert", $sex=>"Male"]] This clearly shows that the callback is invoked after execution has progressed beyond the async call. Now, if you uncomment the division by zero line in search() to intentionally cause an error, you will get the following output instead. After async call error() called JavaGram handles a local async call by creating a separate thread and handing over the management of the call to that thread. However, because of its single-threaded nature, the Flash version of JavaGram runtime treats a local async call synchronously. 6.3.2 Parallel Processing Async calls make it very easy to implement parallel processing algorithms that significantly increase the runtime speed of your programs, especially on multi-CPU computers. As a practical example, suppose that your program needs to convert a bunch of image files from one format to another. If the conversion process is computationally intensive, you can speed things up by processing the images in parallel. <jag domain="doc/code/chap6"> class AsyncTest { vector<string> bitmapsToGifs (vector<string> bitmapImages) { vector<string> gifImages @= vector(); for (string bitmap in bitmapImages) { string gif = bitmapToGif(bitmap) -> addGif(gifImages, gif); } Advanced Topics 199 // Wait until all images have been converted: do { sys.sleep(10); } while (sys.length(gifImages) != sys.length(bitmapImages)); return gifImages; } string bitmapToGif (string bitmap) { //... return bitmap; } void addGif (vector<string> gifImages, string gif) { sys.append(gifImages, gif); } } </jag> We’ve used a do-while loop here to synchronize on the completion of all the async calls issued in the for-loop. The former loop repeatedly puts the main thread into sleep for 10 milliseconds until the gifImages vector is fully populated. 6.3.3 Remote Asynchronous Call When you deploy an application server, you have the option of configuring it as an async server. To do so, simply add the following to your server configuration map. $mode => $async An async server is capable of handling async remote calls. For each such call, the server creates a separate thread to handle that call. When a client connects to an async server, it internally creates a reader thread for handling async calls. The role of this thread is to invoke the async call’s callback on the client side. An async server handles sync calls like a sync server – it forces the client to block until the call returns. To see an example of an async remote call, modify the AsyncTest class as follows: Add a remote qualifier to the search() method. Add $mode => $async to your server configuration map. Deploy a server. Run a client against it. You should get the same output as before. When you use the Flash version of JavaGram runtime, all remote calls must be async, because Flash is single-threaded and cannot block until a sync call returns. 200 JavaGram Agile Development 6.3.4 Guarded Asynchronous Call You can also use a guarded version of the asynchronous call syntax to code for both synchronous and asynchronous clients. For example: User u = login() ?? sys.async -> setUser(u) -> failed(sys.getAsyncErr()); or User u = login() ?> sys.async -> setUser(u) -> failed(sys.getAsyncErr()); The condition appearing after ?? or ?> must be a boolean expression. When this condition evaluates to true, the call is performed as before. When this condition evaluates to false, login() is called synchronously and no callback is invoked, unless ?> is used, in which case the callback(s) are still performed but synchronously. The lib/lang/Common.jag script (described in Chapter 13) provides a Callback class for use in asynchronous calls. Here is an example of its use in the Quest sample application: // Snippet from a GUI script that authenticates the user: User user = User.login(username, password, !sys.async ? null : new Callback() { public void completed (vague user) { setCurrUser(user@User); onLoginOK(); } public void failed (Exception e) { onLoginFail(e); } }); // Method in User business object that calls a remote method asynchronously. // Note the use of a guard to call synchronously when there is no callback. public static User login (string username, string password, Callback cb = null) { User user @= Object.findOne(User, $username, username) ?> cb != null -> { if (!validate(user, password)) { Exception e = new Exception("invalid username or password"); if (cb == null) throw e; else cb.failed(e); } else cb?.completed(user); } -> cb?.failed(); return user; } 6.4 Report Generation JavaGram provides a template-driven report engine that simplifies the task of report generation. A report template is a document that defines the structure and format of a Advanced Topics 201 given report type. Within this document, special tags are used to insert JavaGram code fragments, which perform dynamic computation. Three types of tags are supported: Anything that appears within << ... >> is treated as JavaGram code. Such code is simply evaluated during report generation. Where these tags occur in normal text, escape them as <\< ... >\>. Anything that appears within <? ... ?> is also treated as JavaGram code (the content is escaped if it happens to include markup). Such code is evaluated during report generation, and the result of evaluation is inserted in the generated report. Where these tags occur in normal text, escape them as <\? ... ?\>. The tag <?= ... ?> behaves the same as <? ... ?>, except that the content is not escaped if it happens to contain markup. You can create a report template in any of these formats: RTF (en.wikipedia.org/wiki/Rich_Text_Format). To create an RTF template, use Microsoft Word, OpenOffice, or any other word processor that supports RTF, and save the document in Rich Text Format (the report template file must have an rtf extension). When you generate a report using an RTF template the result will be an RTF document. You can programmatically convert this document to PDF using JodConverter (see below). HTML (en.wikipedia.org/wiki/HTML). This is supported by all web browsers. To create an HTML template, use an HTML editor or a text editor (the report template file must have an html extension). When you generate a report using an HTML template the result will be an HTML document. XSL-FO (xmlgraphics.apache.org/fop/). To create an XSL-FO template, use an XML editor or a text editor (the report template file must have a fo extension). When you generate a report using an XSL-FO template the result will also be a FO document. You can programmatically convert this document to a variety of printable formats (including PDF and RTF) using FopConverter (see below). Of these formats, XSL-FO is the one we recommend most, because it’s very flexible and there is excellent open source software for it. It is generally a good idea to use a ‘reports’ directory within your application to hold all report-related files. For example: Put your report templates in reports/plate/. Use reports/compiled/ as the destination directory for compiled report templates. Use reports/generated/ for storing generated reports. 202 JavaGram Agile Development The lib/io/Report.jag script provides a number of classes that drive the report engine. These are described below. 6.4.1 Report Class This is the base class of a report class generated by the report engine. It has two public methods: public static Report factory(string absFilePath, vague data) This method is also defined in the generated subclass. Call it on the subclass to instantiate it, passing to it the absolute path for the report file to be generated and the data variable referred to by the report template. final void generate () Call this method on an instantiated report class in order to generate the report. 6.4.2 ReportEngine Class This class provides the following static methods for compiling report templates and manipulating documents. public static string compileTemplate (string platePath, string scriptPath, string domain, list loads, string klass) Before you can generate a report, you must use this method to compile the report template. This method translates your report template into a JavaGram script that, when executed, performs report generation. Once a template is compiled, it can be used as many times as needed to generate reports. It is a good practice to perform template compilation during application initialization – this will ensure that any modified report templates are recompiled. However, templates can also be compiled on the fly to, for example, allow end-user customization. platePath denotes the report template to be compiled. scriptPath denotes the JavaGram script to be generated as a result of compilation. domain denotes the JavaGram domain of the generated script. loads can be used to nominate scripts to be loaded by the generated script (use it to specify script dependencies). Finally, klass is the name of the class that subclasses Report in the generated script. This method returns the absolute path of the generated script (i.e., scriptPath). public static dstPlatePath) map<string,list> extractMacros (string srcPlatePath, string Parses the template denoted by srcPlatePath and looks for macros of the form <#MacroName#MacroDescription#MacroValue#> where: MacroName must be a valid identifier, consisting of alphanumeric characters and underscores, but no blanks. MacroDescription can be any string (even empty string). Any literal # characters in this string must be escaped by a preceding backslash (\). MacroValue can be any string. No # should appear in this string. Returns a map of the form ["MacroName"=>("MacroDescription" "MacroValue"))...]. When dstPlatePath is not null, this file is generated to be a copy of srcPlatePath, Advanced Topics 203 where each macro has been replaced by its MacroValue. Note: only RTF templates are supported by this method. public static string expandMacros (string srcPlatePath, map<string,list> macros, string dstPlatePath) Parses the template denoted by srcPlatePath and looks for RTF fields of the form {QUOTE MacroName}. In Microsoft Word, you can create such fields using Insert | Field | Links and References | Quote. In Word 2007, use Insert | Quick Parts | Field... | Quote. In the displayed dialog, enter the macro name as a single identifier. macros must be a map that provides a definition of each macro (as returned by extractMacros()). The file denoted by dstPlatePath is generated to be a copy of srcPlatePath, where each QUOTE field has been replaced by the value of the corresponding macro in macros. This method returns dstPlatePath. public static string mergeRTFs (vague rtfFiles, string mergedRtfPath, list options = null, list subst = null) Merges the RTF files denoted by rtfFiles (which should be a list or vector of file paths) to generate a single RTF file whose path is denoted by mergedRtfPath. options may contain the following: $pageBreak. This causes a page break to be inserted between the merged files. $noFileHeader. This causes the merged file to have no file header (this is useful when your merged file is intended to appear inside another RTF file). subst may be null or contain two strings, in which case, every occurrence of the first string is replaced by the second string in the merged file. This method returns the full path of the merged file (i.e., mergedRtfPath). public static list getRTFHeaderFooter (string rtfPath) Returns a list of two strings, the first being the RTF header of the document denoted by rtfPath, and the second being the RTF footer of the document. Either or both may be an empty string if the document has no header or footer. Don’t confuse document header with RTF file header. The former is a paragraph that appears at the top of each page, whereas the latter is a binary block that appears at the beginning of every RTF file. public static string setRTFHeaderFooter (string srcRTFPath, string dstRTFPath, string rtfHeader, string rtfFooter) Copies the file denoted by srcRTFPath to the file denoted by dstRTFPath, and in the process sets the header and footer of the latter to the RTF header/footer denoted by, respectively, rtfHeader and rtfFooter. If rtfHeader or rtfFooter is null then the header or footer of the file is left unchanged. If rtfHeader or rtfFooter is "" then the header or footer of the file is set to an empty header or footer (i.e., a valid RTF header or footer with no content). This method returns dstRTFPath. 6.4.3 FopConverter Class This singleton class is a JavaGram wrapping of the open source Apache FOP library (xmlgraphics.apache.org/fop/) that converts XSL-FO documents into a verity of printable 204 JavaGram Agile Development formats (GIF, JPEG, MIF, PCL, PDF, PNG, PS, RTF, SVG, TXT, TIFF). Apache FOP is quite powerful and has the advantage of being open source and free to use. Please note that the JAR files supplied with FOP must be accessible by a JavaGram program that uses this class (e.g., via the -jar command line option). Here is an example to illustrate how FopConverter is used: FopConverter.singleton.convert("C:/reports/Report1.fo", null, "C:/reports/Report1.pdf"); The FopConverter class is very simple and has only two methods: public void convert (string foDocPath, string xsltDocPath, string dstDocPath) Converts a document from XSL-FO format to another. The destination document format is determined from the file extension of dstDocPath. The xsltDocPath parameter should be either null or denote an XSLT stylesheet file. public void setConfigFile (string configFilePath) Sets the configuration file for FOP. This is an XML file that specifies how FOP should be configured if the default configuration is inadequate. For example, you can use it to specify the fonts that are to be used by FOP. See xmlgraphics.apache.org/fop/1.0/configuration.html for details. Note that you only need to call this method once (e.g., from a static initialization block). Here is a sample FOP XML configuration file, showing how to include fonts: <?xml version="1.0"?> <fop version="1.0"> <!-- Strict user configuration --> <strict-configuration>true</strict-configuration> <!-- Strict FO validation --> <strict-validation>false</strict-validation> <!-- Base URL for resolving relative URLs --> <base>.</base> <!-- Font Base URL for resolving relative font URLs --> <font-base>./</font-base> <fonts> <substitutions> <substitution> <from font-family="Barcode" font-weight="700..900"/> <to font-family="Free 3 of 9"/> </substitution> </substitutions> </fonts> <renderers> Advanced Topics 205 <renderer mime="application/pdf"> <filterList> <value>flate</value> </filterList> <fonts> <directory recursive="true">./fonts</directory> <font kerning="yes" embed-url="file:///C:/JavaGram/myapp/reports/fonts/msgothic.ttf"> <font-triplet name="MS Gothic" style="normal" weight="normal"/> </font> </fonts> </renderer> <renderer mime="application/X-fop-print"> <fonts> <directory recursive="true">./fonts</directory> </fonts> </renderer> </renderers> </fop> 6.4.4 JodConverter Class This singleton class is a JavaGram wrapping of the open source JodConverter library (code.google.com/p/jodconverter) that facilitates document format conversion by running OpenOffice (openoffice.org) in headless mode. Please note that the JAR files supplied with JodConverter must be accessible by a JavaGram program that uses this class (e.g., via the -jar command line option). Also, OpenOffice 3 or later must be installed on the machine where conversion is performed. Here is an example to illustrate how JodConverter is used: JodConverter.singleton.start(); JodConverter.singleton.convert("C:/reports/Report1.rtf", "C:/reports/Report1.pdf"); JodConverter.singleton.convert("C:/reports/Report2.rtf", "C:/reports/Report2.pdf"); //... JodConverter.singleton.stop(); Because start() is a lengthy method, you should consider calling it only once during server or application initialization. Once JodConverter is started, convert() calls execute fairly rapidly. Similarly, stop() should be called when the application or server is about to exit. JodConverter supports numerous file formats – see openoffice.org for details. The class provides the following methods. public void log (boolean on) Switches logging on or off. By default, logging is switched off. 206 JavaGram Agile Development public void start (string officeHomeDir = null, int officePort = 0, int taskTimeoutMS = 0) Starts JodConverter which in turn instantiates an OpenOffice process pool for handling conversions. The parameters can be used to override the default settings for OpenOffice home directory, the port on which it listens for connections, and conversion task timeout (expressed in milliseconds). public void convert (string srcDocPath, string dstDocPath) Converts a document from one format to another. The source and destination document formats are determined, respectively, from the file extension of srcDocPath and dstDocPath. public void stop () Stops JodConverter and clears the OpenOffice process pool. JodConverter has the advantage of being open source and free to use. There are also commercial document format conversion tools in the market that you could use instead. An example is RTF TO XML (www.rtf-to-xml.com) – a Java solution that allows conversion of RTF to XSL FO, PDF, HTML, and many other printable and viewable formats. It can be operated from a Graphics User Interface, a command line, or through Java API. Should you decide to use it, you can use JavaGram’s sys.java() method to hook into its Java API. 6.4.5 Example Consider a simple application that generates sales reports for a group of salesmen. Assume that the following two classes have been defined in a script called Sale.jag: class Sale { getable string item; getable real price; } class SaleSummary { getable string salesman; getable date endPeriod; getable vector<Sale> sales; } Here is a simple HTML report template for generating a sales report: <<SaleSummary summary @= data;>> <html> <head> <title>Report Test</title> </head> <body> <h1>Sales Report</h1> Salesman: <b><?summary.getSalesman()?></b> Advanced Topics 207 <br> Sales Period: month ending on <?sys.format(summary.getEndPeriod(), "dd MMM yyyy")?> <br> Sales Summary: <table border=1 cellspacing=0> <tr><th width=200>Item</th><th width=100>Price</th></tr> <<real total = 0; for (Sale sale in summary.getSales()) {>> <tr> <td align=left><?sale.getItem()?></td> <td align=right><?sys.format(sale.getPrice(), "$0,000.00")?></td> </tr> << total += sale.getPrice(); }>> <tr> <td align=right><b>Total:</b></td> <td align=right><?sys.format(total, "$0,000.00")?></td> </tr> </table> </body> </html> The report engine provides two predefined variables: data, which is of type vague, and represents whatever data the report will need to access. You can freely refer to this variable in your report template. file, which is of type stream, and represents the file for the report currently being generated. You should avoid referring to this variable in your report template. Here is simple test program that uses some static data to generate a report. class Test { static SaleSummary testData = object(SaleSummary , salesman=>"John Smith" , endPeriod=>sys.date(2010, 10, 30) , sales=>[[@Sale item=>"Fridge", price=>1120.5], [@Sale item=>"HiFi", price=>450.0]] ); public static void main () { string platePath = sys.pathConc(sys.root, "reports/plate/Sample.html"); string scriptPath = sys.pathConc(sys.root, "reports/compiled/SampleHTML.jag"); ReportEngine.compileTemplate(platePath, scriptPath, "reports/compiled", list("reports/Sale"), "SampleHTMLReport"); sys.load(scriptPath, false); string reportPath = sys.pathConc(sys.root, "reports/generated/SampleGen.html"); Report rep @= sys.call($SampleHTMLReport, "factory", reportPath, testData); rep.generate(); } } 208 JavaGram Agile Development Because the report template refers to classes defined in Sale.jag, we’ve passed this script to the compileTemplate() method (see the fourth argument) so that it can be loaded accordingly. The last argument to this method nominates the name of the generated class that subclasses Report (i.e., SampleReport). Once the report script is generated, we load it dynamically using sys.load(). The report class is now ready for use and can be instantiated as many times as needed (once for each report generation). We instantiate this class dynamically, using sys.call() to invoke its factory() method. Note, how we’ve passed testData as the last argument in the call. Finally, we can the generate() method of the class to generate the report. This will produce a HTML file which, when viewed in a web browser, will look like this: Reviewing the generated report script will give you a clearer picture of how the report template is translated into JavaGram code. Here it is: // Auto-generated by JavaGram -- do not edit <jag domain="reports/compiled"> <load> "lib/io/Report" "reports/Sale" </load> class SampleReport extends Report { public SampleReport (string absFilePath, vague data) { super(absFilePath, data); } public static Report factory (string absFilePath, vague data) { return new SampleReport(absFilePath, data); } protected void _generate () { SaleSummary summary @= data; sys.print(file, "<html>\n"); sys.print(file, "<head>\n"); sys.print(file, "\t<title>Report Test</title>\n"); sys.print(file, "</head>\n"); sys.print(file, "<body>\n"); sys.print(file, "<h1>Sales Report</h1>\n"); sys.print(file, "Salesman: <b>"); sys.print(file, summary.getSalesman()); Advanced Topics 209 sys.print(file, "</b>\n"); sys.print(file, "<br>\n"); sys.print(file, "Sales Period: month ending on "); sys.print(file, sys.format(summary.getEndPeriod(), "dd MMM yyyy")); sys.print(file, "<br>\n"); sys.print(file, "Sales Summary:\n"); sys.print(file, "<table border=1 cellspacing=0>\n"); sys.print(file, "\t<tr><th width=200>Item</th><th width=100>Price</th></tr>\n"); sys.print(file, "\t\n"); real total = 0; for (Sale sale in summary.getSales()) { sys.print(file, "\t\t<tr>\n"); sys.print(file, "\t\t<td align=left>"); sys.print(file, sale.getItem()); sys.print(file, "</td>\n"); sys.print(file, "\t\t<td align=right>"); sys.print(file, sys.format(sale.getPrice(), "$0,000.00")); sys.print(file, "</td>\n"); sys.print(file, "\t\t</tr>\n"); sys.print(file, "\t\n"); total += sale.getPrice(); } sys.print(file, "\t<tr>\n"); sys.print(file, "\t<td align=right><b>Total:</b></td>\n"); sys.print(file, "\t<td align=right>"); sys.print(file, sys.format(total, "$0,000.00")); sys.print(file, "</td>\n"); sys.print(file, "\t</tr>\n"); sys.print(file, "</table>\n"); sys.print(file, "</body>\n"); sys.print(file, "</html>\n"); } } </jag> Here is the same report template defined in RTF format: 210 JavaGram Agile Development Generating a report using this template and the same data used earlier will produce the following RTF file. Finally, the same template can be expressed in XSL-FO as follows. <<SaleSummary summary @= data;>> <\?xml version="1.0" encoding="utf-8"\?> <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"> <!-- defines the layout master --> <fo:layout-master-set> <fo:simple-page-master master-name="first" margin-top="2cm" marginbottom="2cm" margin-left="2.5cm" margin-right="2.5cm"> <fo:region-body margin-top="3cm"/> <fo:region-before extent="3cm"/> <fo:region-after extent="1.5cm"/> </fo:simple-page-master> </fo:layout-master-set> <!-- starts actual layout --> <fo:page-sequence master-reference="first"> <fo:flow flow-name="xsl-region-body" font-family="Helvetica"> <!-- this defines a title level 1--> <fo:block font-size="18pt" font-family="sans-serif" font-weight="bold" space-after.optimum="5pt" color="#365F91"> Sales Report </fo:block> <fo:block text-align="start"> Salesman: <fo:inline fontweight="bold"><?summary.getSalesman()?></fo:inline> </fo:block> <fo:block text-align="start"> Sales Period: month ending on <?sys.format(summary.getEndPeriod(), "dd MMM yyyy")?> </fo:block> <fo:block> Advanced Topics 211 Sales Summary: </fo:block> <!-- table start --> <fo:table table-layout="fixed" width="100%"> <fo:table-column column-width="50mm"/> <fo:table-column column-width="50mm"/> <fo:table-body> <fo:table-row> <fo:table-cell border-style="solid" border-width="thin"> <fo:block font-weight="bold">Item</fo:block> </fo:table-cell> <fo:table-cell border-style="solid" border-width="thin"> <fo:block font-weight="bold" textalign="right">Price</fo:block> </fo:table-cell> </fo:table-row> <<real total = 0; for (Sale sale in summary.getSales()) {>> <fo:table-row> <fo:table-cell border-style="solid" border-width="thin"> <fo:block><?sale.getItem()?></fo:block> </fo:table-cell> <fo:table-cell border-style="solid" border-width="thin"> <fo:block text-align="right"> <?sys.format(sale.getPrice(), "$0,000.00")?> </fo:block> </fo:table-cell> </fo:table-row> << total += sale.getPrice(); }>> <fo:table-row> <fo:table-cell border-style="solid" border-width="thin"> <fo:block font-weight="bold" textalign="right">Total:</fo:block> </fo:table-cell> <fo:table-cell border-style="solid" border-width="thin"> <fo:block text-align="right"><?sys.format(total, "$0,000.00")?> </fo:block></fo:table-cell> </fo:table-row> </fo:table-body> </fo:table> </fo:flow> </fo:page-sequence> </fo:root> Generating a report using this template with the data used earlier and converting the result to PDF will produce the following output. 212 JavaGram Agile Development 6.5 Web Services Once you’ve developed a JavaGram application server, you can expose the functionality provided by the server to other systems via web services. The primary advantage of using a web service as an interface is technology independency – the service can be accessed by any other system, regardless of its underlying technology, so long as it supports web services. The inherent loose-coupling of web services makes them the preferred integration method for web applications. For example, you can use web services to integrate a web site developed in Java, .NET, PHP, or any other similar technology, with a JavaGram server. To do this, you don’t need to write any additional JavaGram code, but simply configure your application server appropriately. 6.5.1 Architecture The JavaGram web service is packaged as a servlet (JAGWS.war) that must be deployed into an Apache Tomcat/AXIS2 environment. The JAG runtime is packaged into this WAR file and is responsible for loading/compiling/executing JavaGram server scripts in response to a client requests. This process may also involve accessing external databases or systems in the same manner as a normal JavaGram application server. 6.5.2 JavaGramService JavaGram provides a single web service (called JavaGramService) that manages all ‘web services’ communication. A client first calls the authenticate() method of this service to obtain a valid ‘client reference’ for use in subsequent calls. The generic method methodCall() can be used to access any exposed server-side functionality. Additionally, downloadFile() and uploadFile() allow you to download/upload files from/to the server. Advanced Topics 213 6.5.2.1 JavaGramService Methods For ease of description, the service methods are described here using Java syntax. Please refer to the WSDL file for their platform-independent syntax. public String authenticate (String username, String password) throws Exception SUMMARY: Before using the JavaGramService, a client must be authenticated. The required username and password can be defined in the server config file. If not defined, any username and password combination will be accepted. If successful, authenticate will return a ‘client reference’ as a string, which you will use in subsequent calls. Otherwise, it will throw an exception. You only need to call this method once. EXAMPLE: String client = authenticate("soap", "secret"); public String methodCall (String client, String call) SUMMARY: Requests that the JavaGramService performs the method call denoted by call, and returns the result of the call. For client, you must pass a client reference returned by an earlier call to authenticate(). Both call and the returned result are expressed in XML (see the next sub-section for syntax definition). EXAMPLE: String xml = methodCall(client, "<request id='1' class='crm.bom.User' method='findById'>" + "<arg type='int' value='1'/>" + "</request>" ); public byte[] downloadFile (String client, String relPath) throws Exception SUMMARY: Attempts to download the file whose server-side relative path is denoted by relPath. For client, you must pass a client reference returned by an earlier call to authenticate(). If successful, it returns the file content as a binary array, which is encoded as a Base64 string. Otherwise, it throws an exception. EXAMPLE: byte[] data = downloadFile(client, "crm/rep/Sales.pdf"); public String uploadFile (String client, String relPath, byte data[]) throws Exception SUMMARY: Attempts to upload the file whose content is denoted by data to the serverside location denoted by relPath. For client, you must pass a client reference returned by an earlier call to authenticate(). Encode the data as a Base64 string before passing it to this method. If unsuccessful, it throws an exception. This method always returns null. EXAMPLE: 214 uploadFile(client, "crm/data/Orders.xml", data); JavaGram Agile Development 6.5.2.2 XML Syntax for methodCall() The second argument of methodCall() must be expressed in XML, and define a serverside method invocation request. For example, suppose that we have a server-side JavaGram script (crm/bom/Client.jag) that defines a Client class: class Client { //... public static find (string name, string postcode) { //... } public void update (int by) { //... } } Assuming that the server has been configured to expose these methods via web services, the following represents valid XML to be passed to methodCall(). <request id="12" class="crm.bom.Client" method="find"> <arg type="string" value="Smith"/> <arg type="string" value="3104"/> </request> The XML call must always be a <request> element and the result will always be a <response> element, having the same id as its corresponding request. For example: <response id="12"> <return type="crm.bom.Client"> <joined type="date" value="2010-09-01 16:05:40"/> <id type="int" value="1012301"/> <name type="string" value="Smith"/> <postcode type="string" value="3104"/> <status type="string" value="Active"/> </return> </response> For a non-static method, the request must also include the object on which the method is invoked (typically returned by an earlier call). <request id="13" class="crm.bom.Client" method="update"> <this type="crm.bom.Client"> <joined type="date" value="2010-09-01 16:05:40"/> <id type="int" value="1012301"/> <name type="string" value="Smith"/> <postcode type="string" value="3104"/> <status type="string" value="Active"/> </this> <arg type="int" value="10221"/> </request> Advanced Topics 215 In this case, the response will also include the modified <this> returned by the server. The following two tables specify the permissible composition of request and response elements. <request> Parts Description id="..." A client-supplied identifier, used for the purpose of matching a request/response pair. Example: id="10" script="..." The server-side script containing the referenced class. You can omit this property if the script path matches the class path. Example: script="crm/bom/Client" class="..." The name of the referenced class. For a qualified name, you can use backslash or dot as the separator character. So, for example, class="crm\\bom\\Client" and class="crm.bom.Client" are equivalent (the latter is internally replaced by the former). Also, if a script is specified, you can use the unqualified class name instead (e.g., class="Client"). method="..." The class method to be called. Example: method="find" minimal="..." When set to true, it causes a non-static call not to return the <this> element (defaults to false). Example: minimal="true" indent="..." When set to true, it causes the response XML to be indented for readability (defaults to false). Example: indent="true" <this> A non-static call must include a <this> element to represent the object on which the method is to be called. <arg> Specifies an argument to be passed to the method. Arguments must appear in the order in which they are to be passed to the method. Example: <arg type="int" value="10221"/> <response> Parts Description id="..." Response identifier, matching the corresponding request. Example: id="10" error="..." When the request fails, this property is present and denotes the error message. In this case, the body of the response will be empty. <this> Present only in response to a non-static call, and represents the modified object. <return> Present only when the method has a non-void return type, and represents the value returned by the method. 216 JavaGram Agile Development 6.5.2.3 XML Representation of JavaGram Data Representation of JavaGram data in XML is governed by the following rules. A data item is represented by an element having these properties: type, which represents the data type (e.g., "int", "map"). For parameterized containers, parentheses are used instead of angled brackets to avoid clashing with XML syntax (e.g., "map(symbol,real)"). For qualified class names, backslash as well as dot can be used as separator (e.g., "crm\\bom\\Client" or "crm.bom.Client"). value, which represents the value of an atomic data item (i.e., int, real, char, boolean, string, symbol, date). This also includes vague, provided it refers to atomic data. This property is not present for non-atomic data. Instead, the data appears as children of the element. keytype, is present for a map element and represents the type of the map element key. This is required to be an atomic type. key, is present for a map element and represents the value of the map element key. Non-atomic data is represented by nested elements. However, a null non-atomic data item is represented by an element whose value="null", having no child elements. In the following descriptions, where an element’s name appears as <name ...>, it is determined by the context. For example, for a return value the element will be <return ...>, for an argument value, it will be <arg ...>, and for an object field it will be the field name. A vector is represented like this: <name type="vector"> <elem type="..." value="..."/> ... </name> A list is represented like this: <name type="list"> <elem type="..." value="..."/> ... </name> A map is represented like this: <name type="map"> <elem keytype="..." key="..." type="..." value="..."/> ... </name> An object is represented like this: Advanced Topics 217 <name type="..."> <fieldName type="..." value="..."/> ... </name> 6.5.3 Software Installation To deploy a web service, you need to have Apache Tomcat and AXIS2 installed on the server host. For simplicity, the following instructions assume that the server name is localhost. Download the Apache Tomcat server from http://tomcat.apache.org and extract it to a directory (e.g., C:\Program Files\tomcat). To run Tomcat, make sure that you have the following environment variables correctly defined. On Windows, you can set these via “System Properties | Advanced | Environment Variables”. Define them as system variable. JAVA_HOME (e.g., set to C:\Program Files\Java\jdk1.6.0_16), or JRE_HOME (e.g., set to C:\Program Files\Java\jre6), and CATALINA_HOME (e.g., set to C:\Program Files\tomcat). CATALINA_OPTS (e.g., set to -Xms256m -Xmx512m). On a 64-bit server, also consider using the incremental garbage collection option (-Xincgc) To install Tomcat as a service, start a DOS session and run the command ‘tomcat\bin\service install’. The service is installed but not started (needs manual startup). Go to “Control Panels | Admin Tools | Services” and start the Apache Tomcat service. Verify that Tomcat is running by entering the URL http://localhost:8080/ in a browser. You should see a page like the following. 218 JavaGram Agile Development To install AXIS2, download the complete binary distribution from http://ws.apache.org/axis2 and extract it to a directory (e.g., C:\Program Files\axis2). Also download the Axis2 WAR distribution from the same site (this is a zip file from which you need to extract axis2.war). Alternatively, you can build the WAR file from the binary distribution, as per the instructions on the web site. Install the AXIS servlet by copying axis2.war to Tomcat’s webapps directory. If Tomcat is running, it will automatically install the WAR file. Start a web browser and go to the AXIS URL (e.g., http://localhost:8080/axis2/). The following page should be displayed. Edit Tomcat’s webapps\axis2\WEB-INF\conf\axis2.xml file and make sure the hotupdate parameter is set to true: <parameter name="hotupdate">true</parameter> Advanced Topics 219 Similarly, install the JAGWS servlet by copying JAGWS.war from the JavaGram installation directory to Tomcat’s webapps directory. Start a web browser and go to the JAGWS URL (e.g., http://localhost:8080/JAGWS/). A page similar to the above should be displayed. Click on ‘Services’ – you should be able to view the JavaGramService methods. After defining the JAGWS_OPTIONS environment variable (see below), in the same browser window, enter the following URL to view the service WSDL file: http://localhost:8080/JAGWS/services/JavaGramService?wsdl 6.5.4 Configuration A number of configuration steps are required in order to deploy a functioning web service. 6.5.4.1 Server Configuration The JavaGram server configuration file must have a $service entry that defines the security requirements of the JavaGramService as well as what is to be exposed. Here is an example: $service=>[ $security=>[ $username=>"soap", $password=>"secret" ] , $classes=>[ "crm\\bom\\User" => ["findById", "getPermissions"] , "crm\\bom\\Client" => "*" 220 JavaGram Agile Development ] ] The $security entry is optional and, if not present, will allow any username/password combination to succeed. When present, the username and password provided by the web service client in the authenticate() call must match in order for client access to be granted. This password denoted by $password may optionally be enclosed in parentheses to signify that it is encrypted. Where using encrypted passwords, the command line parameter, -passKey, must be used (in the JAGWS_OPTIONS environment variable) to specify the key to be used to decipher. The $classes entry nominates the classes that are to be exposed via the web service. Each entry in this map, maps a qualified class name to a vector of methods that are to be exposed. If you want to expose all the exposable methods of a class, use the wildcard "*" instead of a vector. The following rules apply to method exposure: A non-public method is never exposed. An slocal method is never exposed. If an exposed method has multiple overloaded public versions, all are exposed. It is generally a good practice to only expose methods of business objects. The downloadFile() and uploadFile() web service calls are governed by the $download and $upload entries in the server config file (as explained in Chapter 7) much in the same way as sys.download() and sys.upload(). 6.5.4.2 JAGWS Configuration When deploying your web service, you must make sure that the JAG runtime options are specified. You can specify these by defining an environment variable called JAGWS_OPTIONS. The relevant runtime options are (see Chapter 7): -verbose, -noWarn, -keepStdOut, -clearCache, -root, -stage, -config, -passKey, -logfile, -tz For example: JAGWS_OPTIONS=-noWarn -root "C:/CRM/" –stage test -config crm/Config.jag 6.5.4.3 Additional JAR files If you have additional JAR files that need to be accessed by your server (e.g., JDBC drivers, FOP library), you can deploy these in one of two ways: Add the files to JAGWS.war (at the same level as JAG.jar) before you deploy JAGWS.war. Advanced Topics 221 Manually add the files to Tomcat’s webapps\JAGWS\WEB-INF\lib\ directory after deploying JAGWS.war. (Note: the Eclipse build process for JAGWS.jar doesn’t update the JAR files in it correctly, unless they’re manually inserted into the project’s WEBINF/lib folder manually.) It’s important to keep webapps\JAGWS\WEB-INF\lib\ up-todate for future builds (e.g., with new JAG.jar or new 3rd party JAR files). Also, make suer that you don’t place JADE.jar in this folder as it will take precedence over JAG.jar! 6.5.4.4 Virtual Directory If you intend to generate reports/images on the server side and view them on the client side, the most efficient method is to create a virtual directory in your web server. 6.5.5 Virtual Directory You need to setup a virtual directory within the web server of the Rux Server host. When Rux generates a report, it publishes it in this virtual directory so that it can be accessed in a web browser. You can either do this using IIS or Tomcat (IIS is recommended for a production/test server; Tomcat is recommended for a development server). To setup a virtual directory using IIS: Login to the server host and run the Server Manager. Select Roles / Web Server (IID) / Internet Information Services (IIS) Manager In the right-hand pane, right click on Sites / Default Web Site and select Add Virtual Directory from the popup menu. Enter the virtual directory details as illustrated below and press OK. To setup a virtual directory using Tomcat, open Tomcat’s conf/server.xml file in a text editor and add the following XML element as a child of the <Host> element. Then, restart Tomcat. <!-- Add a virtual directory called /data --> <Context path="/data" docBase="C:/JavaGram/app/data" reloadable="true"> 222 JavaGram Agile Development </Context> Where, for example, C:/JavaGram/app/data is the folder you’re publishing as /data. Now if you, for example, generate the report C:/JavaGram/app/data/report/Sample.html, you’ll be able to access it using the URL http://hostname:8080/data/report/Sample.xml. The Desktop class in lib/lang/Common makes it easy to view the report. The following call opens the file in a new browser window. Desktop.browse(url, "_blank") 6.5.6 Client Example The instructions in this section assume a Java client developed using Eclipse enterprise edition. We also assume that you have a JavaGram server-side script called crm/bom/Client similar to what was outlined earlier in this section. The starting point for the web service client is the WSDL file. In Eclipse: Go to ‘File | New | Other | Web Services | Web Service Client’. Enter the WSDL file into the Service definition field. For example: http://localhost:8080/JAGWS/services/JavaGramService?wsdl Make sure that the Server link is set correctly to Tomcat. Click on the Web service runtime link and make sure it’s set to ‘Apache Axis2’. Click on the Client project link and enter ‘JAGWS_client’. Set the project type as ‘Dynamic Web project’. Press Finish. The client stub files will be generated into your JAGWS_client project. Now we can write a client class that uses these stubs to communicate with the deployed service. public class JavaGramTestClient { static public void main (String args[]) { try { JavaGramServiceStub stub = new JavaGramServiceStub(); Authenticate auth = new Authenticate(); auth.setUsername("soap"); auth.setPassword("secret"); AuthenticateResponse ar = stub.authenticate(auth); String client = ar.get_return(); String xml = "<request id='1' class='crm.bom.Client' method='find' indent='true'>" + "<arg type='string' value='Smith'/>" + "<arg type='string' value='3104'/>" + "</request>"; Advanced Topics 223 // ---- Synchronous call example ---MethodCall call = new MethodCall(); call.setClient(client); call.setCall(xml ); MethodCallResponse cr = stub.methodCall(call); String str = cr.get_return(); System.out.println(">> Synchronous call returned: ", str); // ---- Asynchronous call example ---MethodCall asyncCmd = new MethodCall(); asyncCmd.setClient(client); asyncCmd.setCall(xml); JavaGramServiceCallbackHandler cb = new JavaGramServiceCallbackHandler() { public void receiveResultmethodCall(MethodCallResponse result) { String str = result.get_return(); System.out.println(">> Asynchronous call returned: " + str); } public void receiveErrormethodCall(Exception e) { System.out.println(">> Asynchronous call failed: " + e.getMessage()); } }; stub.startmethodCall(asyncCmd, cb); // Avoid exiting program before callback returns: Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } } } This will produce an output, such as: >> Synchronous call returned: <?xml version="1.0" encoding="UTF-8"?> <response id="1"> <return type="crm.bom.Client"> <joined type="date" value="2010-09-01 16:05:40"/> <id type="int" value="1012301"/> <name type="string" value="Smith"/> <postcode type="string" value="3104"/> <status type="string" value="Active"/> </return> </response> >> Asynchronous call returned: <?xml version="1.0" encoding="UTF-8"?> <response id="1"> <return type="crm.bom.Client"> <joined type="date" value="2010-09-01 16:05:40"/> <id type="int" value="1012301"/> <name type="string" value="Smith"/> <postcode type="string" value="3104"/> <status type="string" value="Active"/> 224 JavaGram Agile Development </return> </response> 6.6 Efficiency Considerations JavaGram has a number of built-in features to improve the runtime efficiency of your applications. Some of these apply at all times and are outside the programmer’s domain of control, while others can be configured via runtime options or server configuration. This section describes these features, as well as design and coding guidelines that will assist you in developing better performing applications. 6.6.1 System-level Caching When you deploy an application in client-server mode, JavaGram maintains a file cache on both ends, the basic purpose of which is to avoid unnecessary reprocessing and/or transmission of files that have already been processed. The server-side cache manages compiled scripts generated for consumption by clients or servers. The client-side cache (not applicable to the Flash version of JavaGram runtime) manages compiled scripts downloaded from the server. All scripts (in source or compiled format) are time-stamped to keep track of their currency. JTP uses this information to ensure that a script is transmitted only when it’s out of date with respect to what the target cache holds. This caching mechanism is part of JavaGram’s design and outside programmer’s control. For further details, please refer to Section 7.5. 6.6.2 System-level Compression When deploying a server, you can configure it to compress transmittable data that exceeds a certain size. This is controlled by a $zip entry in the server configuration map. For example, adding $zip => [$min => 1024] Advanced Topics 225 to your server configuration map will ensure that all files and messages exchanged between clients and server that exceed 1024 bytes will be compressed. Compression is applied in both directions and is totally transparent. For example, if a client requests a file from the server that is larger than this size, the server will compress the file, send it across to the client, where it will be automatically decompressed and then handed over to the caller. The same applies to remote calls. For example, if a client makes a remote call where the overall size of the call packet exceeds 1024 bytes (e.g., because one of the arguments is a large vector) then the call packet will be compressed before being sent to the server, and decompressed on the server side before it’s dispatched to its target. Because a large part of a client-server application’s latency is due to data transmission over networks, compression can pay handsome dividends. So its use is highly recommended. 6.6.3 Application-level Caching In most applications there are application-specific opportunities for further caching to improve performance, be it at the client end, the server end, or both. The most common example of this is the management of low-volatility reference data. For example, an application that uses suburb and postcode data often stores this information in a database table. Given the low volatility nature of this data, it would be wasteful to query this table every time the application needs to look up a suburb. This information can be retrieved once and held in a cache for fast access. For most cached information, you also need some way of refreshing the cache such that it can be activated by an automatic or manual trigger. Unless your cached data has zero volatility, there always comes a time when updates need to be reflected. The fallback position of restarting the application or the server is not an attractive solution. You should always treat the use of an application-level cache as a trade off between performance and memory consumption. Therefore, you should be selective and opt for choices that are frequently used but not too memory hungry. 6.6.4 Remoting Performance Factors When designing remote methods, you should think carefully about the implications of making a remote method static or non-static. Generally, a static remote method executes more efficiently because there is no implicit object to be transmitted back and forth. However, if the object is likely to be modified on the server side, then a non-static method is the right approach, as it will ensure that the client-side object gets updated correctly. Here are some guidelines to assist you in deciding between the two options: Search methods are generally best defined as static because of their read-only nature. If an object is very large then you would really want to avoid invoking a non-static remote method on it – the transmission overheads will be substantial. Consider other 226 JavaGram Agile Development options, such as defining it as a remote class, or using a static remote method, or redesigning it as a collection of smaller classes. Be careful with passing large objects as arguments to remote methods, or returning a large object as result. Sometimes this is unavoidable but often, with a little bit of thinking, a better approach can be found. When coding enable, visible, and event handlers for GUI elements, as far as possible, avoid calling remote methods. These handlers may get called very frequently, leading to a lot of traffic. Wherever possible, avoid looping around remote calls. For example, if a remote call is going to return the same value every time round the loop and causes no server-side update, you can safely move the call outside the loop. Advanced Topics 227 7 Deployment This chapter describes the process of deploying JavaGram applications, and what you need to consider during development in order to maximize deployment flexibility. One of the major benefits of JavaGram is deployment simplicity. Because JAG incorporates an applications server as well as a proxy server, all deployments use the same runtime environment, regardless of the model. The recommended development approach is to initially treat the application as standalone so that you can focus on realizing business functionality. You should also consider what methods/classes should be defined as remote but not labor too much on this issue. Once the application has been sufficiently developed, you should revisit the ‘remote’ design decisions and test the application as client-server so that any design oversights regarding distribution can be identified and resolved. 7.1 Running a Program There are three distinct modes for running a JavaGram program: To run a (standalone or client) program that has no GUI, or the JavaGram compiler, use the jag.run.Env Java class. To run a (standalone or client) GUI program, use the jag.gui.Gui Java class. To run a server program use the jag.run.svr.Server Java class. Examples: java -cp JAG.jar jag.run.Env InitialScript javaw -cp JAG.jar jag.gui.Gui InitialScript java -cp JAG.jar jag.run.svr.Server InitialScript where InitialScript is the relative path of the script that boots the application or the server (e.g., quest/Quest.jag). It’s important to note that a GUI program must be run using javaw (not java), otherwise it will abort. 7.1.1 JavaGram Options You can use a range of command line options when running a JavaGram program. For example, in java -cp JAG.jar jag.run.Env –root C:/JavaGram quest/Quest.jag we’ve used the –root option to specify the root path C:/JavaGram. 228 JavaGram Agile Development The following options are available. The –root option sets the sys.root attribute to a given path. It must be followed by a valid directory path. If the path contains spaces, enclose it in double-quotes. If not specified, sys.root defaults to the directory in which you run the program. When JavaGram processes a relative path, it assumes that it’s relative to the root directory. The root path doesn’t need to end in / (a slash is added automatically where needed). The –host option is used by clients and servers, and sets the sys.host attribute to a given host. It must be followed by a host specification in the format host:port (where host is the name or IP address of a computer, and port is a port number). If host:port contains spaces, enclose it in double-quotes. If not specified, sys.host defaults to localhost:443. You can also specify multiple alternate hosts (in which case, you should enclose the whole thing in double-quotes). If you separate the hosts with spaces, commas, semicolons, or | then each host is tried in turn until one succeeds (i.e., failover treatment). If you separate the hosts using a plus character (+) then all hosts are tried and the one with the lowest load is selected (i.e., servers are load balanced from the client side). The recommended approach for load balancing, however, is to use a proxy server (see Section 7.2.2). The –ssl option is used by client applications, and sets the sys.ssl attribute to a given keystore. It must be followed by a keystore path (absolute or relative to sys.root, for the test certificate which you have self-signed), or just "" (if you have a digitallysigned certificate). If not specified, sys.host defaults to null, implying that the client should use a normal (i.e., non-SSL) channel for connecting to the server. The –boot option is used by client applications, and sets the sys.boot attribute to a script. It must be followed by a file path relative to sys.root, which represents the application’s main script. The file path may be optionally preceded by a partition name, in which case the two must be separated by a colon. If the script path contains spaces, enclose it in double-quotes. If the script path has no file extension, it will be resolved automatically to a .jag or .jax file. This option also requires the –host option to be present. It causes the client to negotiate with the server to boot the application, without initially having any of the application code on the client-side. If a partition is specified, that partition is downloaded first and processed. The script is then either sourced from the partition or downloaded, and then run. The –sboot option (strict boot) is used by client applications, and has the same effect as the –boot option, except that if the server to which the client points is a proxy server then the boot is strictly performed against the proxy server itself (and not one of the end-servers). This option is useful for running the JavaGram Server Monitor such that it monitors a proxy server. Any subsequent calls to the sys.client() method from such a client environment assume a strict boot. Using -sboot against an application server is harmless. The –config option is used by a non-client (servers and standalone applications) for specifying the application runtime configuration. It must be followed by a file path Deployment 229 (absolute or relative to sys.root) which contains the configuration map. If the file path contains spaces, enclose it in double-quotes. See Section 7.2.1 for details. The –passKey option can be used by non-client (servers and standalone) applications. It must be followed by a key file path (absolute or relative to sys.root. This key is later used for deciphering encrypted passwords for database connections and web services specified in configuration files. The –debug option is for the internal use of JADE when it runs an application in debug mode. The –verbose option sets the sys.verbose attribute to true. It tells JavaGram to output additional information for certain activities. For example, it causes the sys.load() method to print the name of the file it loads, and the sys.appServer() method to print the client-server communication. The –noWarn option sets the sys.warning attribute to false. It tells JavaGram to suppress warning messages. The –noPart option is applicable to clients. It’s mainly used in application testing situations and tells JavaGram to ignore partitions. The –stage option must be followed by one of dev, test, or prod and sets the sys.stage attribute to one of: $dev, $test, or $prod. By using a stage that reflects the current state of your software, you can avoid inadvertent mistakes such as releasing a script intended for system test into production. The –logfile option is used for redirecting the standard output and standard error to a log file. It must be followed by an absolute log file path. If the file path contains spaces, enclose it in double-quotes. If the file already exists, the existing file is renamed to <file>.old. If the latter already exists, it is first removed. Alternatively, you can use a date pattern in the log file name (e.g., C:/app/Log<yyyy-MM-dd>.txt). This causes old log files not to be renamed or deleted. Instead, the new log file is named using the current date and the specified date pattern. You can also specify a log file age limit in the server configuration, so that new log files are created automatically (e.g., one a day). The –keepStdOut option can be used in conjunction with the –logfile option. It causes standard output to be sent to the log file as well as standard output. The –tz option is used for specifying timezone. It must be followed by a timezone region (e.g., "America/Los_Angeles"). All times calculated within the application will conform to this timezone. The –minZip option is used for specifying a minimum limit for compressing clientserver data exchanges – any data (message content or file) below this limit is not compressed. It must be followed by an integer value (defaults to 1024 bytes). This option applies to servers (you can also override it in the server configuration). Clients obtain the same value from the server during initial handshake and use it subsequently. 230 JavaGram Agile Development The –clearCache option can be used for clearing the JavaGram file cache on client or server side. It takes no argument. The –c option takes no argument and causes nominated JavaGram source files (see Section 7.1.3) to be compiled rather than loaded. The –r option takes no argument and applies to loading or compiling files. If the source is a directory then it’s processed recursively (i.e., the files in the directory and the files in all its sub-directories, and so on). The –cScope option takes a file path as argument and assumes that it contains a list of files to be compiled, each specified on a separate line using either its absolute or relative (to sys.root) path. The –cToDir option takes a directory path as argument and treats that directory as the target for compiled files. If the directory path contains spaces, enclose it double quotes. If no compilation target directory is specified then compiled files will be written to the same directory as the source files. A file path on its own (either absolute or relative to sys.root) is assumed to be a JavaGram script and is loaded (or compiled if the –c option is also present). 7.1.2 Java Options In the command line that you use to run a JavaGram program, you can also specify Java runtime options. You’ve already seen the use of the –cp option to specify the class path for JAG.jar. Consult the Java documentation for other available options. The following Java options are commonly used for running JavaGram programs: The –client option selects the ‘client’ Java VM. This is the default VM. The –server option selects the ‘server’ Java VM. The –Xms option is used for specifying the initial Java heap size (e.g., -Xms256m). The –Xmx option is used for specifying the maximum Java heap size (e.g., -Xmx1024m). The –Xrs option reduces the use of OS signals by Java VM. 7.1.3 Compilation To compile a JavaGram program, use the –c option. For example, java –cp JAG.jar jag.run.Env -c C:/JavaGram/quest/Quest.jag compiles Quest.jag to produce Quest.jax in the same directory. Alternatively, java –cp JAG.jar jag.run.Env -c –r C:/JavaGram/quest/ recursively compiled all files in the specified directory. To send the result of compilation to a different directory, use the –cToDir option. Deployment 231 Another method of specifying the files to be compiled is to list them in a scope file. For example, in java –cp JAG.jar jag.run.Env -c –cScope C:/JavaGram/BuildScope.txt the file appearing after –cScope contains the list of files to be compiled. In this scope file, each file must be specified on a separate line, using either its absolute or relative (to sys.root) path. This method can also be combined with the above methods. Using compiled code has two advantages: Compiled code is more secure, especially when you intend to package and distribute it to other parties. Compiled code runs faster because the JAG runtime doesn’t have to do as much processing and analysis. Within JAG, compilation is managed by a module called codec (coder/decoder), which handles both the encoding of clear text scripts into binary format, and the decoding of binary scripts such that they can be loaded. The codec is version controlled. If, as a part of the JAG maintenance cycle, a codec rule is changed, the codec version number is incremented. Each compiled script records the codec version with which it has been compiled. This is internally used for consistency checking. 7.2 Server Deployment Before deploying a server, make sure that you have JRE and JAG installed on your machine. 7.2.1 Application Server A JavaGram application server is totally generic – it makes no assumptions about what particular application it’s going to support. As far as the application server is concerned, it hosts a collection of JavaGram scripts (similar to the way a web server hosts a collection of web pages) and responds to client requests to perform operations on these scripts (respond to download requests, perform remote calls, etc.) Consequently, a single application server can host many different applications with potentially overlapping functionality. 232 JavaGram Agile Development Application server deployment involves two scripts: The library script lib/svr/AppServer.jag creates a server by calling the sys.appServer() method. The server awaits client connection requests on a nominated port, and responds to a successful connection request by creating a session thread and handing over the client to that thread. A server configuration script (e.g., quest/Config.jag) which defines important runtime parameters. The parameters are organized as a map, whose keys denote the parameter names. To illustrate the simplicity of lib/svr/AppServer.jag, we’ve included its code below. <jag domain="lib/svr"> // Use this class to deploy an application server. class AppServer { public static void main () { vector<string> host = sys.strSplit(sys.host, ":"); if (sys.length(host) < 2) { sys.println(sys.err, "invalid -host option: ", sys.host); sys.exit(1); } string hostName = host[0]; int port = sys.toNum(host[1]) @int; symbol mode = sys.config[$mode] == $async ? $async : $sync; // Infinite loop will handle server resets: for (;;) { try { sys.println("=============== APP SERVER STARTING ==============="); sys.appServer(hostName, port, mode); } catch (Exception e) { sys.println(sys.err, "APP SERVER ABORTED; reason: ", e.getMessage()); sys.exit(2); } } } } </jag> Note how an infinite for-loop is used around the call to sys.appServer(). This is because a server can be reset via the JavaGram Server Monitor, causing this method to return, in which case the method is invoked again. To run an application server, use lib/svr/AppServer.jag as your main script. Specify the server host, server configuration (see below), and any other required options in your command line. For example: Deployment 233 java -cp JAG.jar jag.run.svr.Server -root "C:/JavaGram" -config Config.jag -host "localhost:443" "C:/JavaGram/lib/svr/AppServer.jag" Please note that for servers, the security settings are not specified via the –ssl command line option, but in the server configuration file. The configuration file parameters can be a combination of standard and applicationspecific parameters. If you want to build your configuration map dynamically, create the config file as a normal script and define a class within that script, having a static main method that returns the config map. Here is an example of a configuration file that simply contains a map: [ $download => [$default => ["jag" => $private, "*" => $public] ,"quest/gui/" => $public ,"quest/gui/Rights.jag" => $private ,$cache => "mycache/" ] ,$upload => [$default => ["*" => $public]] ,$policy => "flash/SocketPolicy.xml" ,$security => [$keystore => "ssl/JAG.jks" ,$password => "changeit" ,$trusted => ["quest/Quest.jag", "monitor/Monitor.jag"] ,$exclude => ["testhost:443", "testhost:444"] ] ,$mode => $sync ,$variant=>[$portPlus=>100, $secure=>false, $async=>true] ,$partition => [$cache => "parts" ,$exclude => [".svn/*", "*.jagzip"] ,"QuestPart" => [("quest/", $recursive, "*.jag", "*.html", "*.gif") ,("lib/", $recursive, "*.jag", "*.gif") ] ] ,$session => [$timeout => 300, $error => 10] // 5 minutes timeout ,$log => [$timeout => 86400] // 1 day per log file ,$zip => [$min => 1024] ,$load => "ServerInit.jag" ,$database => [$mysqlDb => [$name => "MySql", $url => " jdbc:mysql://localhost/quest " ,$user => "root", $password => "password" ,$timeout => [$login => 30, $query => 20] ,$driver => "com.mysql.jdbc.Driver" ] 234 JavaGram Agile Development ,$oracleDb => [$name => "Oracle", $url => "jdbc:oracle:thin:@localhost:1213:quest" ,$user => "test", $password => "secret" ,$timeout => [$login => 30, $query => 20] ,$driver => "oracle.jdbc.OracleDriver" ,$schema => "Quest" ] ] ] You need to ensure that: The $download entry reflects the desired access rights to server-side files, which may be private (inaccessible) or public (accessible). The above map, for example, specifies that by default ($default) all .jag files are private and all other file types (*) are public. This default behavior is overridden by the next two entries: all files in the quest/gui/ directory are public, except for quest/gui/Rights.jag which is private. As a general rule any files types not covered by the rules default to private. Note that, to execute, a client doesn’t need source (.jag) files; it always uses compiled (.jax) files instead. Making .jag files private protects you against a client attempting to access your source code. The $cache entry (within $download) specifies a directory for serverside file caching. If not specified, the directory <root>/cache/ is used instead. The $upload entry is similar to the $download entry in its syntax, and optionally specifies what a client application can upload to the server. Files specified as $public can be uploaded, whereas files specified as $private are blocked. The $policy entry optionally specifies a socket policy XML file for access by Flash clients. The path to the policy file may be absolute or relative to sys.root. See https://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html for more details on socket policy files. If no policy file is specified, the following inbuilt policy is used instead: <cross-domain-policy> <allow-access-from domain="*" to-ports="*"/> </cross-domain-policy> The $security entry optionally specifies the security configuration of the server. The above example provides SSL security using a certificate stored in the nominated keystore. The path to the keystore may be absolute or relative to sys.root. The $password is needed but of no consequence. The $trusted vector is optional, and can be used to enforce additional security. When specified, a client application must use only one of the specified relative scripts as its main script. Otherwise, a client application can start with any script. The $exclude vector is optional, and can be used to specify hosts that don’t use SSL security. The $mode entry specifies the application server mode, which may be one of $sync or $async (defaults to $sync). Deployment 235 The $variant entry optionally specifies a variant server. When present, it should point to a map. The value denoted by the $portPlus key in this map is added to the server port to produce another port, on which a variant server is deployed. The map may also contain an $async and a $secure entry (both being booleans) for overriding the main server settings. In our example, this causes two servers to be deployed: a synchronous SSL server on port 443 and an asynchronous non-SSL server on port 543. The $partition entry optionally specifies partitions for use by native or Flash clients. See Section 7.4 for details. The $session entry optionally specifies a timeout period and maximum error limit for server sessions. The timeout should be expressed in seconds. If not specified (or zero or negative), server sessions will not timeout. Session timeout is a practical means of removing rogue sessions, which may have got into trouble due to internal error, communication error, or infinite loops. If you use session timeout then you should also use a timer in clients to ping the server to avoid healthy sessions timing out. Sessions that throw more runtime errors than the specified limit are terminated. The $log entry optionally limits the age of a log file. When a log file becomes older than the specified limit, a new one is opened. The naming of the new file is dependent on how the log file has been specified (see –logfile runtime option in Section 7.1.1). The $zip entry optionally specifies compression requirements. When specified, all messages and files exchanged between clients and server sessions are compressed, provided they exceed the minimum size denoted by $min (in bytes). The $load entry optionally specifies a file to be loaded by the server as soon as the configuration file has been processed. The $database entry specifies database connections. Each database is specified using a key (e.g., $mysqlDb) which you can use in your code to depict a connection. For each database, you can specify the following (see Chapter 12 for more detail on each option). $name provides an arbitrary descriptive name for the database. $url specifies the URL of a specific database instance. $user specifies a valid username for database login. $password specifies a matching password for database login. This string may be enclosed in square brackets to signify that it is an encrypted value. $driver specifies a fully qualified JDBC driver class name for the database. $timeout optionally specifies login and query timeout periods in seconds. $schema optionally specifies a schema other than the default database schema. $pool optionally specifies additional parameters for controlling the JDBC connection pool. 236 JavaGram Agile Development Where encrypted database passwords are used, a file containing a key must be created and used to encrypt the passwords. This may be accomplished by running: java -cp JAG.jar jag.misc.Cypher keyFileName This program will attempt to read a key file at the specified location but in the initial case, where a key file cannot be found, a new key file is created. The program then prompts for password values that it will then encrypt. 7.2.2 Proxy Server A proxy server is an intermediate server that directs client traffic to application servers, without affecting the traffic content. Proxy servers provide these benefits: Failover. If an application server fails for any reason, the proxy server detects this and directs the traffic to other functioning application servers instead. Load balancing. The proxy server can distribute the traffic amongst application servers according to load balancing parameters, thus ensuring that the application servers are evenly utilized. Added security. In a production environment, the proxy server can be placed in the demilitarized zone (DMZ) and the application servers placed within the secure network outside the DMZ. This protects the application servers from external attacks. A single proxy server can represent multiple application servers. However, to avoid a single point of failure, multiple proxy servers can be deployed in failover or load balanced manner. In this case, the selection logic is either provided by the client (JavaGram Application Booter has a feature to support this) or by a third-party load balancing tool. Proxy servers are deployed in a manner similar to application servers, except that the lib/svr/ProxyServer.jag script is used instead of lib/svr/AppServer.jag. For example: java -cp JAG.jar jag.run.svr.Server -root "C:/JavaGram" -config ProxyConfig.jag host "localhost:444" "C:/JavaGram/lib/svr/ProxyServer.jag" Deployment 237 A proxy server configuration file is similar to an application server configuration file, but typically has fewer settings. Example: [ $download => [$default => ["jag" => $private, "*" => $public]] ,$upload => [$default => ["*" => $public]] ,$security => [$keystore => "C:/JavaGram/ssl/JAG.jks" ,$password => "changeit"] ,$session => [$timeout => 300, $error => 10] // 5 minutes timeout ,$log => [$timeout => 86400] // 1 day per log file ,$zip => [$min => 1024] ,$proxy => [$loadBalance ,[$host => "localhost", $port => 443, $weight => 1.0, $keystore => "C:/JavaGram/ssl/JAG.jks"] ,[$host => "localhost", $port => 442, $weight => 0.5, $keystore => "C:/JavaGram/ssl/JAG.jks"] ] ] Note how the $proxy entry specifies the application servers for this proxy server. See sys.proxyServer() in Chapter 10 for details of how an application server is specified in this context. 7.2.3 Multi-tier Servers In most cases, a client communicates with a single logical server, though the latter may have multiple physical instances for failover or load balancing. In general, however, a client can communicate with multiple logical servers, and these servers may in turn be clients of other servers. Every client has a default server, from which it’s booted and obtains its code. On the client side, the stream for this server is denoted by sys.loader. When the client communicates with this server, it doesn’t need to specify the server stream. However, for other servers, the stream must be explicitly stated. Here is how the client specifies the target server in a number of possible situations: To open a stream connection to a server, the client should use the sys.client() method. To invoke a remote call on a non-default server, the client must use the targeted remote call syntax (see Section 6.1.4). 238 JavaGram Agile Development To download a script from a non-default server, the client must specify the source server’s stream, by passing it to the relevant method such as sys.download(), sys.use(), or sys.rload(). When using <load>, the server stream can be specified using the source property. To upload a file to a non-default server, the client must specify the target server’s stream, by passing it to sys.upload(). 7.2.4 Deploying a Server as a Service On the Windows platform, it’s often desirable to install a server (especially a production server) as a service, so that administrators can logout of the server machine without the server stopping. To do this, you need the Java Service tool, which you can download from http://forge.ow2.org/projects/javaservice/. This tool is open source and is provided under the GNU Lesser General Public License (LGPL). Please note that Java Service requires JDK installed on your machine – JRE is insufficient. 7.3 Native Client Deployment To deploy a native client, make sure that you have JRE and JAG installed on your machine. To run a client, define a command line shortcut using the options described in Section 7.1 based on whether it’s a GUI or non-GUI client. To make client deployment easier for users, you may want to consider creating an installation executable that installs JAG.jar, JAG.jks, and an appropriate command line shortcut that points to the desired server. 7.3.1 JavaGram Application Booter When you run a JavaGram application using the –boot option, the following window is presented to the user. This window is presented as read-only when the –stage command line option is set to prod, in which case, it serves only as confirmation that the application is being booted from a remote server. As soon as the boot sequence is completed, the window is removed and the application is displayed instead. The Main Script, Server Host and SSL Keystore fields within this dialog are pre-populated from the corresponding command line options. Deployment 239 If you want to give the user the opportunity to visually change these settings then don’t set –stage to prod. After changing the settings, the user should press the Run button for the boot sequence to begin. The Main Script field can optionally specify a partition. For example, the value CrmPart:crm/Main.jag will be treated as partition CrmPart and main script crm/Main.jag. The partition is downloaded first, followed by a request for the script (which may also be sourced from the partition, depending on its composition). If a file named <root>/boot.jag exists then this file is used to lookup alternative hosts for the Server Host field. This file should contain a map, against which the Server Host is looked up. For example, if the file contains [ "myhost:443" => [$failOver, "test1:443", "test2:443"], "yourhost:443" => [$loadBalance, "test1:443", "test2:443"] ] then if the Server Host field contains myhost:443, it’s replaced by three fail-over hosts: myhost:443 test1:443 test2:443 and if it contains yourhost:443, it’s replaced by three load-balanced hosts: yourhost:443+test1:443+test2:443 This method allows you to effectively change the –host runtime value, without having to amend the application shortcut installed on a user’s machine. The boot.jag file can be downloaded to a user’s machine through a sys.use() call in the application code, and will take effect the next time the user runs the application. Some user sites route their traffic through a firewall proxy server. In such cases, to run a JavaGram client application, correct proxy server settings must be provided. These can be specified in the following fields: The Proxy Host field should contain the host name (or IP address) and port number for the proxy server, in format host:port. If the proxy server also requires authentication then the Proxy Username and Password fields should also be filled. Otherwise, these fields should be left blank. These fields automatically default to the values stored in <root>/proxy.jag. Any proxy server changes made by the user in the Booter window are saved back to this file. For security, the password field is always stored in encrypted format. 240 JavaGram Agile Development 7.3.2 Automatic Upgrade Once a JavaGram process loads a file, any future attempts to load it will be ignored, unless the file has been modified since the last load. (When the latter happens on the server side, the file is recompiled and the cache is updated.) An updated file will be successfully re-loaded if the structure of the classes within it haven’t changed. In other words, the changes must be limited to the implementation of methods. Here are a couple of typical scenarios illustrating how code changes are automatically distributed: The server code base is in source format (.jag files). A source file on the server is patched to fix a bug. The fix affects the logic of a method, but not its signature. When a client (or server session) attempts to load the file, the file is compiled and the server-side cache is updated. The client (or server session) obtains and reloads the file successfully and hence the patch takes effect. The server code base is in binary format (.jax files). A binary file on the server is replaced by a patched version to fix a bug. The fix affects the logic of a method, but not its signature. When a client (or server session) attempts to load the file, the file is decompiled and recompiled to update the server-side cache. The client (or server session) obtains and reloads the file successfully and hence the patch takes effect. In summary, whether you patch a server in source or binary format, the patch is automatically distributed. You don’t need to re-start the server. The only other component that may potentially change and require distribution to clients is JAG.jar. You can implement a simple scheme in your application that allows this file to be also distributed to clients. The process is managed by the library script lib/gui/Upgrade.jag. To check for an upgrade of JAG.jar, instantiate the Upgrade class and override its getRebootCmd() method, as illustrated below for the Quest sample application. Then invoke the check() method and pass it the minimum required major and minor version of JAG.jar (e.g., 1.3 in this example). Upgrade upgrade = new Upgrade() { protected string getRebootCmd (string jarPath) { return $"javaw.exe -cp \"{jarPath}\" jag.gui.Gui -host \"{sys.host}\" -stage {sys.stage} -root \"{sys.root}\" -ssl \"{sys.ssl}\" -boot quest/Quest "; } }; upgrade.check(1, 3, "jar/JAG.jar", "jar/JAG.jar", sys.loader); This code snippet effectively states that this application relies on having at least JAG version 1.3. The code snippet should appear as early as possible in the application, before the application GUI is rendered. Deployment 241 If the JAG used to run the client falls short of the minimum version, the following dialog will appear, inviting the user to initiate a download of the JAG used by the server (which presumably will meet the minimum required version, otherwise the server has been deployed incorrectly). If the user presses the Download button, the server-side JAG will be downloaded into the client, the client will exit and automatically re-run with the new version. At this point, the new JAG permanently replaces the old one. 7.4 Browser Client Deployment Browser-based clients use the Flash version of JavaGram runtime (JAG.swf). This runtime is not invoked directly but through HTML. 7.4.1 HTML Embedding The minimum HTML required to embed JAG.swf is shown below. <object width="600" height="400"> <param name="movie" value="JAG.swf"> <embed src="JAG.swf" width="100%" height="100%"/> </object> However, this approach is not recommended as it does not cater for issues such as detecting the presence of the Flash Player, browser idiosyncrasies, passing the required arguments to JAG.swf, etc. The easiest and safest way to embed JAG.swf in your HTML is to use the JavaGram IDE (see Chapter 14). If you configure a browser client in JADE and run it, it will produce the necessary HTML file automatically. You can then model your HTML file based on the generated one. Your JavaGram installation contains the necessary components (within the flash/ subdirectory) for deploying browser clients, as summarized in the following table. flash/AppTemplate.html Template HTML used by JADE to generate your client HTML file flash/AC_OETags.js JavaScript used by HTML files flash/crossdomain.xml Flash cross-domain policy file flash/JAG.swf Flash version of JavaGram runtime flash/playerProductInstall.swf Used by HTML files for managing the installation of Flash Player, if not present flash/history/ Contains files for managing browser history 242 JavaGram Agile Development flash/html/ Client HTML files generated by JADE are placed in this sub-directory The particular file of interest is AppTemplate.html, which contains complete code for HTML embedding of JAG.swf. Within this file, you’ll notice the following line (formatted as multiple lines here for ease of reading): "FlashVars", "partAndScript=<<PartAndScript>> &hostAndPort=<<HostAndPort>> &transport=<<Transport>> &warning=<<Warning>> &verbose=<<Verbose>> &stage=<<Stage>>" This is how runtime arguments are passed to JAG.swf. The placeholders <<…>> are replaced with valid values by JADE when it generates a HTML file. Here is an example for the Quest sample application after substitution: "FlashVars", "partAndScript=QuestPart%3Aquest%2FQuest.jag &hostAndPort=localhost%3A543 &transport=socket &warning=false &verbose=false &stage=prod" The values must be URL-encoded (www.w3schools.com/TAGS/ref_urlencode.asp) so, for example, QuestPart:quest/Quest.jag becomes QuestPart%3Aquest%2FQuest.jag. The following FlashVars are supported: partAndScript denotes the initial partition and script, separated by a colon. Partitions are explained in the next section. hostAndPort denotes the target server host and port, separated by a colon. transport denotes the means by which messages are transported between client and server ends, and may be one of socket, http, or https. warning may be set to true or false. When set to false, warning messages are suppressed. verbose may be set to true or false. When set to true, the client runs in verbose mode and outputs all messages exchanged with the server to the log. stage may be set to dev, test, or prod. Deployment 243 7.4.2 Flash Security Sandbox Flash security prevents an SWF file from accessing local resources. If an SWF file is sourced from a domain then it’s allowed to access resources (e.g., images) from that domain. When you test a browser client in JADE, JAG.swf is loaded from the local disk. When your application attempts to load images from a partition (whose data is sourced from the server), Flash security may block this by (incorrectly) assuming that you’re attempting to load an image from the local disk. You can work around this issue by placing JAG.swf in the local-trusted sandbox. To do so, create the following directory if it doesn’t exist: C:\Windows\System32\Macromed\Flash\FlashPlayerTrust\ The above path is for Windows7. The actual path may be different on your OS. Create a file named PragSoft.cfg and add one line to the file for each SWF file you want to add to the local-trusted sandbox. For example: C:\JavaGram\flash\JAG.swf 7.4.3 Partitions Because of the Flash asynchronous behavior and security restrictions, JAG.swf uses a different procedure to obtain JavaGram code from the server. As we saw earlier, JAG.jar downloads compiled JavaGram scripts from the application server and caches them on the client side. JAG.swf cannot do this because Flash security prevents it from accessing the local disk storage. Instead, it uses partitions. A partition is a server-generated group of pre-processed files, packed in ZIP file. The makeup of each partition is specified in the server configuration file (under the $partition key). You can include any type of file in a partition (JavaGram scripts, GIF files, HTML files, etc). When the application server starts, it generates the partitions specified in its configuration. Every JavaGram script included in a partition is validated through compilation. The client-side version of the compiled file is then decompiled and included in the partition. For security, no server-side code is ever included in a partition. You can also use partitions for native clients. This will speed up the client because there will be far fewer server calls to download individual files. Consider the following partition entry in a server configuration file. ,$partition => [$cache => "parts" ,$exclude => [".svn/*", "*.jagzip", "report/generated/*.pdf"] ,"QuestPart" => [("quest/", $recursive, "*.jag", "*.html", "*.gif") ,("lib/", $recursive, "*.jag", "*.gif") ] 244 JavaGram Agile Development ] The $cache key optionally denotes a server-side directory name for where the partitions are deposited (defaults to parts if not specified). The $exclude key optionally specifies file patterns to be excluded from all partitions. Use this as a convenient means of excluding files that would otherwise match the partition rules. Any string key within the partition map (e.g., "QuestPart" above) is treated as a partition name, and must denote a vector of file patterns. All file patterns are relative to the server’s sys.root. The following file patterns are supported: An explicit file path. For example, "crm/common/Utils.jag" matches exactly one file. An explicit file path with wildcard for file name, but not any other part. For example, "crm/login/bo/*.jag" matches all JAG files directly inside the nominated directory. A directory pattern. For example, ("crm/login/gui/", "*.jag", "*.gif") matches all JAG and GIF files directly inside the nominated directory, whereas ("crm/login/gui/", "*.*") matches all files directly inside the nominated directory. A recursive directory pattern. For example, ("crm/login/gui/", $recursive, "*.jag", "*.gif") matches all JAG and GIF files inside the nominated directory and its sub-directories recursively, whereas ("crm/login/gui/", $recursive, "*.*") matches all files inside the nominated directory and its sub-directories recursively. A partition/file exclusion pattern. For example, ($exclude, "ReportPart", "crm/db/Gen.jag") causes every file within the ReportPart partition as well as the file "crm/db/Gen.jag" to be excluded from the current partition. The list can contain multiple partition names and/or file paths to be excluded. No wild-card patterns are allowed. A Flash client requires at least one partition (called the initial partition) which is specified via the partAndScript parameter described earlier. The client may load additional partitions using the sys.partition() method (see Chapter 10). Received partitions are unpacked by the Flash client and their content kept in memory for later reference. When a Flash client attempts to load a script or use a file, it looks for it in the loaded partitions. If the file is not present in any of the loaded partitions then it’s flagged as an error. You can also use partitions for a Java client. This is useful if you want to improve performance by reducing the number of times the client contacts the server for the files it requires. All relatively loaded files and files requested via sys.use() are sourced from partition(s) if present in at least one partition. Files requested via sys.download() are never sourced from a partition. Deployment 245 7.4.4 Deploying to a Web Server Assuming that you use socket transport, to test a Flash client, you don’t need a web server. JADE allows you to run a client directly. You can also go to the flash/html/ directory and open the client’s HTML file directly with a browser. However, if you customize your client HTML file further and introduce things that require access to a web server, then you should deploy your HTML file to a web server. To deploy to a web server, copy your flash/ directory to the root of your web server. You only need to do this once. Subsequently, a modified HTML file can be copied to <WebServerRoot>/flash/html/. If your web server points to, say, the domain www.acme.com and your client HTML file is called, MyApp.html, you can access it using this URL: http://www.acme.com/flash/html/MyApp.html You can also set your application’s client root in JADE to point to the root of the web server so that generated HTML files are deposited there directly. 7.4.5 Server Deployment with BlazeDS When you specify your Flash client transport as http of https, you must deploy your application server in a servlet container (such as Tomcat). JavaGram uses Adobe’s BlazeDS as the underlying implementation for HTTP/HTTPS communication, which provides an efficient binary communication protocol called AMF. In this case, the servlet container performs the session management, so you don’t need to boot an application server (or, more accurately, you must not boot an application server). BlazeDS is a free product from Adobe. To use it (for example in Tomcat), download its WAR file (blazeds.war) and place it in Tomcat’s webapps/ directory. Tomcat will automatically expand the WAR file and produce a webapps/blazeds/META-INF/ and webapps/blazeds/WEB-INF/ directories. Alternatively, you can expand the WAR file yourself using a ZIP program such as 7-zip. Rename the blazeds/ directory as JAGBlaze/. The following directories are of particular interest: webapps/JAGBlaze/WEB-INF/lib/ contains the BlazeDS JAR files. You must add any JAR files required by your server (JAG.jar, JDBC derivers, etc.) to this directory. webapps/JAGBlaze/WEB-INF/flex/ contains the XML configuration files for BlazeDS. Open the flex/remoting-config.xml file and add the following destination. <destination id="JagBlazeService" channels="my-amf"> <properties> <source>jag.run.svr.Session</source> <scope>session</scope> </properties> </destination> 246 JavaGram Agile Development This destination defines the service that JavaGram uses for BlazeDS communication. The flex/services-config.xml file defines the channel set for BlazeDS communication. However, you don’t need to define the channels in this file, as JAG.swf creates the channel set and channels dynamically from your nominated server address (as specified in the HTML file). For ease of deployment, it’s recommended that you package the above steps as a deployment script that produces a ZIP file that you can simply expand for all the files to go to the right places in your servlet container. You also need to place a Flash cross domain policy file in the Tomcat root directory (e.g., tomcat/webapps/ROOT/). For example: <cross-domain-policy> <site-control permitted-cross-domain-policies="all"/> <allow-access-from domain="*" to-ports="*"/> <allow-http-request-headers-from domain="*" headers="*"/> </cross-domain-policy> Finally, you must define an environment variable called JAGBLAZE_OPTIONS. For example: -noWarn -root "C:/JavaGram/" -config acme/Config.jag lib/svr/BlazeServer.jag where lib/svr/BlazeServer.jag is the BlazeDS adaptation of lib/svr/AppServer.jag. Unlike normal JavaGram application servers (which boot as soon as the server is deployed) this server will boot (causing partitions to be generated, etc.) when the first session is created in response to a client connection. Therefore, the first client connection will be slower than usual. After making all your changes, you should restart Tomcat so that your changes take effect. 7.5 Standalone Deployment Standalone deployment is intended for applications that run independently. To make it easier for users to install the release, you should package it as an executable installation program that installs JAG, the application code, and any other necessary components (e.g., database drivers). If you want to protect the application’s intellectual property (IP), we recommend that you release it in compiled rather than source format. To compile the application, either use JADE’s build facility (see Section 14.2.6) or use the compilation command line explained in Section 7.1.3. To allow the user to upgrade to the latest version of the application, you can implement a scheme whereby, upon start up, the application connects to a pre-nominated server, checks for new releases, and gives the user the option to upgrade to it. It’s up to you how Deployment 247 sophisticated you want this process to be. A minimal approach would just download the release and ask the user to install it. A fully automated approach would perform the download and install it automatically. 7.6 File Caching JavaGram provides extensive file caching on both client and server sides to improve runtime performance. The basic principle of file caching is to avoid processing or transmitting a file unless it’s absolutely necessary. 7.6.1 Server-side Caching All compiled files are cached by the server. The server will only recompile a script if its source file has been modified since it was last compiled. As a rule, a JavaGram server always compiles a script before making it available for running on the client or the server side. For example, if a client attempts to access Quest.jag or a server attempts to load Quest.jag, the server compiles Quest.jag to produce two separate compiled versions of it – one targeted for the client and one for the server. The compiled client version excludes all slocal methods and the implementation of all remote methods. The compiled server version excludes all clocal methods and all non-server code (such as GUI members). This ensures that neither side has access to code that isn’t intended for it. A compiled client side script that exceeds the zip threshold specified by the server configuration is also compressed so that it’s ready for transmission to clients. The server-side cache looks like this: <root>/cache/client/script.jax <root>/cache/client/script.jax.jagzip <root>/cache/server/script.jax // compiled for clients // compiled and compressed for clients // compiled for server Note that compilation performed by a server is different from explicit compilation using the –c command line option. The latter compiles a .jag file to produce an equivalent .jax file. This is useful for packaging an application so that you don’t have to distribute its source. Server compilation produces .jax files targeted at client and server sides, with irrelevant information removed. This is for the internal caching mechanism of JavaGram and not suitable for explicit packaging and distribution. 7.6.2 Client-side Caching For efficiency, all client-side scripts are cached. The client will not download a file unless its cached version mismatches the version on the server-side. The client-side cache looks like this: <root>/<relative-dir>/script.jax 248 JavaGram Agile Development When the client attempts to load a script, a message is sent to the server which includes the timestamp of the client-side cached file (if any). The server will only transmit the file if the timestamp of the file it has in its cache mismatches that of the client’s. The client will either receive this file (to update its own cache) and use it, or is told by the server to use what it already has (because it’s up to date). An exception to this rule is when the client discovers that its cached version of a script has been encoded by an older version of the JAG codec. In this case, the client forces the script to be downloaded. A partition downloaded by a client is also cached (under <root>/parts/). Because a server cases all the partitions to be regenerated, it will always cause subsequently run clients to re-download requested partitions. 7.7 JavaGram Server Monitor A deployed server requires some monitoring to ensure that it has enough resources to support user demand, and to attend to any potential runtime issues as reported in the server log. The JavaGram Server Monitor (JSM) is a utility written in JavaGram that allows you to do this remotely, without having physical access to the server. This section describes the monitor’s user interface and functionality. 7.7.1 Running JSM JSM is part of the JavaGram release – you’ll find its source code under the monitor directory. The procedure for running JSM is exactly the same as any other JavaGram client application. For example: javaw -cp C:/JavaGram/JAG.jar jag.gui.Gui -root C:/JagClient -ssl ssl/JAG.jks host localhost:443 -stage prod -noWarn -boot monitor/Monitor.jag When you run JSM, it initially displays the following dialog. Because JSM can connect to multiple servers and remembers the server list from a previous run, you can use this dialog to manage the monitored server list before monitoring commences. The servers are displayed in a table. You can add to the list by pressing the Add Server button and directly editing the inserted row. To remove rows, tick their ‘Remove details’ column and press the Remove Details button. When you’ve Deployment 249 finished, press OK – the dialog will disappear and the monitor’s main window will appear instead. 7.7.2 Server Tabs JSM displays a tab for each monitored server, named according to the server’s host and port. Within this tab, a number of sub-tabs appear that display information about that server. This information is periodically updated. The Sessions graph tab displays the number of live sessions and live threads. The Sessions table tab displays a list of all live sessions. A set of buttons to the right of the table allow you to act on the table. 250 JavaGram Agile Development The Server Load tab displays a number of load indicators for the server, such as database requests, server requests, and active server requests per second. The Memory tab displays the maximum memory available to the server (as determined by a Java runtime option), the total memory allocated, and memory actually used so far. Deployment 251 The DB Pools tab displays a bar chart of the database connections utilized by the server. The DB Queries tab shows details of the currently active database queries. This table is empty unless there are queries currently in progress. 252 JavaGram Agile Development The Server Log tab displays the contents of the server log file. This tab is empty unless server output has been redirected to a file using the –logfile command line option. The File Exchange tab provides a view of the file system for the client side (left) and server side (right). You can navigate both file systems and download and upload files/directories by selecting them and pressing the Download or Upload button. You can also compare a file by selecting it from both sides and pressing the Diff button. This is useful as a check before you upload a patch to a server. Deployment 253 The Options button in the JSM toolbar displays a dialog for specifying your preferences. The Add/Maintain Server button displays a dialog for maintaining the list of servers to be monitored by JSM. This is the same as the initial dialog you see when you run JSM. The Suspend button suspends the monitoring of the front-most server tab. When pressed, the button changes to Resume which, when pressed again, will resume the monitoring. The Remove Server button removes the front-most server tab and stops monitoring it. There are also 6 buttons on the extreme right of the JSM window that act on the monitored server(s). The Compact button forces garbage collection of the front-most server tab. The JConsole button displays the Java console for the front-most server tab. 254 JavaGram Agile Development The Broadcast button displays a dialog box for broadcasting a message to users. The scope of the broadcast can be all users on the front-most server tab or all users of all servers. This is useful for informing users about significant events, such as bringing down a server for maintenance. The Reset Server button resets the front-most server tab, causing the server to reload its configuration. No user sessions are affected. Use this if you’ve made changes to a server configuration and you want it to take effect without restarting the server. The Clear DB Pools causes a new database connection pool to be used for new connections in the front-most server tab. Existing database connections won’t be affected. The Evaler button displays a dialog for evaluating arbitrary expressions within the context of the front-most server tab. Type the expression to be evaluated in the top panel and press Evaluate. The result of evaluation will be displayed in the bottom panel. Deployment 255 8 Quest Sample Application This chapter describes the development of a complete JavaGram application to further elaborate the concepts described in earlier chapters. In order to fully grasp a new technology, there is no real substitute for studying the development of a real-life application. This is often the best way to see how techniques and concepts that were earlier described using trivial examples are applied in more complex settings. The application described in this chapter is a multi-user tool for managing project-related issues. To provide you with a complete picture, we’ll describe all aspects of the application, covering: requirements, user interface, class design, and code. After reading through this chapter, we encourage you to experiment – extend the application, add new features, or enhance the existing features. You’ll learn much more through such experiments than simply reading. Quest has been implemented such that it can be deployed in any of the deployments models supported by JavaGram, including: standalone, synchronous client-server, asynchronous client-server, and using a native or a browser-based client. 8.1 Introduction Issues arise in almost every project, representing things that require attention. An issue can be a defect (something that’s not working right), an enhancement (a request to improve something), or a change request (a request to change an earlier requirement). Systematic tracking of issues is essential if project risks are to be managed properly. Because of the complexities involved, most projects use a software tool to at least partially automate the process, thus reducing the risk of administrative errors. 8.1.1 Requirements An issue tracking tool should allow project participants to do the following tasks. Raise and submit a new issue. Assign an issue to an individual, so that they can investigate and/or fix it. Open an issue so that it can be worked on. Resolve an issue. Close an issue after it has been fixed and verified. Reject an issue, if investigation reveals that it’s not really an issue. Find out the status of an issue (its completion state, the person to whom it’s assigned, when it was assigned, its resolution, etc.) View the history of an issue (everything that has been done to the issue to date). 256 JavaGram Agile Development Search for all raised issues using various search criteria. Produce reports on issues. To avoid uncontrolled usage, access to the tool should be limited to authorized users who have been provided with a user account. A user account includes: A username that uniquely identifies the user, as well as a numeric user ID which does the same, but is internally used to keep track of who has done what. A password that allows the user to login to the system. Additionally, the tool should provide administrative functions, including: Ability to create new user accounts. Ability to specify access rights for users (i.e., what they can or can’t do). Ability to enable, disable, and delete user accounts. Ability to change user passwords. Additionally, each user must be able to change their own password. Finally, it’s important that attempts by multiple users to concurrently work on the same data be handled in a consistent and predictable fashion: Multiple users should be able to concurrently view the same issue. Only one user at a time should be able to modify an issue. Only the latest version of an issue should be available for modification. 8.1.1.1 Issue Life Cycle Each issue must follow a defined life cycle, as specified by the following state transition diagram. Quest Sample Application 257 In this diagram, each box represents a state, and each label on a directed line represents an action which, when performed, causes a transition to another state. The initial state for an issue is always New (i.e., all new issues are initially in this state). The final state for an issue is Closed. When an issue is in a given state, the permissible actions that can be performed on it are denoted by the arrow labels emanating from the state. For example, for an Opened issue, the permissible actions are: Assign, Resolve, and Reject. Quest must enforce these rules: The user must not be able to perform an action on an issue, other than the permissible ones (e.g., the user must not be able to close an assigned issue). Only the administrative user should be able to reopen a closed issue. This is reserved for when an issue is closed prematurely, and needs to be reopened. All actions and state changes must be recorded in the issue’s history. Additionally, the above state transition diagram must be soft-coded so that it can be easily changed. 8.1.1.2 Issue BO The application must be able to store the following information for each issue Business Object (BO): ID. Each issue is identified by a unique numeric ID. This ID is allocated by the database and is read-only. Title. Provides a descriptive summary for the issue. Project. Denotes the project to which the issue applies. Module. Denotes the project module to which the issue applies. Build. Denotes the build to which the issue applies. Type. Identifies the type of an issue as one of: Defect, Enhancement, or Change Request. State. Represents the current state of the issue, as defined by its life cycle. Severity. Identifies the issue severity as one of: Critical, High, Medium, or Low. Priority. Identifies the issue priority as one of: Critical, High, Medium, or Low. Raised By. Is automatically set to the user ID who originally created the issue. Raised On. Is automatically set to when the issue was created. Assigned To. Is automatically set to the user to whom the issue is assigned. Description. Provides a full description of the issue. 258 JavaGram Agile Development Resolution. Described how the issue has been resolved. 8.1.1.3 History BO Each time an issue changes state, this change must be recorded using a History BO, which contains the following information: Actioned By. Identifies the user who caused the state change. Action. Identifies the action that caused the state change. Date. Specifies the date on which the action was performed Old State. Identifies the previous state of the issue. New State. Identifies the new state of the issue. Note. The description entered by the user (if any) when performing the action 8.1.1.4 User BO The application must be able to store the following information for each user: ID. Each user is internally identified by a unique numeric ID. Username. Each user is externally identified by a unique name. Last Name. Specifies the user’s real last name. Last Name. Specifies the user’s real first name. Password. The password for the user account. Note. Any additional information pertaining to the user. Active. Boolean flag indicating whether the user is currently active. Only active users can login to the system. Can Unlock. Boolean flag indicating whether the user can unlock locked objects. Can Delete. Boolean flag indicating whether the user can delete objects. When you install Quest, a special user account (Username=admin) is pre-created. This user has special administrative rights, including the ability to create and delete user accounts. However, the admin account itself can’t be deleted. 8.1.1.5 Attachment BO The application allows documents (e.g., screen shots, test results) to be attached to issues to serve as supporting documentation. An attachment captures the following information: ID. Each attachment is internally identified by a unique numeric ID. Issue ID. The ID of the issue to which this document is attached. Name. Actual document name. Size. The document size in bytes. Quest Sample Application 259 Modified. The document’s last modification timestamp. 8.1.1.6 Rules BO The Rules BO captures the rules governing the issue life cycle described earlier. By capturing this information in a BO rather than hard-coding it, we make it possible for the rules to be modified from a central location. 8.1.2 User Interface The user interface for Quest consists of a set of windows. The user is first presented with a Login window. After successful login, a main window is displayed which remains active until the application is closed. The main window contains a number of panels and can display other dialogs, depending on the task being performed by the user. The following hierarchy summarizes all the user interface elements. Login Window Main Window Menu bar Toolbar Navigation Tree Content Pane User Search Panel User Details Panel Change Password Dialog Issue Search Panel Issue Details Panel Description Tab Resolution Tab Attachment Tab History Tab Assign Dialog Resolve Dialog Reject Dialog HTML Panel (for help and reports) Log Panel Pick Lists Panel 8.1.2.1 Login Window The Login window is the first window displayed when the application is run. It invites you to provide your username and password to be granted access. 260 JavaGram Agile Development For security, the password field will echo a * for each typed character. Invalid username and password combinations are rejected. Pressing Cancel will abort the login and quit the application. 8.1.2.2 Main Window The main window is displayed immediately after successful login. The window title bar indicates the mode in which the application is run. The title may contain one of: Standalone. This means that the application is entirely running on the user’s computer. The database server, however, may be on a different host. Host IP address and port. This means that the application is running in client-server mode, allowing multiple users (clients) concurrent access to the same server. The displayed host address refers to the host on which the server runs, and the port refers to the port on which the server listens for requests. Quest Sample Application 261 The main window contains a navigation tree on the left side. When you select a node in this tree, the content pane on the right side displays the information content of that node. For example, in the above figure, the Help node is currently selected, causing the main help page to be displayed. A split pane separates the navigation tree and content pane, so that the vertical border between the two can be dragged sideways. 8.1.2.3 Menubar and Toolbar The menubar in the main window contains a set of pull down menus, which provide commands for performing tasks. For example, selecting the Exit command from the File menu will close the application. The toolbar provides a set of buttons for commonly used commands. When you select a node in the tree, these buttons are enabled/disabled to visually indicate what you can do with that node. These buttons are: Save. This button is enabled when a modified node in the tree is selected. A modified node always has a * next to it, to indicate that it requires saving. Pressing Save causes the data for the node to be saved to the database. Save All. Same as Save except that it saves all unsaved nodes in the tree. Preview. This button is enabled when a help node or report node is selected. Pressing it causes the HTML page for the node to be displayed in a separate browser window. Hide. This button is enabled when an issue, user, or report node is selected in the tree. Pressing it cases that node to be removed (not deleted) from the tree. Unlock. This button is enabled when a modified issue or user node is selected in the tree. Pressing it causes the issue or user object to be unlocked. The user will be prompted to confirm the operation. Only users who have been given ‘unlock’ permission can perform this task. Delete. This button is enabled when an issue, user, or report node is selected in the tree. Pressing it cases that node and its data to be permanently deleted. The user is prompted to confirm the operation. Toggle Tree. This button hides the navigation tree to maximize the available space for the content pane. Back. This button causes the previous node (in the navigation chain) to be selected instead. Forward. This button causes the next node (in the navigation chain) to be selected instead. Refresh. This button refreshes the current content pane by re-fetching its data from the database. Report. This button is enabled when a reportable node is selected. It generates a HTML report that contains the node data, and adds it as a child of the Reports node. 262 JavaGram Agile Development XML Data. This button is enabled when a user or issue node is selected. It displays the node’s underlying data in XML format. JAG Data. This button is enabled when a user or issue node is selected. It displays the node’s underlying data as a JavaGram object literal. 8.1.2.4 User Admin Panel This panel is displayed when the User Admin node is selected in the tree. It allows you to search for users in the database. To perform a search, enter the search criteria and press the Find button. To view the details of a user, select the user row in the table and press the Details button, or just double-click the row. The user is added to the tree, as a child of the User Admin node. To create a new user account, press the New button. To delete a user, select its row and press the Delete button. These two buttons are only enabled for the admin user. 8.1.2.5 User Details Panel When a user is selected in the tree (e.g., after a search, or after creating a new user), the content pane displays the user details as a panel. Quest Sample Application 263 For non-admin users, this panel is displayed as read-only. The admin user can use this panel to edit the details and/or access rights of a user, as well as changing the user’s password via the Change Password button. Users can also change their own password by choosing Change Password from the Tools menu. In both cases, the following dialog is displayed. Enter the old password, the new password, and confirm the new password. Press Change to finalize the change. When the admin user is changing another user’s password, the Old Password field is disabled and not required. 8.1.2.6 Issue Search Panel This panel is displayed when the Issue Search node is selected in the tree. It allows you to search for issues in the database. 264 JavaGram Agile Development To search for an issue by ID, select ‘By ID’ from the Search combo and enter the issue ID in the ID field. To search by other criteria, select ‘By Criteria’ from the Search combo and enter your search criteria into the fields provided. Any of these fields (or even all of them) can be left blank. A blank field will match any record. If all the fields are left blank then the search will return all issues in the database. For the Title field, you can enter the first few character of a title. All issues that begin with this prefix will match. The Module field follows the same rule. All searches are case insensitive. To perform a search, press the Find button. The matching issues are then displayed in the table. The issues are initially sorted in ascending ID order. To sort by another column, click on the column title. To sort in descending order, hold the shift key down while clicking on the column title. You can view the details of an issue by first selecting its row in the table and then pressing the Details button. A convenient shortcut for this is to double click the row in the table. The issue is then added as a node to the tree (as a child of the Issue Search node), and selected so that its content is displayed in the content pane (see the next section). To clear the table, press the Clear button. This will remove all the issues from the table (but will not remove them from the database). Any issues added to the tree are also removed. Quest Sample Application 265 You can use the Jump To field to quickly locate an issue by entering the first few characters of the sort field. This is useful when the table contains a large number of issues. 8.1.2.7 Issue Details Panel When an issue is selected in the tree (e.g., after a search, or after creating a new issue), the content pane displays the issue details as a panel, having four tab pages at the bottom of the panel. A set of buttons appear to the right of these tabs. These buttons allow you to perform actions on the issue. Depending on the issue state, only the permissible action buttons are enabled, as determined by the issue life cycle. The Issue Details panel displays the general details of the issue. Fields that are automatically controlled by Quest (such as ID and State) are disabled so that you can’t change them. All other fields can be altered regardless of the issue state. The Description tab allows you to enter a detailed description of the issue in free text format. Similarly, the Resolution tab allows you to enter a detailed account of how the issue has been fixed. The Attachments tab lists all the supporting documents that have been attached to the issue. To attach a document, press the Attach button, navigate to and select the desired file, and press Open. To view an attachment, select its row and press View, or simply double-click the row. To edit an attachment, select its row and press Edit – the 266 JavaGram Agile Development attachment will be locked for editing. Once you’ve made your changes, close the document, and press Save to save your changes, or press Unlock to discard your changes. To delete an attachment, select its row and press Delete. The History tab lists the actions performed on an issue, including the user who’s performed the action, when the action was performed, the old and new state, and an optional note. When you assign an issue, the following dialog is displayed. Select the user to whom the issue is to be assigned and press Assign. When you resolve an issue, the following dialog is displayed. Describe how the issue was resolved and press Resolve. Quest Sample Application 267 Similarly, when you reject an issue, the following dialog is displayed. Describe why the issue is being rejected and press Reject. 8.1.2.8 Generating Reports When viewing a user/issue search panel or a user/issue details panel, the Generate Report button in the toolbar (and the same in the Tools menu) is enabled. Pressing this button causes an HTML report to be generated and added to the tree as a child of the Reports node. Here is an example of a report generated for an issue. 268 JavaGram Agile Development 8.1.2.9 Log Panel When you select the Settings/Log node, the content pane displays the application log, where all application output is directed. If you instrument Quest by adding output statements, all such output will appear in this panel. 8.1.2.10 Pick Lists Panel When you select the Settings/Pick Lists node, the content pane displays all customizable pick lists for the application. Quest Sample Application 269 To view/edit the details of a pick list, select its row and press Details, or simply doubleclick the row. The following dialog appears. Make your changes using the buttons to right of the dialog and press OK to save your changes. Pick list changes are immediately reflected in the application’s combo boxes. 8.1.2.11 Help Panel When you select a help node (or report node in the tree), its content is displayed as a HTML page in the content pane. To open the HTML page in an external web browser, select the node and press the Print Preview in the toolbar, or choose the same from the File menu, or simply double-click the node. 8.2 Design 8.2.1 Object Model 8.2.1.1 quest/ The application scripts are located within the quest directory. The main class is Quest which subclasses GuiApp. The rest of the application appears within the quest/bom and quest/gui subdirectories. 270 JavaGram Agile Development 8.2.1.2 quest/gui/ This directory defines the main classes that comprise the application user interface. AppPanel implements the overall application window, which includes the navigation tree realized by AppTree. The user interface for all commands (toolbar buttons and menu items) is realized by the Commands class. Quest Sample Application 271 8.2.1.3 quest/bom/ This directory defines the BOs for the application. User, Issue, and Attachment are defined as UserLockableObject BOs. History is defined as an Object BO. For each BO, the attributes are captured in a separate class that the BO derives from. This enables the details of a BO to be passed to the BO constructor as one argument. Rules and DbTables are not persistent BOs. The former captures the definition of the issue life cycle. The latter sets up the database tables for all persistent BOs. 272 JavaGram Agile Development 8.2.1.4 quest/gui/user/ This directory defines the user interface for managing user accounts. It includes the UserSearch class for searching purposes, the UserScreen class for viewing and editing the details of a user, and the PasswordDialog class for changing user account passwords. Quest Sample Application 273 8.2.1.5 quest/gui/issue/ This directory defines the user interface for managing issues. It includes the IssueSearch class for searching issues, the IssueScreen class for viewing and editing the details of an issue, and a number of dialog classes that support the actions that can be performed on issues. 274 JavaGram Agile Development 8.2.1.6 quest/gui/misc/ This directory defines the HtmlPanel which is used to display the HTML content of reports and help pages. 8.2.1.7 quest/gui/setting/ This directory defines the PickListPanel class which displays the application pick lists, and the LogPanel class which displays the application log. Quest Sample Application 275 8.2.2 Data Model The Quest data model is very simple. It consists of four tables that provide persistence for BOs of the same name. Each table has an integer primary key called id which uniquely identifies rows within that table. The rest column of each table is used to capture class attributes that are not explicitly defined within the table. The tables for lockable objects also have a locked_by column, as required by the UserLockableObject class. 8.3 Implementation The code for Quest is presented below, divided into logical categories. In the interest of brevity and given the highly readable nature of the code, no detailed commentary is provided. 8.3.1 Business Objects When reading the BOs code, pay particular attention to methods intended for the server side (i.e., those defined as remote or slocal). The distinction conveys the intended distribution model of the application code. 8.3.1.1 User BO <jag domain="quest/bom"> <load> "lib/bom/UserLockableObject" 276 JavaGram Agile Development "lib/bom/Object" "lib/lang/Common" </load> class UserDetails { int id; // Internal unique numeric user ID getable string username; // External unique username getable string password; // Encrypted password getable string firstName; // User's real first name getable string lastName; // User's real last name getable string email; // User's email address getable boolean active; // When true, user is active getable boolean canUnlock; // When true, user can unlock objects getable boolean canDelete; // When true, user can delete objects getable string note; // Note about the user } class User extends UserDetails, UserLockableObject { static final int MIN_USERNAME_LEN = 4; static final int MIN_PASSWORD_LEN = 4; static final string ENCRYPTION_KEY = "quest"; static final string ADMIN_USER_NAME = "admin"; protected static vector<map> allUsersInfo; protected static vector<map> allActiveUsersInfo; static { defineTable(User, [ $db => $questDb , $table => $user , $columns => [ $id => $id , $user_name => $username , $first_name => $firstName , $last_name => $lastName , $active => $active , $can_unlock => $canUnlock , $can_delete => $canDelete , $rest => [$fields => [$password, $email, $note] , $compress => false] ] , $lock => [$column=>$locked_by, $style=>$byUser] ] ); } public User (UserDetails details) { sys.assign(this, details, UserDetails); validateUsername(); validatePassword(password); password = encryptPassword(password); } protected void validateUsername () { if (sys.length(username) < MIN_USERNAME_LEN) throw new Exception($"username must be at least {MIN_USERNAME_LEN} chars long!"); } protected slocal void validateUniqueUsername () { validateUsername(); int theId = usernameToId(username); if (theId >= 0 && id != theId) throw new Exception($"username '{username}' is not unique!"); Quest Sample Application 277 } public void validatePassword (string password) { if (sys.length(password) < MIN_PASSWORD_LEN) throw new Exception($"password must be at least {MIN_PASSWORD_LEN} chars long!"); } private remote static int usernameToId (string username) { query ($questDb) { native rs = _usernameToId(username); if (sql.next(rs)) return sql.get@int(rs, 1); } return -1; } private static string encryptPassword (string password) { return sys.cypher@string(password, ENCRYPTION_KEY); } public static User login (string username, string password, Callback cb = null) { User user @= Object.findOne(User, $username, username) ?> cb != null -> { if (!validate(user, password)) { Exception e = new Exception("invalid username or password"); if (cb == null) throw e; else cb.failed(e); } else cb?.completed(user); } -> cb?.failed(); return user; } private static boolean validate (User user, string password) { return user != null && encryptPassword(password) == user.password; } public void logout () { } public string title () { return username; } public boolean checkPassword (string password) { return this.password == encryptPassword(password); } public void changePassword (string password, Callback cb = null) { validatePassword(password); this.password = encryptPassword(password); persist() ?? cb != null -> cb.completed(null) -> cb.failed(); } public boolean isAdminUser () { return username == ADMIN_USER_NAME; } public vague getId () { // Override Object.getId() for efficiency: return id; } public static vector<User> find (map criteria, Callback cb = null) { map like = map(); 278 JavaGram Agile Development map exact = map(); if (nonEmpty(criteria.$username)) like[$username] = criteria.$username@string + "%"; if (nonEmpty(criteria.$firstName)) like[$firstName] = criteria.$firstName@string + "%"; if (nonEmpty(criteria.$lastName)) like[$lastName] = criteria.$lastName@string + "%"; if (nonEmpty(criteria.$active)) exact[$active] = criteria.$active == "Yes"; if (nonEmpty(criteria.$canUnlock)) exact[$canUnlock] = criteria.$canUnlock == "Yes"; if (nonEmpty(criteria.$canDelete)) exact[$canDelete] = criteria.$canDelete == "Yes"; criteria = map($exact => exact, $like => like); vector<User> matches @= Object.find(User, criteria) ?? cb != null -> cb.completed(matches) -> cb.failed(); return matches; } public static User findById (vague id, Callback cb = null) { User user @= Object.findOne(User, $id, id) ?? cb != null -> cb.completed(user == null ? vector() : vector(user)) -> cb.failed(); return user; } public remote void persist () { validateUniqueUsername(); super@Object.persist(); } public static vague userModel (boolean active, symbol cmd, int idx) { switch (cmd) { case $count: return sys.length(getAllUsersInfo(active)); case $get: return getAllUsersInfo(active)[idx][$name]; case $id: return getAllUsersInfo(active)[idx][$id]; } return null; } public static map getUserInfo (boolean active, int id) { for (map m in getAllUsersInfo(active)) { if (m[$id] == id) return m; } return [=>]; } public static void initAllUsersInfo () { getAllUsersInfo(false); getAllUsersInfo(true); } protected static vector<map> getAllUsersInfo (boolean active) { if (active) { if (allActiveUsersInfo == null) { allActiveUsersInfo @= []; // Avoid async re-entry allActiveUsersInfo = _getAllUsersInfo(active) ?? sys.async -> null; } Quest Sample Application 279 return allActiveUsersInfo; } if (allUsersInfo == null) { allUsersInfo @= []; // Avoid async re-entry allUsersInfo = _getAllUsersInfo(active) ?? sys.async -> null; } return allUsersInfo; } protected remote static vector<map> _getAllUsersInfo (boolean active) { // Always add an empty user to the list: vector<map> users @= vector(map($id=>0, $name=>"")); query ($questDb) { native rs = _allUsersInfo(active); while (sql.next(rs)) { map info = sql.getRow@map(rs); info[$name] = info[$firstName] == "" ? info[$lastName] : info[$firstName]@string + " " + info[$lastName]@string; sys.append(users, info); } } sys.sort(users, true, [$name]); return users; } public slocal static <text.sql.update int createTable () db=$questDb> create table if not exists user ( id integer not null auto_increment, user_name varchar(16) not null, first_name varchar(16), last_name varchar(16), active bit, can_unlock bit, can_delete bit, rest text, locked_by integer, primary key (id), index (user_name) ) </text.sql> protected slocal static <text.sql.query native _usernameToId (string username) db=$questDb> select id from user where user_name={`username} </text.sql> protected slocal static <text.sql.query native _allUsersInfo (boolean active) db=$questDb> select id, first_name, last_name from user {= active ? "where active=1" : ""} </text.sql> } </jag> 8.3.1.2 Issue BO <jag domain="quest/bom"> <load> "lib/lang/Common" "lib/bom/UserLockableObject" "lib/bom/Object" "quest/bom/User" "quest/bom/Attachment" "quest/bom/History" 280 JavaGram Agile Development </load> class IssueDetails { int id; setable string title; getable string project; getable string module; getable string build; getable string type; setable string state; getable string severity; getable string priority; getable int raisedBy; getable date raisedOn; setable int assignedTo; getable string description; getable string resolution; } // // // // // // // // // // // // // // Internal unique numeric issue ID Issue title Project to which it applies Module to which it applies Build to which it applies Issue type Issue state Issue severity Issue priority ID of the use who raised it Date on which it was raised ID of the user to whom it's assigned Issue description Issue resolution class Issue extends IssueDetails, UserLockableObject { getable string raisedByName; // Not persistent getable string assignedToName; // Not persistent protected setable vector<Attachment> attach; protected getable vector<History> history; static { defineTable(Issue, [ $db => $questDb , $table => $issue , $columns => [ $id => $id , $title => $title , $project => $project , $module => $module , $build => $build , $type => $type , $state => $state , $severity => $severity , $priority => $priority , $raised_by => $raisedBy , $assigned_to => $assignedTo , $rest => [$fields => [$raisedOn, $description, $resolution], $compress => false] ] , $lock => [$column=>$locked_by, $style=>$byUser] ] ); } public Issue (IssueDetails details) { sys.assign(this, details, IssueDetails); } public string title () { return sys.length(this.title) > 16 ? sys.subStr(this.title, 0, 16) + "..." : this.title; } public vague getId () { return id; } Quest Sample Application 281 public static vector<Issue> find (map criteria, Callback cb = null) { map like = map(); map exact = map(); if (nonEmpty(criteria.$title)) like[$title] = criteria.$title@string + "%"; if (nonEmpty(criteria.$module)) like[$module] = criteria.$module@string + "%"; if (nonEmpty(criteria.$build)) like[$build] = criteria.$build@string + "%"; if (nonEmpty(criteria.$project)) exact[$project] = criteria.$project@string; if (nonEmpty(criteria.$type)) exact[$type] = criteria.$type; if (nonEmpty(criteria.$state)) exact[$state] = criteria.$state; if (nonEmpty(criteria.$severity)) exact[$severity] = criteria.$severity; if (nonEmpty(criteria.$priority)) exact[$priority] = criteria.$priority; if (nonEmpty(criteria.$raisedBy)) exact[$raisedBy] = criteria.$raisedBy; criteria = map($exact => exact, $like => like); vector<Issue> matches @= Object.find(Issue, criteria) ?> cb != null -> { for (Issue issue in matches) issue.updateVolatiles(); cb?.completed(matches); } -> cb?.failed(); return matches; } public static Issue findById (vague id, Callback cb = null) { Issue issue @= Object.findOne(Issue, $id, id) ?> cb != null -> { issue.updateVolatiles(); cb?.completed(issue == null ? null : vector(issue)); } -> cb?.failed(); return issue; } public remote void persist () { transaction (getDbTag()) { for (History hist in history) { if (!hist.persisted()) bom.persist(hist); } bom.persist(this); } } protected remote void _delete () { transaction (getDbTag()) { sql.execUpdate(deleteAttach()); sql.execUpdate(deleteHistory()); bom.delete(this); } } 282 JavaGram Agile Development public Issue updateVolatiles () { raisedByName = raisedBy == null ? "" : User.getUserInfo(false, raisedBy)[$name]@string; assignedToName = assignedTo == null ? "" : User.getUserInfo(false, assignedTo)[$name]@string; return this; } public void populateAttach (Callback cb = null) { if (attach == null) { attach = Attachment.find(id, !sys.async ? null : new Callback(cb) { public void completed (vague data) { attach @= data; super.completed(data); } }); } } public void addHistory (History hist) { if (history == null) history @= vector(); sys.append(history, hist); } public void populateHistory (Callback cb = null) { if (history == null) { history = History.find(id, !sys.async ? null : new Callback(cb) { public void completed (vague data) { history @= data; super.completed(data); } }); } } public slocal static <text.sql.update int createTable () db=$questDb> create table if not exists issue ( id integer not null auto_increment, title varchar(64) not null, project varchar(32), module varchar(32), build varchar(16), type varchar(16), state varchar(16), severity varchar(16), priority varchar(16), raised_by integer, assigned_to integer, rest text, locked_by integer, primary key (id), index (assigned_to) ) </text.sql> protected slocal <text.sql.prepare native deleteAttach () db=$questDb> delete from attachment where issue_id={?id} </text.sql.prepare> protected slocal <text.sql.prepare native deleteHistory () db=$questDb> delete from history where issue_id={?id} </text.sql.prepare> Quest Sample Application 283 } </jag> 8.3.1.3 Attachment BO <jag domain="quest/bom"> <load> "lib/lang/Common" "lib/bom/Object" "lib/bom/UserLockableObject" "quest/bom/User" "quest/Quest" </load> class AttachmentDetails { int id; // Internal unique numeric attachment ID getable int issueId; // ID of the issue to which this attachment belongs getable string name; // Attachment name setable int size; // Attachment size (in bytes) getable date modified; // Last modification timestamp } class Attachment extends AttachmentDetails, UserLockableObject { setable string path; // Attachment path static final string TEMP_DIR = "temp/"; static { defineTable(Attachment, [ $db => $questDb , $table => $attachment , $columns => [ $id => $id , $issue_id => $issueId , $rest => [$fields => [$name, $size, $modified]] , $content => [$fields => $path, $viaFile => $binary, $extend => true] ] , $lock => [$column => $locked_by, $style => $byUser] ]); } public Attachment (AttachmentDetails details, string path) { sys.assign(this, details, AttachmentDetails); this.path = path; } public vague getId () { return id; } public static string recommendLocalDir (int issueId) { return sys.pathConc(sys.root, "attach", "issue" + issueId + "/"); } protected slocal string getViaFilePath (symbol dbColumn, symbol field, int rowNum) { string dir = sys.pathConc(sys.root, TEMP_DIR); if (!sys.pathExists(dir)) sys.createPath(dir); return sys.pathConc(dir, "Attachment_" + rowNum + ".zip"); } public static string genUniqueZipFileName (string name) { return sys.normPath(name + "_" + sys.date()@int + ".zip"); } public static string genUniqueTempFilePath () { return sys.pathConc(TEMP_DIR, sys.normPath("Temp_" + sys.date()@int + ".dat")); 284 JavaGram Agile Development } public static string getPartsName (map<symbol, string> parts) { return parts[$ext] == "" ? parts[$name] : (parts[$name] + "." + parts[$ext]); } public static vector<Attachment> find (int issueId, Callback cb = null) { vector<Attachment> matches @= Object.find(Attachment, $issueId, issueId) ?> cb != null -> { sys.sort(matches, true, [$name]); cb?.completed(matches); } -> cb?.failed(); return matches; } public static Attachment findById (vague id, Callback cb = null) { Attachment attach @= Object.findOne(Attachment, $id, id) ?? cb != null -> cb.completed(attach) -> cb.failed(); return attach; } public clocal void save (boolean existing, Callback cb = null) { if (path == null) throw new Exception("no file path specified for " + name); if (!sys.pathExists(path)) throw new Exception(path + " doesn't exist"); date timestamp @= sys.pathProps(path)[$modified]; if (existing && timestamp == modified) { // No need for async handling, as we're raising an exception immediately: unlock(Quest.singleton.getUserId()); throw new Exception(path + " hasn't been modified"); } map<symbol,string> parts = sys.pathParts(path); string zipRelPath = sys.pathConc(TEMP_DIR, genUniqueZipFileName(parts[$name])); string zipAbsPath = sys.pathConc(sys.root, zipRelPath); sys.createPath(sys.pathConc(sys.root, TEMP_DIR)); sys.zip(zipAbsPath, parts[$dir], getPartsName(parts)); if (sys.loader != null) { sys.upload(zipAbsPath, zipRelPath); sys.deletePath(zipAbsPath); } _save(zipRelPath, true, Quest.singleton.getUserId()@int) ?? cb != null -> cb.completed(null) -> cb.failed(); } public void saveUploaded (string uploadedPath, Callback cb = null) { _save(uploadedPath, false, Quest.singleton.getUserId()@int) ?? cb != null -> cb.completed(null) -> cb.failed(); } protected remote void _save (string docPath, boolean zipped, int byUserId) { path = sys.pathConc(sys.root, docPath); if (!zipped) { string zipPath = path + ".zip"; map<symbol,string> parts = sys.pathParts(path); Quest Sample Application 285 string fileName = getPartsName(parts); sys.zip(zipPath, parts[$dir], map(fileName => name), fileName); path = zipPath; } int lockedBy @= lockedBy(false); if (lockedBy != null && lockedBy != byUserId) throw new Exception("Can't update file; object is locked by " + User.getUserInfo(false, byUserId)[$name]@string); try { // save all fields, including file to 'content' marked 'extend': setExtendOn(true); persist(); } finally { setExtendOn(false); } sys.deletePath(path); path = null; } public string retrieve (string intoDir, Callback cb = null) { string remotePath = _retrieve() ?> cb != null -> { if (sys.flash) { sys.download(remotePath, name, null, false, $onDownload, list(cb)); } else { sys.createPath(intoDir); string localPath = remotePath; if (sys.loader != null) { localPath = sys.pathConc(intoDir, genUniqueZipFileName(name)); sys.download(remotePath, localPath, null, false); } sys.unzip(localPath, intoDir); try {sys.deletePath(localPath);} catch (Exception e) {} path = sys.pathConc(intoDir, name); Util.setModifiedTimestamp(path, modified@int); cb?.completed(path); } } -> cb?.failed(); return path; } protected void onDownload (vague res, Callback cb) { if (res instanceof map) { path @= res@map[$local]; cb.completed(path); } else cb.failed(res@Exception); } protected remote string _retrieve () { try { // get all fields, including file to 'content' column marked 'extend': setExtendOn(true); refresh(); } finally { 286 JavaGram Agile Development setExtendOn(false); } return path; } public slocal static <text.sql.update int createTable () db=$questDb> create table if not exists attachment ( id integer not null auto_increment, issue_id integer not null, content mediumblob, rest text, locked_by integer, primary key (id), index (issue_id) ) </text.sql> } </jag> 8.3.1.4 History BO <jag domain="quest/bom"> <load> "lib/lang/Common" "lib/bom/Object" "quest/bom/User" </load> class HistoryDetails { int id; // Internal unique numeric history ID int issueId; // ID of the issue to which this applies getable int actorId; // ID of the user who acted getable date actDate; // Action date getable symbol action; // The action getable string oldState; // Issue's old state getable string newState; // Issue's new state getable string note; // Optional note } class History extends HistoryDetails, Object { getable string actorName; // Not persistent static { defineTable(History, [ $db => $questDb , $table => $history , $columns => [ $id => $id , $issue_id => $issueId , $rest => [$fields => [$actorId, $actDate, $action, $oldState ,$newState, $note] ,$compress => false] ] ] ); } public History (HistoryDetails details) { sys.assign(this, details, HistoryDetails); } public vague getId () { return id; } Quest Sample Application 287 public string getFilteredNote () { return note == null ? "" : note; } public static vector<History> find (int issueId, Callback cb = null) { map exact = map($issueId => issueId); vector<History> matches @= Object.find(History, map($exact => exact)) ?> cb != null -> { afterFind(matches); cb?.completed(matches); } -> cb?.failed(); return matches; } private static void afterFind (vector<History> matches) { for (History hist in matches) hist.updateVolatiles(); sys.sort(matches, true, [$actDate]); } public History updateVolatiles () { actorName = actorId == null ? "" : User.getUserInfo(false, actorId)[$name]@string; return this; } public slocal static <text.sql.update int createTable () db=$questDb> create table if not exists history ( id integer not null auto_increment, issue_id integer not null, rest text, primary key (id), index (issue_id) ) </text.sql> } </jag> 8.3.1.5 Rule BO <jag domain="quest/bom"> // Defines the life-cycle of issues. singleton class Rules { static final map<string, vector<symbol>> STATE_TO_ACTIONS @= [ "New" => [$submit] , "Submitted" => [$assign, $open] , "Assigned" => [$open, $assign, $close] , "Opened" => [$resolve, $reject, $assign] , "Resolved" => [$close, $assign] , "Rejected" => [$close, $assign] , "Closed" => [] ]; final vector<string> STATES @= [""] + sys.mapKeys(STATE_TO_ACTIONS); public vector<symbol> stateToActions (string state) { return STATE_TO_ACTIONS[state]; } public string actionToState (symbol action) { switch (action) { case $submit: return "Submitted"; 288 JavaGram Agile Development case $assign: return "Assigned"; case $open: return "Opened"; case $resolve: return "Resolved"; case $reject: return "Rejected"; case $close: return "Closed"; } return null; } public final vector<string> getStates () { return STATES; } } </jag> 8.3.1.6 DbTables This script is not a BO, but creates the Quest database and its tables. <jag domain="quest/bom"> <load> "lib/bom/Object" "lib/bom/PickList" "quest/bom/User" "quest/bom/Issue" "quest/bom/History" "quest/bom/Attachment" </load> // This class drops, creates, and populates the tables for Quest. class DbTables { public static void main () { createDatabase(); transaction ($questDb) { Object.dropTable(User, $questDb); User.createTable(); } UserDetails ud = [@UserDetails username=>"admin", password=>"admin" , firstName=>"Admin", lastName=>"Administrator" , active=>true, canUnlock=>true, canDelete=>true]; new User(ud).persist(); ud = [@UserDetails username=>"fred", password=>"fred", firstName=>"Fred" ,lastName=>"Smith", active=>true, canUnlock=>true, canDelete=>false]; new User(ud).persist(); ud = [@UserDetails username=>"jane", password=>"jane", firstName=>"Jane" ,lastName=>"Smith", active=>true, canUnlock=>true, canDelete=>false]; new User(ud).persist(); transaction ($questDb) { Object.dropTable(Issue, $questDb); Issue.createTable(); } transaction ($questDb) { Object.dropTable(History, $questDb); History.createTable(); } transaction ($questDb) { Object.dropTable(Attachment, $questDb); Attachment.createTable(); Quest Sample Application 289 } transaction ($questDb) { Object.dropTable(PickList, $questDb); PickList.createTable(); } PickListDetails pl; pl = [@PickListDetails name=>"Type", description=>"Issue types", editable=>true , items=>["", "Defect", "Enhancement", "Change Request"]]; new PickList(pl).persist(); pl = [@PickListDetails name=>"Severity", description=>"Issue severity" , editable=>true, items=>["", "Critical", "High", "Medium", "Low"]]; new PickList(pl).persist(); pl = [@PickListDetails name=>"Priority", description=>"Issue priority" , editable=>true, items=>["", "High", "Medium", "Low"]]; new PickList(pl).persist(); pl = [@PickListDetails name=>"Project", description=>"List of all projects" , editable=>true, items=>["", "Default"]]; new PickList(pl).persist(); } protected static void createDatabase () { transaction ($mysqlDb) { sql.execUpdate($mysqlDb, "create database if not exists quest"); } } } </jag> 8.3.2 Main Window A number of the user interface screens use the Hot plate to define their fields. This is defined in the Screen library class. Its main purpose is to ensure that when the user edits such a field, the screen is informed of the fact that it has been modified. 8.3.2.1 Quest This class initially sets up the application main frame as a login window. As soon as the user logs in successfully, the frame is redefined by the loginWorder() method, which removes the login panel and instead adds the panel provided by the AppPanel class. Note how we do this dynamically using the Util.getSingleton() method, thus ensuring that the application panel is neither downloaded nor created until necessary. <jag domain="quest"> // // // // // // // // // // // Quest Sample JavaGram Application Copyright (c) 2010 PragSoft Corporation. All rights reserved. This application is provided for educational purposes. Use it as a 'best practice' guide for JavaGram programming. Quest also illustrates how the same JavaGram code can run under different deployment models: 1. As a standalone desktop application. 2. As a synchronous desktop client running against a synchronous JAG app server, directly or through a JAG proxy server. 3. As an asynchronous desktop client running against an asynchronous JAG app server, directly or through a JAG proxy server. 4. As an asynchrnous browser-based client running against an asynchronous JAG app server, 290 JavaGram Agile Development // directly or through a web server. <load> "lib/lang/Common" "lib/gui/GuiApp" "lib/gui/Upgrade" "quest/bom/User" "quest/gui/setting/LogPanel" </load> singleton class Quest extends GuiApp { protected User currUser; protected symbol loggedIn = $no; public static final string REPORTS_DIR = sys.pathConc(sys.root, "quest/reports/"); public static final <Icon HAMMER image={sys.use("lib/gifs/Hammer.gif")}/> <Worker login worker=loginWorker/> <App app lookAndFeel=$windows stdOut={LogPanel.singleton.getTextArea()} stdErr={LogPanel.singleton.getTextArea()}> <Frame frame sizeable=false title="Quest Login" width=250 height=140 image={sys.use("lib/gifs/Planet.gif")} event=frameHandler> <Panel panel> <Layout.border/> <Panel lay=$center> <Layout.gridBag/> <Lay row=0 col=0 margin=2 weight=0.0 align=$east> <Label title="Username"/> </Lay> <Lay row=0 col=1 margin=2 fill=$horizontal> <Field.text username focus=true event=fieldHandler value={sys.stage == $prod ? "" : "admin"}/> </Lay> <Lay row=1 col=0 margin=2 weight=0.0 align=$east> <Label title="Password"/> </Lay> <Lay row=1 col=1 margin=2 fill=$horizontal> <Field.password password event=fieldHandler value={sys.stage == $prod ? "" : "admin"}/> </Lay> <Lay row=2 col=0 margin=2 colSpan=2 fill=$horizontal> <ProgressBar progress/> </Lay> </Panel> <Panel lay=$south> <Button loginButn title="Login" image={sys.use("lib/gifs/Apply.gif")} action={onLogin()} enable={enableLogin()}/> <Button cancelButn title="Cancel" image={sys.use("lib/gifs/Cancel.gif")} action={Quest.singleton.exit()}/> </Panel> </Panel> </Frame> </App> public Quest () { super(frame); progress.title = sys.host == null ? "Standalone" : sys.host; frame.enter = loginButn; if (!sys.flash) checkJag(); Quest Sample Application 291 } protected void frameHandler (native comp, symbol event) { if (event == $close) exit(); } private void fieldHandler (native comp, symbol event) { gui.maintain(frame); } private boolean enableLogin () { return loggedIn == $no && username.value != "" && password.value != ""; } private void onLogin () { login.run = true; } protected vague loginWorker (symbol cmd, vague val) { switch (cmd) { case $do: setLoggedIn($pending); progress.indefinite = true; try { User user = User.login(username.value@string, password.value@string, !sys.async ? null : new Callback() { public void completed (vague user) { setCurrUser(user@User); onLoginOK(); } public void failed (Exception e) { onLoginFail(e); } }); if (!sys.async) { setCurrUser(user); setLoggedIn($yes); return getCurrUser(); } } catch (Exception e) { onLoginFail(e); } break; case $done: if (!sys.async && loggedIn == $yes) onLoginOK(); break; } return null; } public void onLoginOK () { setLoggedIn($yes); // Replace the login panel with the application panel: frame.visible = false; frame -= panel; sys.call(Util.getSingleton("quest/gui/AppPanel"), $addToFrame, frame); } public void onLoginFail (Exception e) { setLoggedIn($no); 292 JavaGram Agile Development progress.indefinite = false; gui.alert("Login Failed", e.getMessage(), frame, $warning); } protected void setLoggedIn (symbol status) { loggedIn = status; gui.maintain(loginButn); } protected void checkJag () { Upgrade upgrade = new Upgrade() { protected string getRebootCmd (string jarPath) { return $"javaw.exe -cp \"{jarPath}\" jag.gui.Gui -host \"{sys.host}\" stage {sys.stage} -root \"{sys.root}\" -ssl \"{sys.ssl}\" -boot quest/Quest"; } }; // Client-side must have at least JAG1.1: upgrade.check(1, 1, "jar/JAG.jar", "jar/JAG.jar", sys.loader); } public User getCurrUser () { return currUser; } public void setCurrUser (User user) { currUser = user; } public vague getUserId () { return currUser.getId(); } public void setCursor (symbol cursor) { frame.cursor = cursor; } public void setStdErr (native textArea) { app.stdErr = textArea; } public static void main () { Quest.singleton.run(); } } </jag> 8.3.2.2 AppPanel <jag domain="quest/gui"> <load> "lib/lang/Event" "lib/gui/NavTree" "lib/gui/InfoBar" "lib/gui/Screen" "quest/bom/User" "quest/Quest" "quest/gui/Commands" "quest/gui/AppTree" </load> singleton class AppPanel extends EventListener { protected Quest app = Quest.singleton; protected AppTree tree = AppTree.singleton; protected Commands commands = Commands.singleton; protected Screen contentScreen; protected boolean treeVisible = true; Quest Sample Application 293 protected int lastSplitDivider; <Panel noContent> <Layout.border/> <Label title="No Content" align=$center fgColor="0xFF0000" lay=$center/> </Panel> <Null noTree/> <Panel mainPanel> <Layout.border/> <Panel lay=$north> <Layout.flow align=$west/> <Indirect ref={commands.getToolBar()}/> </Panel> <Pane.split mainSplit lay=$center divider=200 weight=0.3> <Pane.scroll treeScroll lay=$west> <Indirect ref={tree.getTree()}/> </Pane.scroll> <Indirect ref={noContent} lay=$east/> </Pane.split> <Panel lay=$south> <Layout.border/> <Indirect ref={InfoBar.singleton.activate()}/> </Panel> </Panel> public AppPanel () { User.initAllUsersInfo(); tree.addListener(ShowContentEvent, this); tree.addListener(ShowNoContentEvent, this); } public void addToFrame (native frame) { string title = $"Quest ({app.getCurrUser().getUsername()}) -- "; title += sys.host == null ? "Standalone" : $"Server: {sys.host}"; frame@<Frame>.title = title; frame@<Frame>.width = 800; frame@<Frame>.height = 500; frame@<Frame>.sizeable = true; frame += commands.getMenuBar(); frame += mainPanel; app.setStdErr(InfoBar.singleton.getErrLog()); if (sys.flash) frame@<Frame>.state = $max; // Maximize the frame so that it fills the browser window frame@<Frame>.visible = true; trackTasks(true); tree.reload(); tree.selectNode($helpRootNode); gui.maintain(frame); frame@<Frame>.refresh = true; } public void toggleTree () { if (treeVisible) { lastSplitDivider = getMainSplitDivider(); mainSplit.west = noTree; } else { mainSplit.west = treeScroll; setMainSplitDivider(lastSplitDivider); 294 JavaGram Agile Development } treeVisible = !treeVisible; } public boolean consumeEvent (Event event) { if (event instanceof ShowContentEvent) { contentScreen = (event@ShowContentEvent).screen; mainSplit.east = contentScreen.getView(); } else if (event instanceof ShowNoContentEvent) { mainSplit.east = noContent; contentScreen = null; } return false; } public int getMainSplitDivider () { return mainSplit.divider@int; } public void setMainSplitDivider (int div) { mainSplit.divider = div; } public Screen getContentScreen () { return contentScreen; } public void onDownloadTask (vague file) { app.beginLengthy(); InfoBar.singleton.startTask("Downloading: " + file@string); } public void onUploadTask (vague file) { app.beginLengthy(); InfoBar.singleton.startTask("Uploading: " + file@string); } public void onLoadTask (vague script) { app.beginLengthy(); InfoBar.singleton.startTask("Loading: " + script@string); } public void onRemoteTask (vague method) { app.beginLengthy(); InfoBar.singleton.startTask("Remote call: " + method@string); } public void onReadyTask (vague ignore) { app.endLengthy(); InfoBar.singleton.endTask(); } public void trackTasks (boolean track) { sys.hook($download, track ? AppPanel : null, $onDownloadTask); sys.hook($upload, track ? AppPanel : null, $onUploadTask); sys.hook($load, track ? AppPanel : null, $onLoadTask); sys.hook($remote, track ? AppPanel : null, $onRemoteTask); sys.hook($ready, track ? AppPanel : null, $onReadyTask); } } </jag> 8.3.2.3 AppTree <jag domain="quest/gui"> <load> "lib/lang/Common" Quest Sample Application 295 "lib/bom/Object" "lib/bom/UserLockableObject" "lib/bom/Document" "lib/bom/PickList" "lib/gui/NavTree" "lib/gui/Screen" "lib/gui/Html" "quest/Quest" "quest/bom/User" "quest/bom/Issue" "quest/gui/AppPanel" </load> singleton class AppTree extends NavTree { int lastSplitDiv; // Last divider position of main splitter Document helpRoot = Document.getByPath("quest/doc/help/Root.html"); Document helpContents = Document.getByPath("quest/doc/help/Contents.html"); Document helpReports = Document.getByPath("quest/doc/help/Reports.html"); Document helpSettings = Document.getByPath("quest/doc/help/Settings.html"); <Node userAdminNode image={sys.use("lib/gifs/FindUser.gif")} title="User Admin" presentation="quest/gui/user/UserSearch"/> <Node issueSearchNode image={sys.use("lib/gifs/FindFolder.gif")} title="Issue Search" presentation="quest/gui/issue/IssueSearch"/> <Node reportsNode image={sys.use("lib/gifs/ReportFolder.gif")} title="Reports" presentation="quest/gui/misc/HtmlPanel" content={helpReports}/> <Node settingsNode image={sys.use("lib/gifs/OptionsFolder.gif")} title="Settings" presentation="quest/gui/misc/HtmlPanel" content={helpSettings}> <Node logNode image={sys.use("lib/gifs/Report.gif")} title="Log" presentation="quest/gui/setting/LogPanel"/> <Node pickListNode image={sys.use("lib/gifs/PickList.gif")} title="Pick Lists" presentation="quest/gui/setting/PickListPanel"/> </Node> <Node helpRootNode image={sys.use("lib/gifs/Help.gif")} title="Help" presentation="quest/gui/misc/HtmlPanel" content={helpRoot}> <Node helpContentsNode image={sys.use("lib/gifs/Help.gif")} title="Contents" presentation="quest/gui/misc/HtmlPanel" content={helpContents}/> </Node> public AppTree () { super(Quest.singleton); tree += userAdminNode; tree += issueSearchNode; tree += reportsNode; tree += settingsNode; tree += helpRootNode; pickListNode.content = new PickLists(!sys.async ? null : new Callback() { public void completed (vague data) { pickListNode.content = data; } }); } public boolean selectLogNodeCmd (boolean maintain) { if (!maintain) selectNode(logNode); return true; } public void showUser (Object user) { 296 JavaGram Agile Development showObject(userAdminNode, user, "lib/gifs/User.gif", "quest/gui/user/UserScreen"); } public void hideUser (vague user) { hideChildNode(userAdminNode, user, false); } public void showIssue (Object issue) { showObject(issueSearchNode, issue, "lib/gifs/Issue.gif", "quest/gui/issue/IssueScreen"); } public void hideIssue (vague issue) { hideChildNode(issueSearchNode, issue, false); } public void showHelpPage (Object html) { showObject(helpRootNode, html, "lib/gifs/Help.gif", "quest/gui/misc/HtmlPanel"); } protected boolean removableNode (native node) { vague content = getContent(node); return content instanceof User || content instanceof Issue; } protected boolean canUnlockNode (native node) { return getContent(node) instanceof UserLockableObject && Quest.singleton.getCurrUser().getCanUnlock(); } protected boolean canDeleteNode (native node) { return removableNode(node) && Quest.singleton.getCurrUser().getCanDelete(); } protected void onShowContent (symbol stage) { if (stage == $before) lastSplitDiv = AppPanel.singleton.getMainSplitDivider(); else AppPanel.singleton.setMainSplitDivider(lastSplitDiv); } protected void showObject (native parentNode, Object obj, string gifPath, string presentation) { vague node = findChildNode(parentNode, obj); if (node == null) { node = gui.create($Node, map( $image => sys.use(gifPath), $title => obj.title(), $presentation => presentation, $content => obj )); parentNode += node; parentNode@<Node>.reload = true; } selectNode(node); } public string getSelectedNodeTitle () { native node = tree.select; return node == null ? "Untitled" : node@<Node>.title; } public boolean printPreviewCmd (boolean maintain) { Object content = getContent(lastSelNode); if (content == null || maintain) return content instanceof Document; content@Document.view(); return true; } Quest Sample Application 297 public boolean generateReportCmd (boolean maintain) { Screen screen = getScreen(lastSelNode); if (screen == null || maintain) return !sys.flash && screen instanceof HtmlGenerator; string path = sys.pathConc(Quest.REPORTS_DIR, AppTree.singleton.getSelectedNodeTitle() + ".html"); HtmlGenerator.generateAuto(path, screen, lastSelNode@<Node>.title); Document report = new Document(path, true); showObject(reportsNode, report, "lib/gifs/Report.gif", "quest/gui/misc/HtmlPanel"); return true; } public boolean showNodeContentCmd (boolean maintain, symbol syntax) { boolean res = super.showNodeContentCmd(maintain, syntax); if (!maintain) selectLogNodeCmd(false); return res; } } </jag> 8.3.2.4 Commands <jag domain="quest/gui"> <load> "lib/gui/GuiUtil" "lib/gui/PickListMgr" "quest/Quest" "quest/gui/AppTree" "quest/gui/AppPanel" "quest/gui/user/PasswordDialog" "quest/gui/misc/AboutDialog" </load> singleton class Commands { AppTree tree = AppTree.singleton; <MenuBar menuBar> <Menu title="File"> <MenuItem title="Save" image={sys.use("lib/gifs/Save.gif")} enable={tree.persistNodeCmd(true)} action={tree.persistNodeCmd(false)} accelerator="control S"/> <MenuItem title="Save All" image={sys.use("lib/gifs/SaveAll.gif")} enable={tree.persistAllNodesCmd(true)} action={tree.persistAllNodesCmd(false)}/> <Separator/> <MenuItem title="Print Preview" image={sys.use("lib/gifs/DrillDown.gif")} enable={filePrintPreviewCmd(true)} action={filePrintPreviewCmd(false)} accelerator="control P"/> <Separator/> <MenuItem title="Exit" image = {GuiUtil.blankIcon} action={fileExitCmd()}/> </Menu> <Menu title="Edit"> <MenuItem title="Hide" image={sys.use("lib/gifs/HideNode.gif")} enable={tree.hideNodeCmd(true)} action={tree.hideNodeCmd(false)}/> <MenuItem title="Unlock" image={sys.use("lib/gifs/Unlock.gif")} enable={tree.unlockNodeCmd(true)} action={tree.unlockNodeCmd(false)}/> <Separator/> <MenuItem title="Delete" image={sys.use("lib/gifs/DeleteNode.gif")} 298 JavaGram Agile Development enable={tree.deleteNodeCmd(true)} action={tree.deleteNodeCmd(false)}/> </Menu> <Menu title="View"> <MenuItem title="Previous Tree Node" image={sys.use("lib/gifs/Back.gif")} enable={tree.goBackCmd(true)} action={tree.goBackCmd(false)}/> <MenuItem title="Next Tree Node" image={sys.use("lib/gifs/Forward.gif")} enable={tree.goForwardCmd(true)} action={tree.goForwardCmd(false)}/> <MenuItem title="Reload Tree" image={sys.use("lib/gifs/Refresh.gif")} action={tree.reload()}/> <Separator/> <MenuItem title="Show Log" image={sys.use("lib/gifs/Report.gif")} enable={tree.selectLogNodeCmd(true)} action={tree.selectLogNodeCmd(false)}/> </Menu> <Menu title="Tools"> <MenuItem title="Change Password..." image={sys.use("lib/gifs/Rename.gif")} action={toolsChangePasswordCmd()}/> <Separator/> <MenuItem title="Generate Report..." image={sys.use("lib/gifs/GenReport.gif")} enable={toolsGenReportCmd(true)} action={toolsGenReportCmd(false)}/> <Separator/> <MenuItem title="Refresh Pick Lists" image={sys.use("lib/gifs/RefreshCombo.gif")} enable={toolsRefreshPickListsCmd(true)} action={toolsRefreshPickListsCmd(false)}/> </Menu> <Menu title="Help"> <MenuItem title="User Guide..." image={sys.use("lib/gifs/Help.gif")} action={helpUserGuideCmd()}/> <Separator/> <MenuItem title="About Quest..." image={sys.use("lib/gifs/Question.gif")} action={helpAboutCmd()}/> </Menu> </MenuBar> <ToolBar toolBar floatable=true border=$empty> <Button tooltip="Save Node" image={sys.use("lib/gifs/Save.gif")} enable={tree.persistNodeCmd(true)} action={tree.persistNodeCmd(false)}/> <Button tooltip="Save All Nodes" image={sys.use("lib/gifs/SaveAll.gif")} enable={tree.persistAllNodesCmd(true)} action={tree.persistAllNodesCmd(false)}/> <Separator/> <Button tooltip="Print Preview" image={sys.use("lib/gifs/DrillDown.gif")} enable={filePrintPreviewCmd(true)} action={filePrintPreviewCmd(false)}/> <Separator/> <Button tooltip="Hide Tree Node" image={sys.use("lib/gifs/HideNode.gif")} enable={tree.hideNodeCmd(true)} action={tree.hideNodeCmd(false)}/> <Button tooltip="Unlock Tree Node" image={sys.use("lib/gifs/Unlock.gif")} enable={tree.unlockNodeCmd(true)} action={tree.unlockNodeCmd(false)}/> <Button tooltip="Delete Tree Node" image={sys.use("lib/gifs/DeleteNode.gif")} enable={tree.deleteNodeCmd(true)} action={tree.deleteNodeCmd(false)}/> <Separator/> <Button.toggle tooltip="Toggle Navigation Tree" image={sys.use("lib/gifs/ShowTree.gif")} select=true action={viewShowHideTreeCmd()}/> <Separator/> <Button tooltip="Previous Tree Node" image={sys.use("lib/gifs/Back.gif")} enable={tree.goBackCmd(true)} action={tree.goBackCmd(false)}/> <Button tooltip="Next Tree Node" image={sys.use("lib/gifs/Forward.gif")} enable={tree.goForwardCmd(true)} action={tree.goForwardCmd(false)}/> <Button tooltip="Reload Tree Node" image={sys.use("lib/gifs/Refresh.gif")} Quest Sample Application 299 action={tree.reload()}/> <Separator/> <Button tooltip="Generate Report" image={sys.use("lib/gifs/GenReport.gif")} enable={toolsGenReportCmd(true)} action={toolsGenReportCmd(false)}/> <Separator/> <Button tooltip="Show Node XML Data" image={sys.use("lib/gifs/ViewXml.gif")} enable={toolsShowXmlCmd(true)} action={toolsShowXmlCmd(false)}/> <Button tooltip="Show Node JAG Data" image={sys.use("lib/gifs/ViewJAG.gif")} enable={toolsShowJagCmd(true)} action={toolsShowJagCmd(false)}/> </ToolBar> public native getMenuBar () { return menuBar; } public native getToolBar () { return toolBar; } protected boolean filePrintPreviewCmd (boolean maintain) { return AppTree.singleton.printPreviewCmd(maintain); } protected void fileExitCmd () { if (tree.canExit()) Quest.singleton.exit(); } protected void viewShowHideTreeCmd () { AppPanel.singleton.toggleTree(); } protected void toolsChangePasswordCmd () { PasswordDialog.singleton.show(Quest.singleton.getCurrUser()); } protected boolean toolsGenReportCmd (boolean maintain) { return AppTree.singleton.generateReportCmd(maintain); } protected boolean toolsShowXmlCmd (boolean maintain) { return AppTree.singleton.showNodeContentCmd(maintain, $xml); } protected boolean toolsShowJagCmd (boolean maintain) { return AppTree.singleton.showNodeContentCmd(maintain, $jag); } protected boolean toolsRefreshPickListsCmd (boolean maintain) { if (!maintain) PickListMgr.singleton.refreshLists(); return true; } protected void helpUserGuideCmd () { AppTree.singleton.selectNode($helpRootNode); } protected void helpAboutCmd () { AboutDialog.singleton.show(); } } </jag> 300 JavaGram Agile Development 8.3.3 User Admin 8.3.3.1 UserSearch <jag domain="quest/gui/user"> <load> "lib/lang/Common" "lib/bom/Object" "lib/gui/Table" "lib/gui/Search" "lib/gui/Html" "quest/Quest" "quest/bom/User" "quest/gui/AppTree" </load> singleton class UserSearch extends SearchScreen, HtmlGenerator { static final vector<map> TABLE_FORMAT = [ [$key=>$id, $title=>"ID", $width=>10, $align=>$east] ,[$key=>$username, $title=>"Username", $width=>100, $align=>$west] ,[$key=>$lastName, $title=>"Last Name", $width=>100, $align=>$west] ,[$key=>$firstName, $title=>"First Name", $width=>100, $align=>$west] ,[$key=>$active, $title=>"Active", $width=>20, $align=>$center] ,[$key=>$canUnlock, $title=>"Can Unlock", $width=>30, $align=>$center] ,[$key=>$canDelete, $title=>"Can Delete", $width=>30, $align=>$center] ]; static final vector<symbol> PIE_KEYS = [$active, $canUnlock, $canDelete]; <Panel criteria> <Layout.gridBag/> <Lay row=0 col=0 margin=2 weight=0.0 align=$east> <Label title="Search"/> </Lay> <Lay row=0 col=1 margin=2 fill=$horizontal> <Hot:Combo byCombo key=$searchBy data=["By Criteria", "By ID"]/> </Lay> <Lay row=0 col=2 margin=2 weight=0.0 align=$east> <Label title="ID"/> </Lay> <Lay row=0 col=3 margin=2 fill=$horizontal> <Field.text key=$id enable={isIdSearch()}/> </Lay> <Lay row=2 col=0 margin=2 weight=0.0 align=$east> <Label title="Username"/> </Lay> <Lay row=2 col=1 margin=2 fill=$horizontal> <Hot:Field.text key=$username enable={isCriteriaSearch()}/> </Lay> <Lay row=2 col=2 margin=2 weight=0.0 align=$east> <Label title="Last Name"/> </Lay> <Lay row=2 col=3 margin=2 fill=$horizontal> <Hot:Field.text key=$lastName enable={isCriteriaSearch()}/> </Lay> <Lay row=2 col=4 margin=2 weight=0.0 align=$east> <Label title="First Name"/> </Lay> <Lay row=2 col=5 margin=2 fill=$horizontal> Quest Sample Application 301 <Hot:Field.text key=$firstName enable={isCriteriaSearch()}/> </Lay> <Lay row=3 col=0 margin=2 weight=0.0 align=$east> <Label title="Active"/> </Lay> <Lay row=3 col=1 margin=2 fill=$horizontal> <Hot:Combo key=$active enable={isCriteriaSearch()} data={TICK_DATA}/> </Lay> <Lay row=3 col=2 margin=2 weight=0.0 align=$east> <Label title="Can Lock"/> </Lay> <Lay row=3 col=3 margin=2 fill=$horizontal> <Hot:Combo key=$canUnlock enable={isCriteriaSearch()} data={TICK_DATA}/> </Lay> <Lay row=3 col=4 margin=2 weight=0.0 align=$east> <Label title="Can Delete"/> </Lay> <Lay row=3 col=5 margin=2 fill=$horizontal> <Hot:Combo key=$canDelete enable={isCriteriaSearch()} data={TICK_DATA}/> </Lay> </Panel> public UserSearch () { super@SearchScreen( new SearchCriteriaPanel(this, criteria), new TableAndPieSearchResultScreen(this, TABLE_FORMAT, new TableButtons(TableButtons.ALL_BUTNS), PIE_KEYS) { protected Object drillRow (Object bo) { AppTree.singleton.showUser(bo); return bo; } protected vague createRow (Callback cb = null) { User user = new User([@UserDetails username=>"NewUser", password=>"password", active=>true]); cb?.completed(user); return user; } protected vague deleteRow (int idx, Callback cb = null) { vague row = super@Table.deleteRow(idx, !sys.async ? null : new Callback(cb) { public void completed (vague data) { AppTree.singleton.hideUser(rowToObject(data)); } }); if (!sys.async) AppTree.singleton.hideUser(rowToObject(row)); return row; } } ); getResultScreen().setEditable(Quest.singleton.getCurrUser().isAdminUser()); } protected boolean isIdSearch () { return byCombo.value == "By ID"; } protected boolean isCriteriaSearch () { return byCombo.value == "By Criteria"; } 302 JavaGram Agile Development protected vector find (map criteria, Callback cb = null) { if (criteria.$searchBy == "By ID") { User user = User.findById(criteria.$id, cb); return user == null ? vector() : vector(user); } else { return User.find(criteria, cb); } } public void clearCmd () { super@SearchScreen.clearCmd(); AppTree.singleton.hideUser($all); } } </jag> 8.3.3.2 UserScreen <jag domain="quest/gui/user"> <load> "lib/gui/Screen" "lib/gui/Html" "quest/bom/User" "quest/gui/user/PasswordDialog" "quest/Quest" </load> singleton class UserScreen extends Screen, HtmlGenerator { <Panel view enable={Quest.singleton.getCurrUser().isAdminUser()}> <Layout.border/> <Panel lay=$north title="User Details" enable=true> <Layout.gridBag/> <Lay row=0 col=0 margin=2 weight=0.0 align=$east> <Label title="ID"/> </Lay> <Lay row=0 col=1 margin=2 fill=$horizontal> <Field.number key=$id readOnly=true precision=0/> </Lay> <Lay row=0 col=5 margin=2 weight=0.0 align=$east> <Button title="Change Password" image={sys.use("lib/gifs/Hammer.gif")} enable={canChangePassword()} action={changePassword()}/> </Lay> <Lay <Lay <Lay <Lay <Lay <Lay row=1 row=1 row=1 row=1 row=1 row=1 col=0 col=1 col=2 col=3 col=4 col=5 margin=2 margin=2 margin=2 margin=2 margin=2 margin=2 weight=0.0 align=$east><Label title="Username"/></Lay> fill=$horizontal><Hot:Field.text key=$username/></Lay> weight=0.0 align=$east><Label title="Last Name"/></Lay> fill=$horizontal><Hot:Field.text key=$lastName/></Lay> weight=0.0 align=$east><Label title="First Name"/></Lay> fill=$horizontal><Hot:Field.text key=$firstName/></Lay> <Lay row=2 col=1 align=$west><Hot:Option.tick key=$active title="Active"/></Lay> <Lay row=2 col=3 align=$west><Hot:Option.tick key=$canUnlock title="Can Unlock"/></Lay> <Lay row=2 col=5 align=$west><Hot:Option.tick key=$canDelete title="Can Delete"/></Lay> <Lay row=3 col=0 margin=2 weight=0.0 align=$east><Label title="Email"/></Lay> <Lay row=3 col=1 margin=2 colSpan=5 fill=$horizontal><Hot:Field.text key=$email/></Lay> </Panel> <Panel lay=$center title="Note"> <Layout.border/> Quest Sample Application 303 <Pane.scroll lay=$center> <Hot:Area.text key=$note/> </Pane.scroll> </Panel> </Panel> public UserScreen () { setEditable(Quest.singleton.getCurrUser().isAdminUser()); } protected boolean canChangePassword () { return Quest.singleton.getCurrUser().isAdminUser(); } protected void changePassword () { PasswordDialog.singleton.show(getContent()@User); } } </jag> 8.3.3.3 PasswordDialog <jag domain="quest/gui/user"> <load> "lib/lang/Common" "lib/gui/GuiUtil" "quest/Quest" "quest/bom/User" </load> singleton class PasswordDialog { User user; <Dialog dialog parent={GuiUtil.getMainFrame()} center={GuiUtil.getMainFrame()} fixed=true modal=true title="Change Password" width=250 height=140> <Layout.border/> <Panel lay=$center> <Layout.gridBag/> <Lay row=0 col=0 margin=2 weight=0.0 align=$east> <Label title="Old Password"/> </Lay> <Lay row=0 col=1 margin=2 fill=$horizontal> <Field.password oldPasswd enable={oldPassRequired()} event=fieldHandler/> </Lay> <Lay row=1 col=0 margin=2 weight=0.0 align=$east> <Label title="New Password"/> </Lay> <Lay row=1 col=1 margin=2 fill=$horizontal> <Field.password newPasswd event=fieldHandler/> </Lay> <Lay row=2 col=0 margin=2 weight=0.0 align=$east> <Label title="Confirm Password"/> </Lay> <Lay row=2 col=1 margin=2 fill=$horizontal> <Field.password confirmPasswd event=fieldHandler/> </Lay> </Panel> <Panel lay=$south> <Button changeButn title="Change" image={sys.use("lib/gifs/Apply.gif")} 304 JavaGram Agile Development action={onChange()} enable={enableChange()}/> <Button cancelButn title="Cancel" image={sys.use("lib/gifs/Cancel.gif")} action={onCancel()}/> </Panel> </Dialog> public void show (User user) { this.user = user; oldPasswd.value = ""; newPasswd.value = ""; confirmPasswd.value = ""; gui.maintain(dialog); dialog.enter = changeButn; dialog.show = true; } protected void fieldHandler (native comp, symbol event) { gui.maintain(dialog); } protected void onChange () { string oldPass @= oldPasswd.value; string newPass @= newPasswd.value; string confirmPass @= confirmPasswd.value; if (newPass != confirmPass) { gui.alert("Failed", "New Password doesn't match Cofirm Password!", dialog, $warning); return; } if (oldPassRequired()) { if (!user.checkPassword(oldPass)) { gui.alert("Failed", "Old Password is invalid!", dialog, $warning); return; } if (oldPass == newPass) { gui.alert("Failed", "New Password is the same as Old Password!", dialog, $warning); return; } } try { user.changePassword(newPass, !sys.async ? null : new Callback() { public void completed (vague data) { dialog.show = false; } public void failed (Exception e) { passwordFailed(e); } }); } catch (Exception e) { passwordFailed(e); return; } if (!sys.async) dialog.show = false; } protected void passwordFailed (Exception e) { gui.alert("Failed", e.getMessage(), dialog, $warning); } Quest Sample Application 305 protected void onCancel () { dialog.show = false; } protected boolean oldPassRequired () { return !Quest.singleton.getCurrUser().isAdminUser() || user.isAdminUser(); } protected boolean enableChange () { return (!oldPassRequired() || oldPasswd.value != "") && newPasswd.value != "" && confirmPasswd.value != ""; } } </jag> 8.3.4 Issue Management 8.3.4.1 IssueSearch <jag domain="quest/gui/issue"> <load> "lib/lang/Common" "lib/bom/Object" "lib/gui/GuiUtil" "lib/gui/Table" "lib/gui/Search" "lib/gui/Html" "lib/gui/PickListMgr" "quest/bom/User" "quest/bom/Issue" "quest/bom/Rules" "quest/gui/AppTree" "quest/Quest" </load> singleton class IssueSearch extends SearchScreen, HtmlGenerator { static final vector<map> TABLE_FORMAT = [ [$key=>$id, $title=>"ID", $width=>20, $align=>$east] ,[$key=>$title, $title=>"Title", $width=>100, $align=>$west] ,[$key=>$project, $title=>"Project", $width=>50, $align=>$west] ,[$key=>$module, $title=>"Module", $width=>50, $align=>$west] ,[$key=>$build, $title=>"Build", $width=>50, $align=>$west] ,[$key=>$type, $title=>"Type", $width=>50, $align=>$west] ,[$key=>$state, $title=>"State", $width=>50, $align=>$west] ,[$key=>$severity, $title=>"Severity", $width=>50, $align=>$west] ,[$key=>$priority, $title=>"Priority", $width=>50, $align=>$west] ,[$key=>$raisedByName, $title=>"Raised By", $width=>50, $align=>$west] ,[$key=>$assignedToName, $title=>"Assigned To", $width=>50, $align=>$west] ]; static final vector<symbol> PIE_KEYS = [$project, $type, $state, $severity, $priority]; int reportNo = 0; <Panel criteria> <Layout.gridBag/> <Lay row=0 col=0 margin=2 weight=0.0 align=$east> <Label title="Search"/> </Lay> <Lay row=0 col=1 margin=2 fill=$horizontal> <Hot:Combo byCombo key=$searchBy data=["By Criteria", "By ID"]/> 306 JavaGram Agile Development </Lay> <Lay row=0 col=2 margin=2 weight=0.0 align=$east> <Label title="ID"/> </Lay> <Lay row=0 col=3 margin=2 fill=$horizontal> <Field.text key=$id enable={isIdSearch()}/> </Lay> <Lay row=1 col=0 margin=2 weight=0.0 align=$east> <Label title="Title"/> </Lay> <Lay row=1 col=1 margin=2 colSpan=3 fill=$horizontal> <Field.text key=$title enable={isCriteriaSearch()}/> </Lay> <Lay row=2 col=0 margin=2 weight=0.0 align=$east> <Button title="Project" image={Quest.HAMMER} action={PickListMgr.singleton.editList($Project)}/> </Lay> <Lay row=2 col=1 margin=2 fill=$horizontal> <Hot:Combo projCombo key=$project enable={isCriteriaSearch()} data={PickListMgr.singleton.registerList($Project, projCombo)}/> </Lay> <Lay row=2 col=2 margin=2 weight=0.0 align=$east> <Label title="Module"/> </Lay> <Lay row=2 col=3 margin=2 fill=$horizontal> <Hot:Field.text key=$module enable={isCriteriaSearch()}/> </Lay> <Lay row=2 col=4 margin=2 weight=0.0 align=$east> <Label title="Build"/> </Lay> <Lay row=2 col=5 margin=2 fill=$horizontal> <Hot:Field.text key=$build enable={isCriteriaSearch()}/> </Lay> <Lay row=3 col=0 margin=2 weight=0.0 align=$east> <Button title="Type" image={Quest.HAMMER} action={PickListMgr.singleton.editList($Type)}/> </Lay> <Lay row=3 col=1 margin=2 fill=$horizontal> <Hot:Combo typeCombo key=$type enable={isCriteriaSearch()} data={PickListMgr.singleton.registerList($Type, typeCombo)}/> </Lay> <Lay row=3 col=2 margin=2 weight=0.0 align=$east> <Label title="State"/> </Lay> <Lay row=3 col=3 margin=2 fill=$horizontal> <Hot:Combo key=$state enable={isCriteriaSearch()} data={Rules.singleton.getStates()}/> </Lay> <Lay row=3 col=4 margin=2 weight=0.0 align=$east> <Button title="Severity" image={Quest.HAMMER} action={PickListMgr.singleton.editList($Severity)}/> </Lay> <Lay row=3 col=5 margin=2 fill=$horizontal> Quest Sample Application 307 <Hot:Combo sevCombo key=$severity enable={isCriteriaSearch()} data={PickListMgr.singleton.registerList($Severity, sevCombo)}/> </Lay> <Lay row=4 col=0 margin=2 weight=0.0 align=$east> <Button title="Priority" image={Quest.HAMMER} action={PickListMgr.singleton.editList($Priority)}/> </Lay> <Lay row=4 col=1 margin=2 fill=$horizontal> <Hot:Combo priorCombo key=$priority enable={isCriteriaSearch()} data={PickListMgr.singleton.registerList($Priority, priorCombo)}/> </Lay> <Lay row=4 col=2 margin=2 weight=0.0 align=$east> <Label title="Raised By"/> </Lay> <Lay row=4 col=3 margin=2 fill=$horizontal> <Hot:Combo key=$raisedBy enable={isCriteriaSearch()} model=userModel/> </Lay> <Lay row=4 col=4 margin=2 weight=0.0 align=$east> <Label title="Assigned To"/> </Lay> <Lay row=4 col=5 margin=2 fill=$horizontal> <Hot:Combo key=$assignedTo enable={isCriteriaSearch()} model=userModel/> </Lay> </Panel> public IssueSearch () { super@SearchScreen( new SearchCriteriaPanel(this, criteria), new TableAndPieSearchResultScreen(this, TABLE_FORMAT, new TableButtons([$details, $new, $delete, $jump]), PIE_KEYS) { protected Object drillRow (Object bo) { Callback cb = null; if (sys.async) { cb = new Callback() { public void completed (vague data) { Issue issue @= bo; if (issue.getAttach() != null && issue.getHistory() != null) AppTree.singleton.showIssue(bo); } public void failed (Exception e) { gui.alert("Can get issue details", e.getMessage(), GuiUtil.getMainFrame(), $error); } }; } bo@Issue.populateAttach(cb); bo@Issue.populateHistory(cb); if (!sys.async) AppTree.singleton.showIssue(bo); return bo; } protected vague createRow (Callback cb = null) { int userId @= Quest.singleton.getUserId(); Issue issue = new Issue(object(IssueDetails, title=>"NewIssue", state=>"New" , raisedBy=>userId, raisedOn=>sys.date())); issue.persist() ?? cb != null -> cb.completed(issue); 308 JavaGram Agile Development return issue; } protected vague deleteRow (int idx, Callback cb = null) { vague row = super@Table.deleteRow(idx, !sys.async ? null : new Callback(cb) { public void completed (vague data) { AppTree.singleton.hideIssue(rowToObject(data)); } }); AppTree.singleton.hideIssue(rowToObject(row)); return row; } } ); getResultScreen().setCanDeleteRow(Quest.singleton.getCurrUser().getCanDelete()); } protected boolean isIdSearch () { return byCombo.value == "By ID"; } protected boolean isCriteriaSearch () { return byCombo.value == "By Criteria"; } protected vector find (map criteria, Callback cb = null) { if (criteria.$searchBy == "By ID") { Issue issue = Issue.findById(criteria.$id, cb); return issue == null ? vector() : vector(issue); } else { return Issue.find(criteria, cb); } } public void clearCmd () { super@SearchScreen.clearCmd(); AppTree.singleton.hideIssue($all); } protected vague userModel (native combo, symbol cmd, int idx) { return User.userModel(false, cmd, idx); } } </jag> 8.3.4.2 IssueScreen <jag domain="quest/gui/issue"> <load> "lib/lang/Common" "lib/gui/Screen" "lib/gui/GuiUtil" "lib/gui/Html" "lib/bom/Object" "quest/bom/Issue" "quest/bom/Attachment" "quest/bom/History" "quest/bom/User" "quest/bom/Rules" "quest/Quest" "quest/gui/AppTree" "lib/gui/PickListMgr" "quest/gui/issue/AssignDialog" Quest Sample Application 309 "quest/gui/issue/ResolveDialog" "quest/gui/issue/RejectDialog" </load> singleton class IssueScreen extends Screen, HtmlGenerator { static final vector<map> ATTACH_TABLE_FORMAT = [ [$key=>$name, $title=>"File Name", $width=>200, $align=>$west] ,[$key=>$path, $title=>"Local Name or Path", $width=>400, $align=>$west] ,[$key=>$modified, $title=>"Modified", $width=>150, $align=>$east] ,[$key=>$size, $title=>"Size", $width=>50, $align=>$east] ]; static final vector<map> HIST_TABLE_FORMAT = [ [$key=>$actorName, $title=>"Actioned By", $width=>100, $align=>$west] ,[$key=>$action, $title=>"Action", $width=>50, $align=>$west, $format=>$name] ,[$key=>$actDate, $title=>"Date", $width=>50, $align=>$west] ,[$key=>$oldState, $title=>"Old State", $width=>50, $align=>$west] ,[$key=>$newState, $title=>"New State", $width=>50, $align=>$west] ,[$key=>$note, $title=>"Note", $width=>200, $align=>$west] ]; string lastAttachDir = "C:/"; <Panel view> <Layout.border/> <Panel lay=$north title="Issue Details" enable=true> <Layout.gridBag/> <Lay row=0 col=0 margin=2 weight=0.0 align=$east> <Label title="ID"/> </Lay> <Lay row=0 col=1 margin=2 fill=$horizontal> <Field.number key=$id readOnly=true precision=0/> </Lay> <Lay row=1 col=0 margin=2 weight=0.0 align=$east> <Label title="Title"/> </Lay> <Lay row=1 col=1 margin=2 colSpan=3 fill=$horizontal> <Hot:Field.text key=$title/> </Lay> <Lay row=1 col=4 margin=2 weight=0.0 align=$east> <Label title="Raised On"/> </Lay> <Lay row=1 col=5 margin=2 fill=$horizontal> <Field.date key=$raisedOn format={GuiUtil.DATE_TIME_FORMAT} readOnly=true/> </Lay> <Lay row=2 col=0 margin=2 weight=0.0 align=$east> <Button title="Project" image={Quest.HAMMER} action={PickListMgr.singleton.editList($Project)}/> </Lay> <Lay row=2 col=1 margin=2 fill=$horizontal> <Hot:Combo projCombo key=$project data={PickListMgr.singleton.registerList($Project, projCombo)}/> </Lay> <Lay row=2 col=2 margin=2 weight=0.0 align=$east> <Label title="Module"/> </Lay> <Lay row=2 col=3 margin=2 fill=$horizontal> 310 JavaGram Agile Development <Hot:Field.text key=$module/> </Lay> <Lay row=2 col=4 margin=2 weight=0.0 align=$east> <Label title="Build"/> </Lay> <Lay row=2 col=5 margin=2 fill=$horizontal> <Hot:Field.text key=$build/> </Lay> <Lay row=3 col=0 margin=2 weight=0.0 align=$east> <Button title="Type" image={Quest.HAMMER} action={PickListMgr.singleton.editList($Type)}/> </Lay> <Lay row=3 col=1 margin=2 fill=$horizontal> <Hot:Combo typeCombo key=$type data={PickListMgr.singleton.registerList($Type, typeCombo)}/> </Lay> <Lay row=3 col=2 margin=2 weight=0.0 align=$east> <Label title="State"/> </Lay> <Lay row=3 col=3 margin=2 fill=$horizontal> <Field.text stateField key=$state readOnly=true/> </Lay> <Lay row=3 col=4 margin=2 weight=0.0 align=$east> <Button title="Severity" image={Quest.HAMMER} action={PickListMgr.singleton.editList($Severity)}/> </Lay> <Lay row=3 col=5 margin=2 fill=$horizontal> <Hot:Combo sevCombo key=$severity data={PickListMgr.singleton.registerList($Severity, sevCombo)}/> </Lay> <Lay row=4 col=0 margin=2 weight=0.0 align=$east> <Button title="Priority" image={Quest.HAMMER} action={PickListMgr.singleton.editList($Priority)}/> </Lay> <Lay row=4 col=1 margin=2 fill=$horizontal> <Hot:Combo priorCombo key=$priority data={PickListMgr.singleton.registerList($Priority, priorCombo)}/> </Lay> <Lay row=4 col=2 margin=2 weight=0.0 align=$east> <Label title="Raised By"/> </Lay> <Lay row=4 col=3 margin=2 fill=$horizontal> <Field.text key=$raisedByName readOnly=true/> </Lay> <Lay row=4 col=4 margin=2 weight=0.0 align=$east> <Label title="Assigned To"/> </Lay> <Lay row=4 col=5 margin=2 fill=$horizontal> <Field.text assignedToField key=$assignedToName readOnly=true/> </Lay> </Panel> <Pane.tabbed lay=$center> <Tab title="Description" image={sys.use("lib/gifs/Comment.gif")}> <Pane.scroll> Quest Sample Application 311 <Hot:Area.text key=$description/> </Pane.scroll> </Tab> <Tab title="Resolution" image={sys.use("lib/gifs/Apply.gif")}> <Pane.scroll> <Hot:Area.text key=$resolution/> </Pane.scroll> </Tab> <Tab title="Attachments" image={sys.use("lib/gifs/Attachment.gif")}> <Panel attachPanel> <Layout.border/> <Pane.scroll lay=$center> <Table attachTable key=$attach autoSize=true styled=true format={ATTACH_TABLE_FORMAT} event=attachHandler/> </Pane.scroll> <Panel lay=$south> <Button title="Attach..." image={sys.use("lib/gifs/Attachment.gif")} enable={canAttachDoc()} action={attachDoc()}/> <Button title="View" image={sys.use("lib/gifs/DrillDown.gif")} enable={canViewDoc()} action={viewDoc(false)}/> <Button title="Edit" image={sys.use("lib/gifs/Comment.gif")} enable={canEditDoc()} action={viewDoc(true)}/> <Button title="Save" image={sys.use("lib/gifs/Save.gif")} enable={canSaveDoc()} action={saveDoc()}/> <Button title="Unlock" image={sys.use("lib/gifs/Unlock.gif")} enable={canUnlockDoc()} action={unlockDoc(true)}/> <Button title="Delete" image={sys.use("lib/gifs/Delete.gif")} enable={canDeleteDoc()} action={deleteDoc()}/> </Panel> </Panel> </Tab> <Tab title="History" image={sys.use("lib/gifs/Calendar.gif")}> <Pane.scroll> <Table histTable key=$history autoSize=true styled=true format={HIST_TABLE_FORMAT}/> </Pane.scroll> </Tab> </Pane.tabbed> <Panel lay=$east> <Layout.gridBag/> <Lay row=0 col=0 margin=2 fill=$horizontal> <Button title="Submit" image={sys.use("lib/gifs/RightArrow.gif")} enable={actionAllowed($submit)} action={submit()}/> </Lay> <Lay row=1 col=0 margin=2 fill=$horizontal> <Button title="Assign" image={sys.use("lib/gifs/Assign.gif")} enable={actionAllowed($assign)} action={assign()}/> </Lay> <Lay row=2 col=0 margin=2 fill=$horizontal> <Button title="Open" image={sys.use("lib/gifs/Open.gif")} enable={actionAllowed($open)} action={open()}/> </Lay> <Lay row=3 col=0 margin=2 fill=$horizontal> <Button title="Resolve" image={sys.use("lib/gifs/Resolve.gif")} enable={actionAllowed($resolve)} action={resolve()}/> </Lay> 312 JavaGram Agile Development <Lay row=4 col=0 margin=2 fill=$horizontal> <Button title="Reject" image={sys.use("lib/gifs/Reject.gif")} enable={actionAllowed($reject)} action={reject()}/> </Lay> <Lay row=5 col=0 margin=2 fill=$horizontal> <Button title="Close" image={sys.use("lib/gifs/Close.gif")} enable={actionAllowed($close)} action={close()}/> </Lay> <Lay row=6 col=0 margin=2 fill=$horizontal> <Button title="Reopen" image={sys.use("lib/gifs/Open.gif")} enable={actionAllowed($reopen)} action={reopen()} enable={canReopen()}/> </Lay> </Panel> </Panel> <FileChooser fileChooser owner={GuiUtil.getMainFrame()} multiSelect=false file=true title="Select the file to attach" path={lastAttachDir}/> protected Attachment getSelectedAttachment () { int row @= attachTable.select; return (row == null ? null : attachTable.data[row]) @ Attachment; } protected string getSelectedAttachmentName () { Attachment doc = getSelectedAttachment(); return doc == null ? "unnamed" : doc.getName(); } protected void attachHandler (native comp, symbol event) { switch (event) { case $select: gui.maintain(attachPanel); break; case $drill: viewDoc(false); break; } } protected boolean canAttachDoc () { return editable; } protected void attachDoc () { int issueId @= getContent()@Issue.getId(); if (sys.flash) { sys.upload(null, Attachment.genUniqueTempFilePath(), null, true, $attachCB , list(issueId)); return; } string path @= fileChooser.show; if (path == null) return; map<symbol, string> parts = sys.pathParts(path); map props = sys.pathProps(path); lastAttachDir @= parts[$dir]; string name = parts[$name] + (parts[$ext] == "" ? "" : "." + parts[$ext]); AttachmentDetails details = object(AttachmentDetails, issueId => issueId, name => name, size => props[$length]@int, modified => props[$modified]@date ); Quest Sample Application 313 Attachment doc = new Attachment(details, path); saveAndRefresh(doc, false); } protected void attachCB (vague res, int issueId) { if (res instanceof map) { map info @= res; AttachmentDetails details = object(AttachmentDetails, issueId => issueId, name => info.$local@string, size => info.$size@int, modified => sys.date(info.$timestamp@int) ); Attachment doc = new Attachment(details, info.$remote@string); doc.saveUploaded(info.$remote@string, !sys.async ? null : new Callback() { public void completed (vague data) { refreshAttachments(); } }); } } protected boolean canViewDoc () { return getSelectedAttachment() != null; } protected void viewDoc (boolean edit) { Attachment doc = getSelectedAttachment(); if (sys.flash) { doc.retrieve(null, new Callback() { public void completed (vague data) { attachTable.refresh = true; } }); } else { try { doc.refresh() ?> sys.async -> afterRefreshDoc(doc, edit) -> refreshDocFailed(doc, sys.getAsyncErr()); } catch (Exception e) { refreshDocFailed(doc, e); return; } } } protected void afterRefreshDoc (Attachment doc, boolean edit) { int lockedBy @= doc.lockedBy(); boolean lockedByMe = lockedBy == Quest.singleton.getUserId(); boolean lockedByOther = lockedBy != null && !lockedByMe; string dir = Attachment.recommendLocalDir(doc.getIssueId()); string path = sys.pathConc(dir, doc.getName()); boolean exists = sys.pathExists(path); if (edit && lockedByOther) { gui.alert("Can't edit " + doc.getName(), "File is currently locked by " + User.getUserInfo(false, lockedBy)[$name]@string, GuiUtil.getMainFrame(), $waring); refreshAttachments(); return; } map pathProps = exists ? sys.pathProps(path) : null; 314 JavaGram Agile Development boolean useLocalCopy = exists && (lockedByMe || pathProps[$modified] == doc.getModified()); boolean lockIt = edit && !lockedByMe; symbol amend; if (!edit && !lockedByMe) amend = $makeReadOnly; else if (exists && edit && !pathProps[$writeable]@boolean) amend = $makeWriteable; if (useLocalCopy) { doc.setSize(pathProps[$length]@int); doc.setPath(path); } else { path = doc.retrieve(dir); } if (lockIt) { try { doc.lock(Quest.singleton.getUserId(), !sys.async ? null : new Callback() { public void completed (vague data) { afterLockDoc(data@string, amend); } public void failed (Exception e) { failLockDoc(doc, e); } }); } catch (Exception e) { failLockDoc(doc, e); return; } } if (!sys.async || !lockIt) afterLockDoc(path, amend); } protected void refreshDocFailed (Attachment doc, Exception e) { gui.alert("Can't access " + doc.getName(), "Reason: " + e.getMessage(), GuiUtil.getMainFrame(), $warning); refreshAttachments(); } protected void afterLockDoc (string path, symbol amend) { if (amend == $makeReadOnly) Util.setFilePermission(path, $readOnly); else if (amend == $makeWriteable) Util.setFilePermission(path, $writeable); attachTable.refresh = true; Desktop.open(path); } protected void failLockDoc (Attachment doc, Exception e) { gui.alert("Can't lock " + doc.getName(), "Reason: " + e.getMessage(), GuiUtil.getMainFrame(), $waring); refreshAttachments(); return; } protected boolean canEditDoc () { return !sys.flash && editableDoc(); } protected boolean editableDoc () { Attachment doc = getSelectedAttachment(); Quest Sample Application 315 int id @= doc?.lockedBy(); return editable && doc != null && (id == null || id == Quest.singleton.getUserId()); } protected boolean canSaveDoc () { Attachment doc = getSelectedAttachment(); int id @= doc?.lockedBy(); return editable && doc != null && id == Quest.singleton.getUserId(); } protected void saveDoc () { Attachment doc = getSelectedAttachment(); saveAndRefresh(doc, true); unlockDoc(false); } protected boolean canUnlockDoc () { return canSaveDoc(); } protected void unlockDoc (boolean warn) { if (warn) gui.alert("Are you sure?", $"Unlock {getSelectedAttachmentName()}?\nUnlocking will disca rd any changes made to the document.'", GuiUtil.getMainFrame(), $question, $unlockDocContinue); else unlockDocContinue($default); } protected void unlockDocContinue (symbol answer) { if (answer == $no || answer == $cancel) return; Attachment doc = getSelectedAttachment(); try { doc.unlock(Quest.singleton.getUserId(), !sys.async ? null : new Callback() { public void completed (vague data) { refreshAttachments(); } public void failed (Exception e) { failUnlockDoc(doc, e); } }); } catch (Exception e) { failUnlockDoc(doc, e); return; } if (!sys.async) refreshAttachments(); } protected void failUnlockDoc (Attachment doc, Exception e) { gui.alert("Can't unlock " + doc.getName(), "Reason: " + e.getMessage(), GuiUtil.getMainFrame(), $warning); } protected boolean canDeleteDoc () { return editableDoc(); } protected void deleteDoc () { gui.alert("Are you sure?", $"Permanently delete {getSelectedAttachmentName()}?", GuiUtil.getMainFrame(), $question, $deleteDocContinue); } protected void deleteDocContinue (symbol answer) { 316 JavaGram Agile Development if (answer == $no || answer == $cancel) return; Attachment doc = getSelectedAttachment(); try { doc.delete(!sys.async ? null : new Callback() { public void completed (vague data) { refreshAttachments(); } public void failed (Exception e) { deleteDocFailed(doc, e); } }); } catch (Exception e) { deleteDocFailed(doc, e); } if (!sys.async) refreshAttachments(); } protected void deleteDocFailed (Attachment doc, Exception e) { gui.alert("Can't delete " + doc.getName(), "Reason: " + e.getMessage(), GuiUtil.getMainFrame(), $warning); } protected void refreshAttachments () { Issue issue @= getContent(); Attachment doc = getSelectedAttachment(); vector<Attachment> vec @= Attachment.find(Attachment, $issueId, issue.getId()) ?> sys.async -> { issue.setAttach(vec); attachTable.data = vec; attachTable.refresh = true; if (doc != null) { // Select what was selected before refresh: for (int i = 0, n = sys.length(vec); i < n; ++i) { if (doc.getId() == vec[i].getId()) { attachTable.select = i; break; } } } }; } protected void saveAndRefresh (Attachment doc, boolean existing) { try { doc.save(existing); } catch (Exception e) { gui.alert("Can't save " + doc.getName(), "Reason: " + e.getMessage(), GuiUtil.getMainFrame(), $warning); } refreshAttachments(); } protected boolean actionAllowed (symbol action) { Quest Sample Application 317 Issue issue @= getContent(); return issue != null && sys.find(Rules.singleton.stateToActions(issue.getState()), action) != null; } protected boolean canReopen () { Issue issue @= getContent(); return issue != null && issue.getState() == "Closed" && Quest.singleton.getCurrUser().isAdminUser(); } protected void submit () { if (onModified()) doAction($submit); } protected void assign () { if (onModified()) { int userId = AssignDialog.singleton.show(getContent()@Issue.getAssignedTo()@int, !sys.async ? null : new Callback(){ public void completed (vague data) { afterAssign(data@int); } }); if (!sys.async) afterAssign(userId); } } protected void afterAssign (int userId) { if (userId != null) { getContent()@Issue.setAssignedTo(userId); string assignedToName @= User.getUserInfo(true, userId)[$name]; assignedToField.value = assignedToName; doAction($assign, "Assigned to " + assignedToName); } } protected void open () { if (onModified()) doAction($open); } protected void resolve () { if (onModified()) { string resolution = ResolveDialog.singleton.show(!sys.async ? null : new Callback(){ public void completed (vague data) { afterResolve(data@string); } }); if (!sys.async) afterResolve(resolution); } } protected void afterResolve (string resolution) { if (resolution != null) doAction($resolve, "Resolved: " + resolution); } protected void reject () { if (onModified()) { string reason = RejectDialog.singleton.show(!sys.async ? null : new Callback(){ public void completed (vague data) { 318 JavaGram Agile Development afterReject(data@string); } }); if (!sys.async) afterReject(reason); } } protected void afterReject (string reason) { if (reason != null) doAction($reject, "Reason: " + reason); } protected void close () { if (onModified()) doAction($close); } protected void reopen () { if (onModified()) doAction($open, "Reopened"); } protected boolean onModified () { boolean res = super@Screen.onModified(); return res && getContent()@Issue.lockedBy() == Quest.singleton.getUserId(); } protected void doAction (symbol action, string note = null) { Issue issue @= getContent(); string oldState = issue.getState(); string newState = Rules.singleton.actionToState(action); if (newState == null) newState = oldState; issue.setState(newState); stateField.value = newState; HistoryDetails hd = object(HistoryDetails , issueId => issue.getId()@int , actorId => Quest.singleton.getUserId()@int , actDate => sys.date() , action => action , oldState => oldState , newState => newState , note => note ); History hist = new History(hd).updateVolatiles(); issue.addHistory(hist); AppTree.singleton.persistNodeCmd(false); } } </jag> 8.3.4.3 AssignDialog <jag domain="quest/gui/issue"> <load> "lib/lang/Common" "lib/gui/GuiUtil" "quest/bom/User" </load> singleton class AssignDialog { string oldUserName; Quest Sample Application 319 int assignedUserId; Callback cb; <Dialog dialog parent={GuiUtil.getMainFrame()} center={GuiUtil.getMainFrame()} modal=true fixed=true width=200 height=90 title="Assign Issue"> <Layout.border/> <Panel lay=$center> <Layout.gridBag/> <Lay row=0 col=0 margin=2 align=$east weight=0.0> <Label title="Assign To"/> </Lay> <Lay row=0 col=1 margin=2 fill=$horizontal weight=1.0> <Combo idCombo model=userModel event=eventHandler/> </Lay> </Panel> <Panel lay=$south> <Button assignButn title="Assign" image={sys.use("lib/gifs/Assign.gif")} action={assign()} enable={canAssign()}/> <Button title="Cancel" image={sys.use("lib/gifs/Cancel.gif")} action={cancel()}/> </Panel> </Dialog> public int show (int userId, Callback cb = null) { this.cb = cb; oldUserName @= User.getUserInfo(false, userId)[$name]; idCombo.value = User.getUserInfo(true, userId)[$name]; dialog.enter = assignButn; gui.maintain(dialog); dialog.show = true; return assignedUserId; } protected boolean canAssign () { return idCombo.value != "" && idCombo.value != oldUserName; } protected void assign () { assignedUserId @= User.userModel(true, $id, idCombo.select); dialog.show = false; if (cb != null) cb.completed(assignedUserId); } protected void cancel () { assignedUserId = null; dialog.show = false; } protected void eventHandler (native comp, symbol event) { gui.maintain(dialog); } protected vague userModel (native combo, symbol cmd, int idx) { return User.userModel(true, cmd, idx); } } </jag> 8.3.4.4 ResolveDialog <jag domain="quest/gui/issue"> <load> 320 JavaGram Agile Development "lib/lang/Common" "lib/gui/GuiUtil" </load> singleton class ResolveDialog { string resolution; Callback cb; <Dialog dialog parent={GuiUtil.getMainFrame()} center={GuiUtil.getMainFrame()} modal=true fixed=true width=400 height=200 title="Resolve Issue"> <Layout.border/> <Panel lay=$center title="Resolution"> <Layout.border/> <Pane.scroll lay=$center> <Area.text resTextArea lineWrap=true/> </Pane.scroll> </Panel> <Panel lay=$south> <Button resolveButn title="Resolve" image={sys.use("lib/gifs/Apply.gif")} action={resolve()}/> <Button title="Cancel" image={sys.use("lib/gifs/Cancel.gif")} action={cancel()}/> </Panel> </Dialog> public string show (Callback cb = null) { this.cb = cb; dialog.enter = resolveButn; resTextArea.value = ""; dialog.show = true; return resolution; } protected void resolve () { resolution @= resTextArea.value; dialog.show = false; if (cb != null) cb.completed(resolution); } protected void cancel () { resolution = null; dialog.show = false; } } </jag> 8.3.4.5 RejectDialog <jag domain="quest/gui/issue"> <load> "lib/lang/Common" "lib/gui/GuiUtil" </load> singleton class RejectDialog { string reason; Callback cb; <Dialog dialog parent={GuiUtil.getMainFrame()} center={GuiUtil.getMainFrame()} modal=true fixed=true width=400 height=200 title="Reject Resolution"> <Layout.border/> <Panel lay=$center title="Rejection Reason"> Quest Sample Application 321 <Layout.border/> <Pane.scroll lay=$center> <Area.text rejTextArea lineWrap=true/> </Pane.scroll> </Panel> <Panel lay=$south> <Button rejectButn title="Reject" image={sys.use("lib/gifs/Reject.gif")} action={reject()}/> <Button title="Cancel" image={sys.use("lib/gifs/Cancel.gif")} action={cancel()}/> </Panel> </Dialog> public string show (Callback cb = null) { this.cb = cb; dialog.enter = rejectButn; rejTextArea.value = ""; dialog.show = true; return reason; } protected void reject () { reason @= rejTextArea.value; dialog.show = false; if (cb != null) cb.completed(reason); } protected void cancel () { reason = null; dialog.show = false; } } </jag> 8.3.5 Setting 8.3.5.1 PickListPanel <jag domain="quest/gui/setting"> <load> "lib/lang/Common" "lib/bom/Object" "lib/bom/PickList" "lib/gui/Table" "lib/gui/GuiUtil" "lib/gui/PickListMgr" </load> singleton class PickListPanel extends Table { static final vector<map> tableFormat = [ [$key=>$editable, $title=>"Editable", $width=>10, $align=>$center] ,[$key=>$name, $title=>"Name", $width=>100, $align=>$west] ,[$key=>$count, $title=>"#Items", $width=>20, $align=>$center] ,[$key=>$description, $title=>"Description", $width=>200, $align=>$west] ]; <Panel view title="Pick Lists"> <Layout.border/> <Pane.scroll lay=$center> <Indirect ref={table} key=$plVec/> </Pane> 322 JavaGram Agile Development <Indirect ref={(new TableButtons(this, [$details])).view} lay=$east/> </Panel> public PickListPanel () { super(tableFormat); } protected vague formatCell (vague obj, symbol key) { switch (key) { case $editable: return sys.get(obj, key) == true ? GuiUtil.TICK_STR : ""; case $count: return sys.length(sys.get(obj, $items)); default: return sys.get(obj, key); } } public boolean detailsItemCmd (boolean maintain) { if (maintain) { return hasSelection(); } else { vague item = getSelectedItem(); if (item != null) sys.call(Util.getSingleton("lib/gui/PickListMgr"), $show, item); } return true; } protected Object drillRow (Object bo) { PickListMgr.singleton.show(bo@PickList); refresh(); return bo; } } </jag> 8.3.5.2 LogPanel <jag domain="quest/gui/setting"> <load> "lib/gui/Screen" </load> singleton class LogPanel extends Screen { <Font monoFont name="Courier" size=11/> <Panel view title="Quest Output Log"> <Layout.border/> <Panel lay=$north> <Layout.flow/> <Button title="Clear" image={sys.use("lib/gifs/Clear.gif")} action={logText.value=""}/> </Panel> <Pane.scroll> <Area.text logText tab=4/> </Pane.scroll> </Panel> public LogPanel () { logText.font = monoFont; } public native getTextArea () { return logText; } } </jag> Quest Sample Application 323 8.3.6 Miscellaneous 8.3.6.1 AboutDialog <jag domain="quest/gui/issue"> <load> "lib/gui/GuiUtil" </load> singleton class AboutDialog { string resolution; <Dialog dialog parent={GuiUtil.getMainFrame()} center={GuiUtil.getMainFrame()} modal=true fixed=true title="About Quest" width=350 height=160> <Layout.border/> <Panel lay=$north> <Label align=$center image={sys.use("quest/doc/help/gifs/Quest.gif")}/> </Panel> <Panel lay=$center> <Layout.grid rows=3/> <Label align=$center title="Sample Application"/> <Label align=$center title="Developed using JavaGram Technology"/> <Label align=$center title="Copyright © 2010 PragSoft Corporation (www.pragsoft.com)"/> </Panel> <Panel lay=$south> <Button resolveButn title="OK" image={sys.use("lib/gifs/Apply.gif")} action={dialog.show=false}/> </Panel> </Dialog> public void show () { dialog.show = true; } } </jag> 8.3.6.2 HtmlPanel <jag domain="quest/gui/misc"> <load> "lib/bom/Document" "lib/gui/Html" "quest/gui/AppTree" </load> singleton class HtmlPanel extends HtmlScreen { public HtmlPanel () { super("quest"); } protected void doFileRef (string path, boolean isLocal) { super.doFileRef(path, isLocal); AppTree.singleton.showHelpPage(Document.getByPath(path)); } } </jag> 8.3.7 Configuration // Quest app-server configuration. [ $download=>[ $default=>["jag"=>$private, "*"=>$public] 324 JavaGram Agile Development , , , , , , , , , , $cache=>"svrCache/" ] $upload=>[$default=>["*"=>$public]] $security=>[$keystore=>"ssl/JAG.jks", $password=>"changeit", $plainPortInc=>100] $policy=>"flash/crossdomain.xml" $mode=>$async $partition => [ $cache => "parts" , $exclude => [".svn/*", "*.jagzip"] , "QuestPart" => [("quest/", $recursive, "*.jag", "*.html", "*.gif") ,("lib/", $recursive, "*.jag", "*.gif")] ] $session=>[$timeout=>300, $error=>10] // 5 minutes timeout $log=>[$timeout=>86400] // 1 day per log file $zip=>[$min=>128] $database=>[ $defPars=>[ $user=>"root" , $password=>"password" , $timeout=>[$login=>30, $query=>20] , $driver=>"com.mysql.jdbc.Driver" ] , $mysqlDb=>[ $defaults=>$defPars , $name=>"MySql" , $url=>"jdbc:mysql://localhost/mysql" ] , $questDb=>[ $defaults=>$defPars , $name=>"Quest" , $url=>"jdbc:mysql://localhost/quest" ] ] ] Quest Sample Application 325 9 Extensions Two parts of JavaGram have been intentionally designed to be user extensible – the GUI markup and the text markup notations. Internally, JavaGram uses reflection to determine what’s available in support of these notations. Therefore, so long as your extension follows the coding guidelines described in this chapter and its JAR file made accessible, JavaGram will support it in exactly the same way as what’s included in JAG. JavaGram also allows you to create ad-hoc extensions (anything that can be coded in Java) and access them via the sys.java() method. 9.1 Introduction When working on a JavaGram project, you might come across rare situations that are best implemented in Java. You can do this by writing Java code that augments JAG. To do this, you need to have some knowledge of the design of JAG and the classes that it comprises. This section outlines the JAG Java classes that you would need to interact with. The rest of the chapter describes actual examples of how extensions are written. 9.1.1 Parse Tree When a JavaGram script is parsed, it’s converted to a parse tree. Each parsed construct is represented by a node (which is a Java class instance) in the parse tree. For example, a string literal is represented by a StrNode, and an addition is represented by an AddExprNode. The various node types form an inheritance hierarchy, the superclass for which is AbsNode. Here is the UML diagram for a few sample nodes. Location Comparable AbsNode RealNode right left DateNode init DebugNode AddExprNode StrNode VarNode Insightable Location is a Java interface that allows JavaGram to record the physical location of a node in its parent script. Comparable is another interface that allows nodes to be compared 326 JavaGram Agile Development for equality. Every node is ultimately derived from the abstract class AbsNode. Therefore, every node can be treated as an instance of AbsNode. Note, for example, how AddExprNode treats its left and right operands as AbsNode instances. Primitive literals (such as integer, real, string, etc) are directly derived from AbsNode. All other nodes are derived from another abstract class called DebugNode which is itself a subclass of AbsNode. DebugNode captures debugging information so that JADE can exercise break points on them. There are over 120 node classes defined in the jag.parse.node package. To write an extension, you need to know about only a few of these. The ones you’re most likely to use deal with the built-in types, constants, and identifiers: TrueNode represents the constant true. This is a singleton class whose instance is accessed as TrueNode.self. FalseNode represents the constant false. This is a singleton class whose instance is accessed as FalseNode.self. NullNode represents the constant null. This is a singleton class whose instance is accessed as NullNode.self. CharNode represents a character literal. CharNode instances are cached, so you’re not allowed to instantiate this class – use CharNode.create() instead. IntNode represents an integer literal. IntNode instances in the range -128 to 127 are cached, so you’re not allowed to instantiate this class – use IntNode.create() instead. RealNode represents a real literal. StrNode represents a string literal. SymNode represents a symbol literal. SymNode instances are cached to ensure uniqueness, so you’re not allowed to instantiate this class – use SymNode.define() and SymNode.lookup() instead. DateNode represents a date literal. PairNode represents the building block of a list. VectorNode represents a vector. MapNode represents a map. IdNode represents an identifier. The jag.parse package contains classes that manage the parsing of JavaGram code. The most important classes in this package are: Lexer performs lexical analysis (i.e., tokenizes JavaGram source). Parser converts the token stream produces by Lexer into a parse tree. ProgramScope manages the scope of all identifiers. Extensions 327 Two important methods that every node supports are: public AbsNode analyze (Parser parser, long redoing) throws JagThrow { // Performs semantic analysis of the node } public AbsNode eval (Env env) throws JagThrow { // Evaluates the node during program execution } The JavaGram Analyzer analyzes a script by recursively calling analyze() on its parse tree. Likewise, the JavaGram Evaluator executes a script by recursively calling eval() on its parse tree. All exceptions in JAG are represented by JagThrow, which records an exception and its physical location. Therefore, most methods are defined to throw this exception. 9.1.2 Types JavaGram types are defined as instances of the jag.parse.node.TypeNode class. Primitive and pseudo types are defined as constants in this class: TypeNode.booleanType represents boolean TypeNode.charType represents char TypeNode.dateType represents date TypeNode.intType represents int TypeNode.listType represents list TypeNode.nativeType represents native TypeNode.objectType represents object TypeNode.realType represents real TypeNode.streamType represents stream TypeNode.stringType represents string TypeNode.symbolType represents symbol TypeNode.vagueType represents vague TypeNode.voidType represents void Because vector types can be parameterized, they’re represented by instances of jag.parse.node.VectorTypeNode which subclasses TypeNode. You can’t instantiate this class directly; instead, you should use its create() method, which caches the new type. For example, VectorTypeNode.create(null, TypeNode.listType) defines a vector of lists, or vector<list>. Within this class, two vector types are predefined: 328 JavaGram Agile Development VectorTypeNode.defaultType represents vector<vague> which is the same as vector. VectorTypeNode.strVecType represents vector<string> Similarly, map types are represented by instances of jag.parse.node.MapTypeNode which sublcasses TypeNode. For example, MapTypeNode.create(null, TypeNode.symbolType, TypeNode.stringType) defines map<symbol, string>. The default map type is predefined in this class as MapTypeNode.defaultType, which represents map<vague, vague> which is the same as map. A user-defined type is represented by an instance of jag.parse.node.NamedTypeNode, which is eventually resolved by the analyzer to an instance of jag.parse.node.ClassNode, which is a subclass of NamedTypeNode. 9.1.3 Objects All class instances (objects) are represented by the jag.run.obj.ClassObj abstract class. AbsNode Location fields NamedTypeNode type ProxyObj ClassObj obj ActualObj RemoteObj LiteralObj ClassObj has a number of subclasses that represent different ‘flavors’ of objects: ActualObj represents real objects that have representation in memory (i.e., local objects created using the new operator). LiteralObj represents literal objects (i.e., objects that are directly expressed in code). RemoteObj represents instances of remote classes. ProxyObj represents the client-side proxies of remote objects. To create a JavaGram object in Java code, use the ActualObj.create() method, whose signature is: public static ActualObj create (Env env, ClassNode klass) This method examines klass to determine whether the instance should be local or remote. Extensions 329 9.1.4 IO All JavaGram streams (be they file, memory, or channel based) are represented by the jag.io.JagStream class, which is a subclass of AbsNode. Memory based streams (created using sys.buffer()) also use StrWriter and StrReader, which in turn use StrBuffer to maintain a memory representation of the IO data. AbsNode PrintWriter BufferedReader JagStream StrWriter StrReader buf buf StrBuffer To read from an input stream, you first use the JagStream.getIn() method, which returns the stream’s Parser instance. You then use one of the parser methods for reading. For example: // Assuming that ps is an input JagStream: AbsNode expr = ps.getIn().parseAndAnalyzeExpr(null); Every parse tree node has a pp() method for pretty printing that node to a PrintWriter, the signature of which is: public void pp (Env env, int margin, PrintWriter to, int flags); To write to an output stream, you first use the JagStream.getOut() method, which returns the stream’s PrintWriter instance. You then pass the latter to the pp() method of the expression you want to output. Pass 0 for margin, and any combination of flags defined in AbsNode for flags. For example: expr.pp(env, 0, ps.getOut(),AbsNode.DETAILED_FLAG | AbsNode.LINE_BREAK_FLAG); 9.1.5 Environment The class jag.run.Env provides a complete runtime environment for each running thread. You would notice that a lot of JAG methods take an Env type argument. This is because they need to have access to things like the thread’s stack, IO streams, and so on. Env also provides methods for reporting runtime errors and warnings. 9.2 Writing a GUI Extension This section demonstrates the process of writing a JavaGram GUI extension in Java. The new element we’ll be creating here is called <MemoryBar>, whose aim is to provide a 330 JavaGram Agile Development visual representation of the amount of memory consumed against the total amount of memory allocated to the current process. 9.2.1 Usage To illustrate the appearance and behavior of the new element, we’ve written a sample application that uses it. <jag domain="doc/code/chap9"> <load> "lib/gui/GuiApp" </load> singleton class ExtDemo extends GuiApp { <App app lookAndFeel=$windows> <Frame frame title="Demo App" width=300 height=200 event=frameHandler> <Panel> <Layout.border/> <Panel lay=$south> <MemoryBar allowGC=true delay=2000/> </Panel> </Panel> </Frame> </App> public ExtDemo () { super(frame); } protected void frameHandler (native comp, symbol event) { if (event == $close) exit(); } public static void main () { ExtDemo.singleton.run(); } } </jag> This application is similar to our earliest demo application presented in Chapter 4. It displays a frame, at the bottom of which we have a <MemoryBar>. Running the application displays the following frame. Extensions 331 The memory bar supports two properties (in addition to the normal properties supported by all visual elements). When set to true, the allowGC property causes a small trashcan button to be displayed next to the memory bar. Pressing this button invokes garbage collection. The delay property specifies (in milliseconds) how often the memory bar should be updated. 9.2.2 Naming Conventions Every GUI element is implemented by a Java class in the jag.gui.tags package. The class name must be the same as the element name, plus a Gui prefix. So, for example, the qualified Java class name for <MemoryBar> would be jag.gui.tags.GuiMemoryBar. Where an element name contains a period, this should be replaced by an underscore (e.g., the Java class name for <Field.text> is jag.gui.tags.GuiField_text). 9.2.3 Implementation To implement the new GUI element, you must subclass an existing Java class defined in JAG.jar. You have three options: Subclass jag.gui.tags.GuiAbcComp. This is recommended for non-visual elements (e.g., <Worker>). Subclass jag.gui.tags.GuiComp. This is recommended for visual elements that need to be developed from the grounds up. Subclass the class of an existing GUI element. This is recommended for elements that build on top of existing elements (e.g., to devise a new type of text field, you can subclass jag.gui.tags.GuiField_text). We’ve used the second approach for <MemoryBar>, resulting in the following Java class. package jag.gui.tags; import import import import import import import import import import java.awt.*; java.awt.event.*; java.util.*; javax.swing.*; jag.gui.GuiUtil; jag.misc.JagThrow; jag.parse.Location; jag.parse.node.*; jag.run.Env; jag.run.obj.ClassObj; public class GuiMemoryBar extends GuiComp { static final Color GREY = new Color(120, 120, 120); static final Color GREEN = new Color(0, 205, 0); static final Color RED = new Color(205, 0, 0); 332 JavaGram Agile Development static static static static final final final final Color AMBER = new Color(250, 128, 23); String ALLOW_GC = "allowGC"; String DELAY = "delay"; int DEFAULT_DELAY = 5000; // 5 seconds JButton butn = new JButton(new ImageIcon(GuiMemoryBar.class.getResource("Trash.gif"))); JProgressBar bar = new JProgressBar(); javax.swing.Timer timer = new javax.swing.Timer(DEFAULT_DELAY, null); public GuiMemoryBar (Location loc, ClassObj dis) { super(loc, dis, new JPanel()); getPanel().add(butn); getPanel().add(bar); bar.setStringPainted(true); bar.setBackground(GREY); updateMemory(); butn.setVisible(false); butn.setFocusPainted(false); Dimension d = new Dimension(20, 20); butn.setPreferredSize(d); butn.setMaximumSize(d); bar.setPreferredSize(new Dimension(90, 20)); bar.setMinimumSize(new Dimension(80, 20)); butn.addActionListener(new ActionListener() { public void actionPerformed (ActionEvent e) { gc(); } }); timer.addActionListener(new ActionListener() { public void actionPerformed (ActionEvent e) { updateMemory(); } }); timer.start(); } public JPanel getPanel () { return (JPanel) nativeVal; } public static Map propKinds; public static Map getPropKinds () { if (propKinds == null) { propKinds = new TreeMap(); propKinds.putAll(GuiComp.getPropKinds()); propKinds.put(DELAY, TypeNode.intType); propKinds.put(ALLOW_GC, TypeNode.booleanType); } return propKinds; Extensions 333 } public static String[] getPropValues (String prop) { if (prop.equals(ALLOW_GC)) return GuiUtil.falseAndTrue; return GuiComp.getPropValues(prop); } public AbsNode doProp (Env env, Location location, String prop, AbsNode val) throws JagThrow { if (prop.equals(ALLOW_GC)) { if (val != null) butn.setVisible(val == TrueNode.self); else return AbsNode.asTrueFalse(butn.isVisible()); } else if (prop.equals(DELAY)) { if (val != null) timer.setDelay(val.intValue(env, location)); else return IntNode.create(timer.getDelay()); } else super.doProp(env, location, prop, val); return TrueNode.self; } public AbsNode getValue () { return IntNode.create(bar.getValue()); } public void setValue (Env env, Location location, AbsNode value) throws JagThrow { // Not needed, as it's controlled internally } public void refresh () { updateMemory(); super.refresh(); } protected void updateMemory () { int totalMem = (int) (Runtime.getRuntime().totalMemory() / 1000000); int freeMem = (int) (Runtime.getRuntime().freeMemory() / 1000000); bar.setString(totalMem - freeMem + "M of " + totalMem + "M"); bar.setToolTipText("Memory: " + (totalMem - freeMem) + "M of allocated " + totalMem + "M used"); bar.setMaximum(totalMem); bar.setValue(totalMem - freeMem); double freeRatio = ((double)freeMem) / totalMem; Color fg = freeRatio >= 0.5 ? GREEN : (freeRatio >= 0.25 ? AMBER : RED); bar.setForeground(fg); } 334 JavaGram Agile Development protected void gc () { bar.grabFocus(); Runtime.getRuntime().gc(); updateMemory(); } } This class creates a button and a progress bar, and wraps these in a panel (see the constructor). It also sets up a timer for periodically refreshing the progress bar. The action handler for the button invokes the gc() method to do garbage collection. The action handler for the timer invokes updateMemory(), which displays the amount of memory used versus the total allocated, and uses green/amber/red color coding for the used portion. All GUI implementation classes must include the following pattern: public static Map propKinds; public static Map getPropKinds () { //... } public static String[] getPropValues (String prop) { //... } getPropKinds() is internally called by JavaGram to discover the type of each of the component’s properties. getPropValues() is called by JADE’s code insight to discover the list of possible values for those properties that are enumerations (e.g., possible values for the lay property). doProp() handles each of the component’s properties, and calls super.doProp() to handle properties inherited by the component. For each property, if val is non-null then the property is being set to that value. Otherwise, we’re getting the property’s value. For container components (not this example), you also need to override the doPart() method, whose signature is shown below. This method should add the component denoted by part to the container when add is true, and remove it otherwise. public AbsNode doPart (Env env, Location location, boolean add, AbsNode part) throws JagThrow; For components that support a value property, you also need to override the getValue() and setValue() methods, as we’ve done here (the latter is not really needed in this case and is only included to illustrates its signature). These methods are internally called by JavGram during data binding. Finally, we’ve also overridden refresh() which is internally called when the component needs to be refreshed. This is just for completeness and not really needed in this example. Extensions 335 9.3 Writing a Text Extension To implement a new text element, you must subclass an existing Java class defined in JAG.jar. You have two options: Subclass jag.text.handlers.HandlerText. This is recommended for text elements that need to be defined from the grounds up. Subclass the class of an existing text element. This is recommended for elements that build on top of existing elements (e.g., to devise a new type of SQL text element, you can subclass jag.text.handlers.HandlerText_sql or one of its subclasses). Rather than showing a new example here, we’ll present the code for HandlerText_sql which implements <text.sql>. package jag.text.handlers; import import import import import import import import java.util.*; jag.parse.node.*; jag.parse.Parser; jag.gui.GuiUtil; jag.misc.JagThrow; jag.Globals; jag.sql.*; jag.run.Env; public class HandlerText_sql extends HandlerText { public HandlerText_sql (TextDefNode def) { super(def); } public void analyzeProp (Parser parser, String prop, AbsNode value, long redoing) throws JagThrow { if (parser.isFake()) return; if (prop.equals(Globals.ESCAPE)) { if (value.getType() != TypeNode.stringType) parser.error(def, "'escape' property must have a string value: " + value); } else { super.analyzeProp(parser, prop, value, redoing); } } public static Map propKinds; public static Map getPropKinds () { if (propKinds == null) { propKinds = new TreeMap(); propKinds.putAll(HandlerText.getPropKinds()); propKinds.put(Globals.ESCAPE, TypeNode.stringType); } 336 JavaGram Agile Development return propKinds; } public static String[] getPropValues (String prop) { return HandlerText.getPropValues(prop); } public String handlePart (Env env, AbsNode part) throws JagThrow { AbsNode value = part.eval(env); String svalue = value.toString(); if (part instanceof IdNode) { String pn = part.toString(); throw JagThrow.warning(part, "substitution of {" + pn + "} not allowed in this context, use {`" + pn + "} or {=" + pn + "}"); } if (part instanceof SqlCurlyNode || value instanceof DelayedStrNode) return svalue; // these classes have already handled escaping return value instanceof StrNode ? SqlUtil.escapeSQLFragment(svalue, true) : svalue; } } The analyzeProp() method is overridden to process properties additional for this element. There is only one additional property here (escape), the rest are processed by calling the analyzeProp() method of the superclass. The call parser.isFake() returns true when the parser is processing compiled code, in which case we don’t need to analyze the properties because they would have already been analyzed during compilation. The getPropKinds() and getPropValues() methods are overridden for the same reasons as explained in Section 9.2 for GUI elements. When a text block is processed by the parser, it’s broken into fragments that comprise the text, separating literal text from spliced expressions (i.e., things enclosed in braces). The handlePart() method is overridden to process these fragments, each of which is denoted by the part parameter. If your text method returns a non-string type (not in this example), you will also need to override analyzeReturnType(). For example, <text.sql.update> returns an integer, so it overrides this method as follows. public void analyzeReturnType (Parser parser) throws JagThrow { if (def.getType() != TypeNode.intType) parser.error(def, "int return type expected: " + def.getType()); } Extensions 337 9.4 Writing an Ad-hoc Extension The final type of extension to be discussed here is an ad-hoc one – anything that for any reason you prefer to implement in Java. As an example, suppose that you need the functionality to ping servers. This can be easily implemented as a Java class package jag.misc; import java.io.*; public class Ping { public static String ping (String ip) { StringBuilder sb = new StringBuilder(); try { InputStream is = Runtime.getRuntime().exec("ping " + ip).getInputStream(); BufferedReader in = new BufferedReader(new InputStreamReader(is)); String line; while ((line = in.readLine()) != null) sb.append(line); in.close(); } catch (IOException e) { return null; } return sb.toString(); } } The Ping.ping() method takes the IP address (or domain name) of a server as argument, attempts to ping it by sending the command ping ip to it, and returns the result as a string. To call Ping.ping() from JavaGram, we simply use the sys.java() method. This method allows any accessible Java method (even constructors) to be called. Here is a sample JavaGram program that does just that. It displays a text field for entering an IP address, a button to initiate the ping, and a text area for displaying the result of the ping. <jag domain="doc/code/chap9"> <load> "lib/gui/GuiApp" </load> singleton class PingTest extends GuiApp { <App app lookAndFeel=$windows> <Frame frame title="Ping Test" width=300 height=200 event=frameHandler> <Panel> <Layout.border/> <Panel lay=$north> <Layout.border/> <Field.text ipField lay=$center/> <Button title="Ping" lay=$east action={ping(ipField.value@string)}/> 338 JavaGram Agile Development </Panel> <Pane.scroll lay=$center> <Area.text text/> </Pane.scroll> </Panel> </Frame> </App> public PingTest () { super(frame); //sys.jload("C:/eclipse/ws/SampleExt/JagExt.jar"); } protected void ping (string ip) { string res @= sys.java("jag.misc.Ping", null, "ping", list($string, ip)); text.value = res; } protected void frameHandler (native comp, symbol event) { if (event == $close) exit(); } public static void main () { PingTest.singleton.run(); } } </jag> Note the sys.java() call in the PingTest.ping() method. Because the Java method is static, the first argument specifies its qualified class name (otherwise, this argument should be a valid Java object reference). The second argument specifies the required return type. We’ve passed null here, which causes the Java return type to be automatically converted to its corresponding JavaGram type. The third argument is the method name, and the last argument is a list or vector of argument types and values. When you run this application, it displays the following frame (where we’ve entered an IP address and pressed the Ping button). Extensions 339 9.5 Packaging Your Extension Before using your extension, you must package it as a JAR file and make that JAR file available to JavaGram. The easiest way to do this is to use a Java IDE (such as Eclipse) to compile and JAR your Java code. Suppose that you’ve done this and produced a JAR file called JagExt.jar that includes the examples presented in this chapter. For GUI and text extensions, we recommend that you make the JAR file available to JADE so that the editor can visually recognize your extensions. For example, the command javaw –cp JADE.jar;JagExt.jar jade.Application will invoke JADE, making the extension available to it. Thereafter, if you type <MemoryBar> in the IDE, it will be recognized, and code insight will understand the <MemoryBar> properties. Similarly, when you run a JavaGram program, you should include the JAR file for your extensions. For example: javaw -cp JAG.jar;JagExt.jar jag.gui.Gui -root C:/JavaGram doc/code/chap9/PingTest.jag Running JavaGram programs within JADE is even easier. Simply, choose Preferences from the Tools menu, select the Environment/Java entry in the preferences tree, and add your extension’s JAR file to the list. This will be permanently remembered. Finally, for ad-hoc extensions only, you can load the JAR file during execution rather than specifying it as a part of the command line. To do this, use the sys.jload() method and pass the JAR path to it. 340 JavaGram Agile Development 10 Core Reference This chapter specifies the core features of JavaGram. Use this chapter to look up topics once you’ve become familiar with the language. Throughout this chapter, by load time, we mean when a script is parsed. Because a compiled script is pre-parsed, by load time we may also mean compile time. Both these are distinct from runtime, which is when the script is actually executed. 10.1 Comments JavaGram supports two styles of comments, similar to C++ and Java: Anything appearing after // until the end of the line is a comment. Anything appearing between /* and */ is treated as a comment. Such comments can span across multiple lines but cannot be nested. All comments are ignored by the JavaGram parser. 10.2 Identifiers An identifier is an alphanumeric name for something (variables, methods, classes, etc.). JavaGram rules for identifiers are the same as Java. Every identifier must begin with a letter (A-Z or a-z) or an underscore, followed by zero or more letters, underscores, or digits. There is no limit on the length of an identifier. Valid examples are: name firstName first_name _temp value2 Invalid examples include: 1stName total$value switch // cannot start with a digit // contains $ // reserved word (see below) 10.2.1 Reserved Words The following words are reserved in JavaGram and cannot be used as identifiers. Core Reference 341 abstract arg boolean break case catch char class clocal const continue date default delayed do else enum extends false final finally for generated getable goto gui if implements import in instanceof int interface list map mutual native new null package private protected public query real remote return setable singleton slocal sql static stream string super switch symbol synchronized sys this throw throws transaction transient true try typeof vague valueof vector void volatile while Reserved words appearing in italics are not currently in use, but are reserved for future use. 10.2.2 Qualification Identifiers may be qualified using the backslash and dot characters. For example, a class named Client in a domain called crm may be referred to as crm\Client, and a static field in this class called type may be referred to as crm\Client.type. In most cases, you don’t need to qualify an identifier, but you must do so when the short version is ambiguous (i.e., resolves to more than one definition). 10.2.3 Naming Conventions Although not enforced by the language, the following naming conventions are recommended for identifiers. JavaGram libraries strictly follow these conventions. By adopting them, you can improve the consistency of your code, especially when you share your code with other people. All constants (i.e., static final fields) should appear in upper case throughout, and individual words separated by underscores. Examples: PROD_CODE, DEFAULT_VALUE. All variable names (class fields, local variables, method parameters) and method names should appear in camel case (i.e., first word in lower case and subsequent words capitalized). Examples: name, firstName, sendMessage. All class names should have every word capitalized. Examples: Client, CalcEngine. The larger the scope of an identifier, the more meaningful its name should be. Therefore, avoid cryptic class and method names because they have a wide scope (e.g., TransactionCategory or TxnCategory is preferred to TranCat). Conversely, a local 342 JavaGram Agile Development variable limited to a small method or block should have a brief name (e.g., ts is more convenient than timestamp). Established abbreviation and acronyms are perfectly acceptable (e.g., DOB or dob instead of dateOfBirth). Single letter loop variables are perfectly acceptable, provided there isn’t too many of them to cause confusion (e.g., i or j for an integer, or s for a string). Use nouns for class and variable names, but verbs for method names. 10.3 Jag Element A JavaGram script (.jag file) consist of a single jag element, the structure of which is like this: <jag ...properties...> ... </jag> Inside a jag element, you may have zero or more load elements, class definitions, or literal data. As a special case, you can also have a tagless script that just contains a literal, such as a vector. Tagless scripts serve as data files. If a tagless script is loaded, it’s parsed but never analyzed. This situation happens in the JavaGram IDE. Analysis is skipped because the file can’t have <load> elements and therefore can’t resolve any class references. 10.3.1 Jag Element Properties Element properties must conform to the usual markup syntax, where each property is specified like this: name=value Multiple property definitions must be separated by whitespace (not commas). You can specify the following properties for a jag element. All jag property values must be literal constants (expressions are not allowed). Property Name domain Description Specifies the domain of the script. This is similar to Java package names, C++ namespaces, or web domain names, except that backslash or slash is used as the separator instead of dot. A domain name must be specified as a string (e.g., "app\\calc" or "app/calc") and becomes an implicit prefix for every class specified in the script. For example, a class named Foo will have the qualified name app\calc\Foo. Use domains to organize your code into manageable hierarchies, and to Core Reference 343 mjv stage avoid name conflicts across scripts that use similar class names. Specifies the Minimum JavaGram Version as a string (e.g., "1.5"). If a script uses features that are available as of a specific version of JavaGram, use this property to document the dependency. JavaGram will report an error if a script is run with a version of JAG that’s less than that specified by mjv. Specifies the development stage of a script. This must be one of these symbols: $dev (for development), $test (for system test), or $prod (for production.). When not specified, it defaults to $dev. When you run a program, you can use the –stage runtime option to specify the overall development stage of the program (defaults to dev). A program run in $dev stage, accepts scripts in any stage. A program run in $test stage, requires all scripts to be in $test or $prod stage. A program run in $prod stage, requires all scripts to be in $prod stage. Any violation of these rules is reported as error and will terminate execution. side Use this property to track the quality of a script during its development cycle and to avoid, for example, an insufficiently tested script finding its way to production. When you write a new script, set its stage initially to $dev. Once it has been unit tested, set its stage to $test. Finally, when it has been fully system tested and is production-ready, set its stage to $prod. Specifies the intended client-server side of a script. This must be one these symbols: $client, $server, or $both. When not specified, it defaults to $both. The side of a program is determined by how you run it. Programs run using the class jag.run.Env accept all scripts, regardless of their side. Programs run using jag.run.svr.Server cannot run scripts that have $client side. Programs run using jag.gui.Gui cannot run scripts that have $server side. A $client side script is not allowed to run on the server-side. Conversely, a $server side script is not allowed to run on the client-side. Use this property to avoid inadvertent execution of a script on a side that it’s not intended for. Here is an example of using all the above properties. <jag domain="app/calc" mjv="1.5" stage=$test side=$server> 10.4 Load Element Use the load element to load one or more scripts statically. The structure of a load element is like this: <load ...properties...> ... </load> 344 JavaGram Agile Development Inside a load element, you may specify the path of zero or more scripts. Each path must be specified as a string. Separate multiple paths using whitespace (not commas). 10.4.1 Load Element Properties You can specify the following properties for a load element. Load property values may be literal constants or expressions. Property Name relative autoCallMain source Description When true, causes file paths to be treated relative to the runtime root path (defaults to true). This is the recommended setting for JavaGram programs, as it simplifies the task of managing directory names. The client-server mode uses relative paths for scripts downloaded from the server. Set this to false on rare occasions when you want to load a file using its absolute path. A Flash client assumes all paths to be relative. When true, automatically calls the public static void main() method of each class (if any). Defaults to false. Specifies the source of the script(s) to be loaded. Possible values are: $local, $boot, or a stream. The default value for this property depends on how the program is run. For a standalone or a server program, it defaults to $local. For a client program, it defaults to $boot. The latter is equivalent to the stream connecting the client to the server (i.e., the stream through which the client boots itself by downloading scripts from the server). When a non-local source is used, the nominated file is first downloaded from the server denoted by the source and then loaded into JAG. Downloading and loading of a script happens only once, regardless of how many times a load request on it is made, unless the script has been modified since its last load. side It’s rarely necessary to use this property. The default setting is almost always adequate. Specifies the intended client-server side of a load. This must be one these symbols: $client, $server, or $both. When not specified, it defaults to $both. A $client side load is ignored on the server-side. Conversely, a $server side load is ignored on the client-side. Use this property to avoid loading something that’s meaningless for a given side. 10.4.2 Data Types Every value in JavaGram is a member (instance) of some type. For example, a whole number is an instance of the integer type. JavaGram provides strong type checking to ensure that, for example, when a method is called, the argument values match the corresponding parameter types. Type checking is primarily done at load time. Any computation that cannot be fully type checked at load time, is type checked at runtime. Core Reference 345 10.4.2.1 Atomic Types An atomic type is one whose values cannot be further subdivided. JavaGram supports eight atomic types: Boolean (boolean) represents the two values true and false, and is equivalent to the Java type boolean. Character (char) represents ASCII and Unicode characters, and is equivalent to the Java type char. Integer (int) represents whole numbers, and is equivalent to the Java type long. Real (real) represents numbers with a fractional part, and is equivalent to the Java type double. String (string) represents arbitrary sequences of characters, and is equivalent to the Java type java.lang.String. Symbol (symbol) is like string except that multiple instances having the same representation are stored only once. It is used to build unique names, such as keys for a map, and has no direct Java equivalent. Date (date) represents date and time, up to nano seconds, and is equivalent to the Java type java.sql.Timestamp. Stream (stream) represents a mechanism for performing IO with respect to a file, buffer, or channel. It has no direct Java equivalent. 10.4.2.2 Composite Types Composite types are the opposite of atomic types – they are composed of other values. JavaGram supports four composite types: Vector (vector) represents a contiguous sequence of values, which may be atomic or composite themselves, and is equivalent to the Java type java.util.List. Vectors are useful for providing random access to data. Linked list (list) represents a sequence of values, which may be atomic or composite themselves. Unlike vector, a list is not suitable for random access, but is memory efficient for representing relatively small collections. Map (map) represents key-value pairs, where each unique key (must be atomic) is associated with an arbitrary value (may be atomic or composite), and is equivalent to the Java type java.util.Map. The internal map implementation uses Java’s java.util.TreeMap and is therefore sorted in lexicographic key order. Object (object) represents opaque instances of user-defined types, and is equivalent to the Java type java.lang.Object. Vector, lists, and map types are also called container types. Vectors and maps can be parameterized; lists can’t. Here are some examples: 346 JavaGram Agile Development vector<string> vector<vector<int>> map<symbol, real> map<symbol, map<date, vector<string>>> // // // // vector vector map of map of of strings of integer vectors symbols to reals symbols to maps of... JavaGram performs sufficient type checking to ensure that values of these containers conform to the specified nested types. If you don’t parameterize a container then the elements are allowed to take any type, which is to say that: vector is equivalent to vector<vague> map is equivalent to map<vague, vague> 10.4.2.3 User-defined Types Each JavaGram class definition establishes a new type, whose members are instances of that class. Furthermore, all class instances (i.e., objects) can be treated as members of the opaque type object. User-defined types can be extended through inheritance. Unlike Java, JavaGram supports multiple inheritance. 10.4.2.4 Native Type In some cases (such as XML data and GUI elements), a JavaGram value is simply a reference to the underlying Java implementation of the value. JavaGram provides the native type for such values. These values are type-checked at runtime. 10.4.2.5 Vague Type The JavaGram type vague encompasses all types. It is used when a variable can take values belonging to different types. For example, the method sys.round() takes a vague argument, because it accepts both integers and reals. When a value whose type is vague is assigned to a non-vague variable, an explicit type cast is required. 10.4.2.6 Void Type The type void is not really a type, but a pseudo type, and signifies lack of a value. It’s used as the return type of a method that returns nothing. You can’t declare variables of this type. 10.4.2.7 Implicit Type Cast Where meaningful, JavaGram performs an implicit type cast to match the type of a value to the type of the variable (or method parameter) it’s being assigned to. For example, if you assign an integer value to a real variable, the integer is implicitly cast to real before the assignment is performed. JavaGram rules for implicit type casting are as summarized below. From Type int int Core Reference To Type char real 347 char char symbol date int real string int native vague object Any type Any type Any class Any class Any of its base classes 10.4.2.8 Explicit Type Cast If the type of a value you’re assigning to a variable mismatches the variable’s type, you can explicitly cast the value to the desired type before the assignment is performed. For example, int average = (sum / 2.7)@int; converts the result of the division to int before assigning it to average. A type cast is valid if the value can actually be converted to the specified type. Otherwise, it’s reported as an error. Incidentally, the @= operator provides a more succinct notation for expressing the above example: int average @= sum / 2.7; This operator casts its right-hand side to the type required by its left-hand side and then performs the assignment. As a rule, a user-defined class can always be cast to a string, to produce the class’s qualified name. For example, given a class Customer having the domain crm: Customer c; Customer@string c.class@String // gives: "crm\Customer" // gives: "crm\Customer" 10.5 Literals Literals are constant values that are established at load time. JavaGram allows you to define literals for atomic types (except for stream), container types, and even user-defined types. 10.5.1 Atomic Literals 10.5.1.1 Boolean Literals There are only two boolean literals: true and false. 348 JavaGram Agile Development 10.5.1.2 Character Literals A character literal is a single character enclosed in single quotes (e.g., 'a'). Commonly used invisible characters as well as meta characters can be specified using the backslash escape character: '\n' (new line) '\r' (carriage return) '\t' (horizontal tab) '\b' (backspace) '\f' (form feed) '\'' (single quote) '\\' (backslash) Additionally, a character can be specified using its octal code, which may be up to three digits long. For example, '\012' represents the new line character, and '\0' represents the null character. Unicode character literals can be formed using the notation ‘\uxxxx’, where xxxx is a fourdigit hexadecimal value that specifies the Unicode character value. For example, '\u00A9' represents the copyright © character. Escaping any other character using a backslash has no effect. 10.5.1.3 Integer Literals An integer literal consists of one or more digits, and may be signed by placing a + or – before the number. Integers can be specified in one of three notations: Decimal (base 10) notation, where the digits may range from 0-9. For example: 194. Octal (base 8) notation, where the digits may range from 0-7. An octal integer must begin with the digit 0. For example, 020 is equivalent to the decimal value 16. Hexadecimal (base 16) notation, where the digits may range from 0-9 and A-F or a-f. A stands for 10, B for 11, …, and F for 15. A hexadecimal integer must begin with 0x or 0X. For example, 0xF1 is equivalent to the decimal value 241. Small integer literals in the range -128 to 127 are optimized – they are stored once. Therefore, for example, two separate occurrences of 20 in a program are not only equal but also identical (i.e., refer to the same memory location). 10.5.1.4 Real Literals A real literal may be specified in one of two notations: Core Reference 349 Decimal point notation, for example, 3.14. Either side of the decimal point may be empty. So, for example, .14 is the same as 0.14 and 3. is equivalent to 3.0. Scientific notation, for example, 314e-2 or 314E-2. This is interpreted as 314.0 times 10 to the power of -2, which is the same as 314.0 divided by 100. Reals may be signed in the same manner as integers by placing a + or – before them. 10.5.1.5 String Literals A string literal is zero or more characters enclosed in double quotes (e.g., "hello"). The characters inside a string literal may be escaped according to the same rules as a character literal. Additionally, the double quote character itself may be specified as \". A single quote character inside a string literal requires no escaping. 10.5.1.6 Symbol Literals A symbol literal is similar to an identifier, except that it’s always preceded by a $ character (e.g., $name). Symbols that have the same name are internally stored once. This makes symbol comparison easy and efficient. Symbols are especially useful as keys in maps. Unlike identifiers, you can define symbols that have non-alphanumeric characters in them. You can do so by enclosing the name in braces. For example, a symbol having the name task@work can be written as ${task@work}. 10.5.1.7 Date Literals Date literals are used to represent calendar dates, and must appear inside [# and ]. The order of the date components must always be: year, month, day of the month, all expressed as numbers. The parts must be separated using the – or / character. For example: [#2007-12-25] [#2007/12/25] // 25th of December 2007 // 25th of December 2007 A date may optionally contain a time part, expressed in hours:minutes:seconds format. For example: [#2007-12-25 14:35:40] For additional precision (e.g., as in timestamps) the time part may be optionally followed by a nanosecond figure. For example: [#2007-12-25 14:35:40.550] An alternative convention is to express date/time as milliseconds since the UNIX epoch time (number of milliseconds elapsed since midnight of 1st of January 1970). For example: 350 JavaGram Agile Development [#10000] [#10000.50] // is equivalent to [#1970-01-01 00:00:10] // is equivalent to [#1970-01-01 00:00:10.50] 10.5.1.8 Binary Literals A binary literal is the hexadecimal representation of a binary value. Example: [##97A290C89282F87227EB20C2] You don’t generally write a binary literal by hand, but use a method that converts a binary value to this format. The sys.readBin() method, for example, takes a file name as argument and returns the binary representation of its content. JavaGram doesn’t provide a binary type. All binary literals have the type vague and are type checked at runtime. 10.5.1.9 XML Literals An XML literal is of type native and is always enclosed within <# and #>. For example, native xml = <#<person id="1" name="John Smith"><job>Manager</job></person>#>; sys.ppln(xml); produces the following output: <# <?xml version="1.0" encoding="UTF-8" standalone="no"?> <person id="1" name="John Smith"> <job>Manager</job> </person> #> 10.5.2 Composite Literals 10.5.2.1 Vector Literals A vector literal is an ordered sequence of other literals, known as its elements. The elements must appear inside [ and ] and be separated by commas. For example, [2, 3, 5, 7, 11] is a vector of the first five prime numbers. The elements of a vector need not be of the same type. For example, here is a vector of five elements that contains mixed types, including another vector: ["hello", 10, 3.14, $there, [10, 20]] The type of a vector literal is deduced from its elements. For example, Core Reference 351 [10, 20, 30] // has the type vector<int> [10, 20.0, 30] // has the type vector, which is the same as vector<vague> [[$one], [], [$two, $three]] // has the type vector<vector<symbol>> An empty vector literal is specified as []. 10.5.2.2 List Literals A list literal is an ordered sequence of other literals, known as its elements. The elements must appear inside $( and ) and be separated by commas. For example, $(1, 2.0, "three") is a list of three elements. Every list has a head (its first element) and a tail (the rest of the list). The head of the above list is 1 and its tail is $(2.0, "three"). The tail of a list that has only one element is null (which also represents an empty list). When a list literal appears inside another literal (i.e., a vector, list, map, or object), you can start the list with ( instead of $(. For example, in the map [$company=>"Acme”, $locations=>("Chicago", "London", "Sydney")] $locations is specified as a list of three cities. 10.5.2.3 Map Literals A map literal associates a set of unique keys with a set of values. All map keys must be atomic literals, whereas the values can be any literals. Map key/value pairs must appear inside [ and ] and be separated by commas. For example, [$name => "Bill", $dob => [#1977-08-20], $sex => "male"] is a map, where $name, $dob and $sex are the keys. If a key appears more than once in a map, the last instance overrides the earlier ones. The type of a map literal is deduced from its elements. For example, [$name => "Bill", $sex => "male"] [$name => "Bill", $age => 40] [$parts => [20, 25, 70]] // has the type map<symbol, string> // has the type map<symbol, vague> // has the type map<symbol, vector<int>> An empty map is specified as [=>]. 352 JavaGram Agile Development 10.5.2.4 Object Literals Ordinarily, an object is created at runtime using the new operator. An object literal is an instance of a class created at load time rather than runtime. It provides a convenient means of pre-creating objects. For convenience, when you try to print an object (using, for example, sys.print() or sys.pp()) you will get a literal representation of it. This has many advantages: It makes it very easy to see the structure of an object and the value of its individual fields. It can be used as a serialized representation of the object, making it ready for storage in files or databases, or transmission over a network. It makes the object protocol independent, so that it can be reconstructed on a different machine reliably. In fact, JavaGram completely hides the binary representation of all objects. Object literals must conform to this notation: [@ClassName …field mapping…] For example, given the class class Person { string name; date dob; symbol sex; // methods... } The following object literals are all valid: [@Person name => "Alex", dob => [#1972-10-23], sex => $male] [@Person name => "Linda", sex => $female] All unspecified fields (e.g., dob in the second example) are assigned the null value. Object literals can be nested to any depth. Because object literals bypass the normal mechanism of creating an object (i.e., class constructors), they can violate any rules imposed by constructors and get away with it. This can be both advantageous and dangerous, so use it wisely. Core Reference 353 10.6 Expressions An expression is a computation that produces a value. Examples include: literals, use of various operators, method calls, references to objects fields, and type casting. Expressions are formed in a hierarchical manner – simple expressions are combined to produce more complex ones, and so on. In discussing expressions, we sometimes refer to an lvalue. This is something that can be used on the left-hand side of an assignment (i.e., something that can be assigned to). Examples include: a variable, an object field reference, a vector element reference, a map element reference. 10.6.1 Unary Operators 10.6.1.1 + Operator The unary + operator is redundant, but is included for completeness. It may appear before any numeric expression and has no effect on the expression – JavaGram simply ignores it. Examples: +10 +sum 10.6.1.2 – Operator The unary - operator negates a numeric expression. The operand must be of type char, int, or real. For example: -'A' -x -(x + y) // gives -65, where 65 is the ASCII code of 'A' // assuming that x is 10.2, gives -10.2 // assuming that y is 5, gives -15.2 10.6.1.3 ++ Operator The auto increment operator expects a char or int lvalue operand, and increments the operand by one. The operator may appear before the operand (prefix) or after it (postfix). In its prefix form, the operand is incremented and then returned. In its postfix form, the operand is returned and then incremented. Examples: ++10 ++n n++ ++c // // // // invalid (not an assuming that n assuming that n assuming that c lvalue) is 5, n becomes 6 and it returns 6 is 6, n becomes 7 and it returns 6 is 'A', returns 'B' 10.6.1.4 -- Operator The auto decrement operator is like the auto increment operator, but decrements the operand by one. Examples: --10 --n 354 // invalid (not an lvalue) // assuming that n is 5, n becomes 4 and it returns 4 JavaGram Agile Development n-- // assuming that n is 4, n becomes 3 and it returns 4 10.6.1.5 ! Operator The logical negate operator expects a boolean operand and reverses its value. Examples: !correct // assuming that correct is true, gives false !(10 > 20) // gives true !10 // invalid (not a boolean operand) 10.6.1.6 ~ Operator The bitwise negate operator toggles the bits in its integer operand. Because the left-most bit of an integer is its sign bit, you’ll notice that bitwise negation of a positive integer will produce a negative one. However, this is not significant because this operator is typically used when an integer is treated as a sequence of bits rather than a numeric value. Examples: ~0xFF ~x ~10.2 // gives -256 // assuming that x is 500, gives -501 // invalid (not an integer operand) 10.6.1.7 Typeof() Operator The typeof() operator can be used to obtain a symbolic representation of an expression’s type. Examples: typeof(10 + 2.1) typeof(10 + false) typeof(new Exception("")) // gives $real // gives $string // gives $Exception This operator is useful when you want to store a type somewhere (e.g., use it as a key in a map). To find out if an expression is of a certain type, use the instanceof operator instead (described below). Please note that when typeof is used on a class or class instance, it returns a symbol which denotes the fully qualified class name. For example, if a class Person has the domain crm\bo then: typeof(new Person()) typeof(Person) // gives ${crm\bo\Person} // gives ${crm\bo\Person} Incidentally, this is similar to using .symbol on the class itself: Person.symbol // gives ${crm\bo\Person} 10.6.1.8 Valueof() Operator The valueof() operator is useful for ensuring that an expression returns a non-null value. For example, assuming that foo() has a return type of int: Core Reference 355 valueof(foo()) valueof(foo(), 10) // gives the return value of foo(), or 0 if it returns null // gives the return value of foo(), or 10 if it returns null When present, the second operand acts an explicit default value. Otherwise, the default value is implicit and determined from the type of the first operand, as summarized below. First Operand Type Implicit Default Value int real string char boolean Any other type 0 0.0 "" '\0' false null 10.6.1.9 Arg() Operator The arg() operator can be used inside any method, according to two rules: arg(null) and arg(-1) both return the number of arguments passed to the method. arg(i) returns the value of an argument, where i denotes the zero-based position of the argument. This operator is useful for methods that take a variable number of arguments, and can be used to retrieve the actual argument values. 10.6.2 Binary Operators 10.6.2.1 = and @= Operators The = (assignment) operator is used for assigning the value of an expression (right operand) to an lvalue (left operand). Examples: r = 20 * 3 // assuming that r is of type real, assigns 60.0 to r v[2] = $ok // stores $ok in the 3rd slot of vector v m[$one] = 1 // stores 1 against the key $one in the map m The right value is implicitly type cast to the lvalue’s type if necessary and permissible. Furthermore, an instance (object) of a class x can be assigned to a variable that is of type object, vague, or any of x’s super classes. No conversion is performed in such cases, as it’s unnecessary. Where an implicit type cast is insufficient, you must use explicit casting to make the types compatible. For example: string s = 2.1 @ string; 356 JavaGram Agile Development Alternatively, you can use the @= operator. This operator casts its right operand to the type of its left operand before performing the assignment. For example: string s @= 2.1; 10.6.2.2 + and += Operators The + operator takes a wide range of operand types and its semantics is tied to the operand types. Its most common use is for adding numeric values. If both operands are of type int or char then the result will be an integer. If at least one of the operands is a real then the result will be a real. Examples: 10 + 2 'A' + 2 2 + 2.3 // gives 12 // gives 67 // gives 4.3 Adding a number to a string, symbol, or boolean is treated as string concatenation. In fact, any combinations of these can be concatenated, except for two booleans. Concatenation of two symbols will result in a symbol rather than a string. Examples: 10 + false true + "one" false + $one "one" + $two $one + $two true + false // // // // // // gives "10false" gives "trueone" gives "false$one" gives "one$two" gives $onetwo (result is a symbol, not a string) invalid (two booleans can’t be added) An integer added to a date adds a number of days. Examples: [#2010-04-16] + 39 // gives [#2010-05-25] You can use the + operator to concatenate two lists, two vectors, or two maps. The operands are not affected; a new container is created to contain the overall result. Examples: $(10) + $(20, 30) // gives $(10, 20 , 30) [$one] + [$two, $three] // gives [$one, $two, $three] [$one=>1] + [$one=>2.0, $two=>3] // gives [$one => 2.0, $two => 3] The returned value’s type is deduced from the operands. For example, the concatenated vector in the above example will have the type vector<symbol> and the concatenated map will have the type map<symbol, vague>. The += operator causes the two operands to be added first and the result assigned to the left operand. In other words, x += y Core Reference // is equivalent to x = x + y 357 except that the shorter version is optimized. For example, applying it to two vectors causes the right vector to be appended to the end of the left vector (no intermediate vector is produced). Similarly, applying it to two maps causes the right map to be added to the left map without producing an intermediate map. Of course, for += to be valid, the type of the left operand must be capable of receiving the resulting value. Example: n += 2.3 // invalid (assuming that n is of type int) Finally, += can be used for adding a GUI element to a container (see Chapter 11). 10.6.2.3 – and –= Operators The - operator is commonly used is for subtracting numeric values. If both operands are of type int or char then the result will be an integer. If at least one of the operands is a real then the result will be a real. Examples: 10 - 2 'A' - 2 2 - 2.3 // gives 8 // gives 63 // gives -0.3 An integer subtracted from a date subtracts a number of days. Subtracting two dates yields the number of days between them, ignoring any specified time of day. Examples: [#2011-01-01] - 7 [#2010-04-16 23:02:59] - [#2010-04-15] // gives [#2010-12-25] // gives 1 You can use the - operator on two vectors or two maps. The operands are not affected; a new container is created to contain the elements of the first operand, excluding those in the second operand. Examples: [$one, $two, $one] - [$one] [$one=>1, $two=>2.0] - [$one=>10, $three=>30] // gives [$two] // gives [$two=>2.0] The returned value’s type is deduced from the operands. The -= operator causes a subtraction followed by an assignment. In other words, x -= y // is equivalent to x = x - y except that the shorter version is optimized. For example, applying it to two vectors causes the right vector’s elements to be removed from the left vector (no intermediate vector is produced). Finally, -= can be used for removing a GUI element from a container (see Chapter 11). 358 JavaGram Agile Development 10.6.2.4 * and *= Operators The * operator is used for multiplying numeric values (integers, characters, reals). When both operands are characters or integers, the result will be an integer. Otherwise, it will be a real. Examples: 2 * 10 2 * 'a' 2 * 5.5 // gives 20 // gives 130 // gives 11.0 The *= operator causes a multiplication followed by an assignment: x *= y // is equivalent to x = x * y 10.6.2.5 / and /= Operators The / operator is used for dividing numeric values (integers, characters, reals). When both operands are characters or integers, the result will be an integer. Otherwise, it will be a real. Examples: 10 / 3 'a' / 2 10.0 / 4 // gives 3 // gives 32 // gives 2.5 Division by zero is considered illegal and will result in an exception. The /= operator causes a division followed by an assignment: x /= y // is equivalent to x = x / y 10.6.2.6 % and %= Operators The % operator is used for getting the remainder of dividing integers or characters. Examples: 10 % 3 10 % 4 'a' % 2 // gives 1 // gives 2 // gives 1 Division by zero is considered illegal and will result in an exception. The %= operator causes a remainder followed by an assignment: x %= y // is equivalent to x = x % y 10.6.2.7 Equality (==, ?=, !=, ===, !==) Operators These operators allow you to compare two values for equality (==), guarded equality (?=), inequality (!=), identicity (===), or non-identicity (!==). Unlike Java (which requires the type of the operands to match), you can use these operators on any values. Examples: Core Reference 359 'a' == 64 X ?= 10 10 != "ten" 10 === 10 10 === 10.0 "man" == "man" "man" === "man" $man === $man // // // // // // // // gives true is equivalent to: x == null || x == 10 gives true gives true (because 10 is in the range -128 to 127) gives false gives true gives false (have different addresses) gives true (symbols are stored only once) The basic rule to remember is that identicity means that two values have exactly the same memory location. 10.6.2.8 Relational (<, <=, >, >=) Operators These operators work on a variety of operand types and can be used to compare: Two numeric values (integers, characters, reals) Two strings Two symbols Two dates For strings and symbols, the comparison is lexicographic and case sensitive. Examples: 10 <= 10.2 'a' > 100 "apple" < "pear" $apple < $pear [#2007-1-1] > [#2006-12-30] // // // // // gives gives gives gives gives true false true true true 10.6.2.9 Logical (&&, ||) Operators The && (logical and) and || (logical or) operators take two boolean operands and return a boolean result. && returns true if and only if both operands evaluate to true. || returns true if at least one of the operands evaluates to true. Examples: n > 0 && n < 10 n == 5 || n < 0 n > 5 && n // gives true when n is 5, and false when it’s 10 // gives true when n is -2, and false when it’s 1 // invalid (both operands must be boolean) 10.6.2.10 Bitwise (&, &=, |, |=, ^, ^=) Operators The & (bitwise and), | (bitwise or), and ^ (bitwise xor) operators take two integer operands and return an integer result whose individuals bits are set according to these rules: & sets a bit to 1 if both corresponding bits are 1. | sets a bit to 1 if either of the corresponding bits is 1. ^ sets a bit to 1 if one of the corresponding bits is 1, but not both. 360 JavaGram Agile Development Examples: 0x70 & 0x10 0x70 | 0x10 0x70 ^ 0x10 // gives 16 // gives 112 // gives 96 The assignment versions of these operators have the usual meaning: x &= y x |= y x ^= y // is equivalent to x = x & y // is equivalent to x = x | y // is equivalent to x = x ^ y 10.6.2.11 Bitwise Shift (<<, <<=, >>, >>=) Operators The << (bitwise left shift) operator takes two integers operands and returns an integer that’s the result of left-shifting the bits in the left operand by the number of positions denoted by the right operand. The >> (bitwise right shift) operator does the opposite. Examples: 2 << 3 100 >> 2 // gives 16 // gives 25 The assignment versions of these operators have the usual meaning: x <<= y x >>= y // is equivalent to x = x << y // is equivalent to x = x >> y 10.6.2.12 List/vector/map access Operator The square brackets [] operator is used for accessing the contents of lists, vectors, and maps. Examples: digits[0] = "zero"; // Update the first element of digits vector age[$jack] = 21; // Store age of Jack in age map With lists and vectors, the operator expects that the element exists – using an out of range index will result in an error. With maps, the key need not be in the map, in which case, null is returned. 10.6.2.13 Object access (. and ?.) Operators The dot (.) operator is used for accessing the methods and fields of an object or class, as well as the properties of a GUI element. prod.price = 500; prod.addToCatalog(); int n = Product.MAX_ID; table.autoSize = true; Core Reference // // // // Update an object's field Invoke an object's method Access a static field of a class Set a GUI element's property 361 You can also use the dot operator to look up a map, provided the key is a symbol. This is equivalent to the use of the [] operator. Example: price.$bread = 3.15; // Store data in price map real cost = quantity * price.$bread; // Look up price map The guarded dot (?.) operator can be used for accessing object methods and fields or map elements without having to check that the object or map is non-null. For example, if (prod != null) prod.addToCatalog(); can be more elegantly written as prod?.addToCatalog(); If prod evaluates to null then the method is not called (if the method has a return type, null is returned). If you use this operator to access a field and the object happens to be null, you’ll get null. You can also use this notation in an lvalue. The effect when the object or map is null is that the assignment is not performed, but the right-hand value is still returned. Example: // 500 is added to cost even when prod is null: cost += prod?.price = 500; // prod as an object cost += prod?.$price = 500; // prod as a map The dot operator can also be used directly on a class name to access its static members. In this case, the following may also be used after the dot: symbol (e.g., Customer.symbol) gives the class name as a symbol (e.g., $Customer). singleton (e.g., CRM.singleton) gives the single instance of the class, provided the class is defined as a singleton class. Finally, the syntax obj.class gives the class of obj. For example, if p is of type Product then p.class@string gives "Product". 10.6.2.14 @ Operator The cast operator @ is used to convert a value from one type to another, provided the conversion is permissible. Examples: 10 @ real 10.0 @ int 10 @ string "10" @ int sys.date() @ int Customer@string 362 // // // // // // gives 10.0 gives 10 gives "10" invalid: no conversion from string to int converts a date to its numeric timestamp gives "Customer" JavaGram Agile Development A common use of @ is for converting an object to a user-defined type: object obj; //... Customer cust = obj @ Customer; This conversion is checked for validity at runtime because the actual value of obj may not be known in advance. You can also use @ after a pseudo class method name. Example: map m = sql.getRow @ map(rs); In this case, the cast instructs sql.getRow() to return a map. Finally, you can cast an expression of type native or vague to a GUI element for the purpose of accessing its properties. Example: panel@<Panel>.title = "Personal Details"; 10.6.2.15 Instanceof Operator The instanceof operator is used to check if a value is of a given type. Examples: 10 instanceof int // gives true obj instanceof Customer // gives true if obj is a Customer class instance 10.6.3 Ternary Operators 10.6.3.1 Conditional Expressions A conditional expression has three parts: a boolean condition, a ‘then’ expression and an ‘else’ expression. If the condition evaluates to true, the ‘then’ expression is evaluated and returned. Otherwise, the ‘else’ expression is evaluated and returned. Example: n < 0 ? $negative : $positive // returns $positive when n is zero 10.6.3.2 Asynchronous Method Invocation An asynchronous method invocation allows you to call a method such that you don’t have to wait for it to complete. Execution proceeds to the next statement as soon as the call is made. When the call eventually completes, a callback is invoked to complete the processing. When the async method throws an exception: If an error callback is also specified, it will be invoked instead. If no error callback is specified, the main callback is still invoked and will be responsible for exception handling. Core Reference 363 In either case, you can use sys.getAsyncErr() to get hold of the exception. An asynchronous method invocation may take one of the following three forms: MethodCall -> Callback -> ErrCallback lvalue = MethodCall -> Callback -> ErrCallback Type var = MethodCall -> Callback -> ErrCallback where ErrCallback is optional. A callback can be any valid expression or statement, or even a block. Example: vector result = crm.search(criteria) -> viewer.show(result) -> viewer.error("search failed"); For the purpose of the error callback, you can use the sys.getAsyncErr() method to retrieve the exception. JavaGram executes each asynchronous method call in a separate thread. If the call is to a remote method then a separate thread on the client-side handles the processing of all callbacks. All this is handled transparently to the programmer. You can also use a guarded version of the asynchronous call syntax to code for both synchronous and asynchronous clients. For example: User u = login() ?? sys.async -> setUser(u) -> failed(sys.getAsyncErr()); or User u = login() ?> sys.async -> setUser(u) -> failed(sys.getAsyncErr()); The condition appearing after ?? or ?> must be a boolean expression. When this condition evaluates to true, the call is performed as before. When this condition evaluates to false, login() is called synchronously and no callback is invoked, unless ?> is used, in which case the callback(s) are still performed but synchronously. Because Flash is single-threaded, the following restrictions apply to a Flash client: All remote calls in a Flash client must be asynchronous because Flash cannot block on a call (calling a remote method synchronously will result in a runtime error). Non-remote asynchronous calls are always handled synchronously (due to lack of multi-threading). When login() is a remote call, it is always called asynchronously and the callback(s) are used, regardless of whether the guard condition evaluates to true or false. 364 JavaGram Agile Development 10.6.4 N-ary Operators 10.6.4.1 Vector Operator The vector operator (same name as the type) allows you to dynamically create a vector. It takes zero or more operands, each of which may be an arbitrary expression, and returns a vector each of whose elements is the result of evaluating the expressions. For example, vector vec = vector(10, 100 + "dollars", 100 / 2.4); sets vec to [10, "100dollars", 41.66666666666667]. The type of the resulting vector is deduced from the operand types. For example, if all elements are integers then the vector will have the type vector<int>. The distinction between a vector literal and a vector created using the vector operator is very significant. For example, the above vector is created each time the line of code is executed, whereas the vector literal [1, 2, 3] is created only once at load time. 10.6.4.2 List Operator The list operator (same name as the type) allows you to dynamically create a list. It takes zero or more operands, each of which may be an arbitrary expression, and returns a list each of whose elements is the result of evaluating the expressions. For example, list lst = list(10, 100 + "dollars", 100 / 2.4); sets vec to $(10, "100dollars", 41.66666666666667). The distinction between a list literal and a list created using the list operator is very significant. For example, the above list is created each time the line of code is executed, whereas the list literal $(1, 2, 3) is created only once at load time. 10.6.4.3 Map Operator The map operator (same name as the type) allows you to dynamically create a map. It takes zero or more pairs of operands, each of which may be an arbitrary expression, and returns a map each of whose key/value pairs is the result of evaluating the expressions. For example, map m = map(1 => "one", 2 + 3 => "two"); sets m to [1 => "one", 5 => "two"]. The type of the resulting map is deduced from the operand types. For example, the above map will have the type map<int, string>, Core Reference 365 The distinction between a map literal and a map created using the map operator is very significant. For example, the above map is created each time the line of code is executed, whereas the map literal [1 => "one", 2 => "two"] is created only once at load time. As with map literals, all key expressions must evaluate to atomic values. 10.6.4.4 New Operator The new operator is used to create an instance of a class. Its operands must follow the same syntax as a method call for a class constructor. If the class has no constructor then a call to an implicit constructor with no argument must be made. For example, Customer c = new Customer("John", 101); creates an instance of the Customer class and invokes its constructor that takes a string and an integer as arguments, whereas Record r = new Record(); creates an instance of the class Record and invokes its parameter-less constructor if it has one. The rules for invoking constructors are specified in the sections on classes (see below). Like Java, JavaGram allows you to anonymously subclass a class when using the new operator. This is typically used to override one or more class methods purely for the benefit of the created object. Example: Customer c = new Customer("John", 101) { // Override the Customer.format() method: public string format () { //... } }; 10.6.4.5 Object Operator The object operator provides a more direct way of creating object instances. Like object literals, this method bypasses class constructors and allows you to specify the values for class fields directly. For example, let’s say you have a class called Person which has a name and a dob (date of birth) field. You can create an instance of Person like this: Person p = object(Person, name=>"John", dob=>[1982-10-22]); Like vectors and map, the difference between an object created this way (or using new) and an object literal is significant. The latter is created once at load time, whereas the former is created each time the code is executed. 366 JavaGram Agile Development 10.6.5 Operator Precedence When multiple operators are used in the same expression, it’s important to be sure of the order in which the operators are evaluated. The following table summarizes the order. The higher an operator in the table, the higher is its precedence (i.e., it’s performed first). For those operators in the same row (i.e., having the same precedence) evaluation is always from left to right. Operator Precedence (high to low) @, ??, ?>, ->, typeof, arg, vector, list, map, object, new [] ++, --, unary +, unary -, ~, ! *, /, % binary +, binary <<, >> <, <=, >, >=, instanceof ==, !=, ===, !=== & ^ | && || ? : =, @=, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |= () You can always use parentheses to override the precedence of operators. For example, in x + y * z if the intent is to add x and y and then multiply by z then this should be written as (x + y) * z. 10.6.6 Delayed Strings A common programming pattern is to concatenate strings with other values (such as numbers) to produce formatted strings, especially for output purposes. For example, sys.println("found " + n + " records in " + s + " seconds"); would produce something like the following output: found 2000 records in 0.05 seconds This line of code can be more elegantly written using a delayed string: sys.println($"found {n} records in {s} seconds"); The $ preceding the string indicates that it’s a delayed string. Unlike normal strings which are created at load time, a delayed string is created at runtime. Any occurrence of {} in a Core Reference 367 delayed string must contain a valid expression. The expression can be as complex as you like, but must not be empty. When the delayed string is evaluated, these expressions are evaluated from left to right and then spliced into the string to produce the final value. 10.6.7 Method Invocation A call to a method that has a non-void return type is a valid expression. Example: int n = sys.min(x, y); Whether a method can be called or not, depends on its visibility and the scope from which it’s called. These considerations are discussed in the sections on classes (see below). 10.7 Statements A statement is a structured piece of computation. Examples include: if statement, loops, and throwing an exception. Like expressions, statements are formed in a hierarchical manner by combining simpler statements to form more complex ones. Whereas the aim of an expression is to produce a value, the purpose of a statement is to cause a side-effect, such as changing the value of a variable, producing output, or modifying an object. The simplest statement is a semicolon on its own, denoting an empty statement. This can be useful in some situations, such as writing a loop that has an empty body. 10.7.1 Expression Statement Any expression followed by a semicolon is a valid statement. Examples: 10; x + 10; ++x; // useless (no side-effect) // useless (no side-effect) // useful (increments x by 1) 10.7.2 Blocks A block allows you to group multiple statements such that the whole thing can be treated as a single statement. Additionally, a block introduces a new scope, allowing you to define local variables that override variables defined in parent scopes. All the statements in a block must be enclosed by a pair of braces. Blocks can be nested to any depth. Example: { int n = 10; { int n = 20; sys.println(n); } sys.println(n); } 368 JavaGram Agile Development This will produce the following output: 20 10 It’s worth noting that JavaGram differs from Java in this respect. Java will not allow the re-declaration of the variable n in the inner scope. 10.7.3 If Statement The if statement is used for writing conditional logic. Example: if (n >= 0) sys.println("n is positive"); The condition must always appear in a pair of parentheses and evaluate to a boolean value. If the condition evaluates to true then the statement following the condition is performed. An optional else part can be included to define a statement to be performed when the condition evaluates to false. Example: if (n >= 0) sys.println("n is positive"); else sys.println("n is negative"); More complex cases can be written by combining multiple if statements. Example: if (n == 0) sys.println("n is zero"); else if (n > 0) sys.println("n is positive"); else sys.println("n is negative"); 10.7.4 While Loop The while loop is used to repeatedly execute a statement (loop body) while a condition holds true. Example: int i = 0, sum = 0; while (i < 10) sum += i++; The condition following while must appear in parentheses and evaluate to a boolean value. The statement following the condition is executed and the process is repeated until the condition evaluates to false. Core Reference 369 10.7.5 Do Loop The do loop is used to repeatedly execute a statement while a condition holds true. Example: int i = 0, sum = 0; do sum += i++; while (i < 10); The key difference between a while loop and a do loop is that in the former the condition is evaluated before the loop body is executed, whereas in the latter the loop body is executed before the condition is evaluated. As a result, the body of a do loop is always executed at least once. 10.7.6 For Loop The for loop is typically used to iterate a loop counter through successive values. Example: int sum = 0; for (int i = 0; i < 10; ++i) sum += i; A pair of parentheses must appear after for and contain three things: initializer, loop condition, and incrementor. These must be separated by semicolons. Any of these can be empty. Therefore, a minimal for loop will look like this: for (;;) { // loop body } Because there is no condition in this loop, it will iterate indefinitely, unless a break or return inside the loop body terminates it. for loops are very flexible. You can declare local loop variables (e.g., i in the above example), have multiple loop counters, and multiple parts in the incrementor. If you use a local loop variable, its scope is limited to the loop. Therefore, i is not defined outside the above loop. Another example: int sum = 0; vector<int> v = [10, 29, 20, 100]; for (int i = 0, n = sys.length(v); i < n; ++i) sum += v[i]; Although loop variables such as i and n need not be defined locally (they can be defined before the loop), in most cases locally defined variables are preferred. 370 JavaGram Agile Development 10.7.7 For-in Loop A for-in loop is used for iterating through the elements of a collection. Although you can do the same using other loop types, a for-in loop provides a more succinct notation for this purpose. Example: int sum = 0; vector<int> v = [10, 29, 20, 100]; for (int n in v) sum += n; The loop variable (e.g., n above) must be local to the loop, and the expression appearing after in must evaluate to a vector, map, or GUI container. Each time round the loop, n is set to the next value in the vector v. If the container is a map then the loop variable iterates through the map keys (not values). Example: real sum = 0; map<symbol, real> price = [$apple=>3.5, $orange=> 2.4, $pear=>1.9]; for (symbol fruit in price) sum += price[fruit]; 10.7.8 Break Statement The break statement is used to terminate the flow of control in a loop or in a switch statement, causing the flow to be transferred to after the loop or switch. Example: while (true) { string s = sys.strTrim(sys.readln()); if (s == "quit") break; sys.println(s); } 10.7.9 Continue Statement The continue statement is used in a loop to transfer the flow of control to the loop condition. Example: while (true) { string s = sys.strTrim(sys.readln()); if (sys.length(s) == 0) continue; if (s == "quit") break; sys.println(s); } Core Reference 371 10.7.10 Return Statement The return statement is used in a method to transfer flow of control to the method caller. If the method has a non-void return type then the return statement must return a value of the same type. Example: public static real average (vector v) { real sum = 0; int count = 0; for (vague n in v) { if (n instanceof int || n instanceof real) { sum += n @ real; ++count; } } return sum / count; } 10.7.11 Switch Statement The switch statement is used to select amongst a number of possible execution paths based on the value of an expression. Example: switch (cmd) { case $send: send(data); break; case $receive: receive(buf); break; case $ack: acknowledge(); break; default: sys.println($"invalid command {cmd}"); break; } The expression immediately appearing after switch is called the selector and must be in parentheses and must evaluate to an integer, character, or symbol. The switch may contain zero or more cases, each starting with the keyword case, followed by a literal (or static field) that has the same type as the selector. An optional default clause will act as a catch-all. The selector value is compared against each case and if a match is found, the statements following the case are executed. The last statement of each case is usually a break (or return). Otherwise, execution will continue to the next case, and so on. If no match is found and a default clause is specified, it’s exercised. 372 JavaGram Agile Development 10.7.12 Throw Statement The throw statement is used to raise an exception; the latter must be an instance of the predefined Exception class or a subclass of it. Example: class SearchException extends Exception {} //... throw new SearchException(); 10.7.13 Try Statement The try statement is used for catching and handling potential exceptions. Example: real sum = 0; vector v = [1, "two", 3.0]; try { for (vague n in v) { if (n instanceof int || n instanceof real) sum += n @ real; else throw new Exception($"number expected: {n}"); } sys.println(sum); } catch (Exception e) { sys.println($"Error: {e.getMessage()}"); } Note that the try block will catch exceptions thrown either directly inside the block (as in the above example) or indirectly by any of the methods called in the block. One or more catch clauses must appear after the try block. Multiple catch blocks allow you to catch different types of exceptions. The order of catch clauses is significant – when an exception is raised in the try block, the exception type is compared against the exception types in the catch clauses in the order they appear. As soon as a match is found, that clause is exercised. Therefore, you should list your catch clauses in the order of specific to generic. If you have a catch clause for catching Exception, it should appear last, because it’s the most generic and will catch anything. A try statement may be optionally followed by a finally block. This block is always executed after the try block, regardless or whether any exception is thrown or not. It’s also permissible to have a try block with a finally block and no intermediate catch blocks. Example: stream s = null; try { s = sys.open(filePath, "r"); string str; while ((str = sys.readln(s)) != null) Core Reference 373 sys.print(str); sys.flush(); } finally { sys?.close(s); } 10.7.14 Synchronized Statement The synchronized statement is used to ensure that only one thread at a time can execute a section of code. Example: synchronized ($mutex) { //... } The keyword synchronized must be followed by a pair of parentheses that contain an expression. This can be any expression of any type, even a literal, and is used as the lock object. However, you should be careful about choosing something appropriate, so that it remains the same for multiple threads attempting to execute the synchronized code section. In the above example, we’ve used a symbol (which is guaranteed to be a singleton). You may also use class field members created specifically for this purpose. When a thread attempts to execute a synchronized section, it first attempts to obtain a lock on the specified object. After exiting the block, the thread will release the lock. The lock ensures that any other thread attempting to execute the section will block until the locking thread has released the lock. 10.7.15 Query Statement The query statement is used to allocate a database connection to the thread as a section of code is executed. Example: query ($ordersDb) { //... } The keyword query must be followed by a pair of parentheses that contain an expression. This expression can be an explicitly created connection but is more generally a reference to a server configuration file entry detailing database connection parameters. Example: $database => [ $ordersDb => [ $name => "Orders", $url => " jdbc:oracle:thin:@uklondon02:1521:ORDERS", $user => "batch", $password => "z$iH;V", $driver => " oracle.jdbc.OracleDriver" ] ] 374 JavaGram Agile Development Where a database configuration specifies database connection pooling, the query block allocates a connection from the pool and de-allocation returns the connection to the pool, making it available to other threads. Where pooling is not specified, allocation and deallocation imply opening and closing the connection. After exiting the block, any statements or result sets that have been created within the block and left open are closed. Finally, the database connection is de-allocated. 10.7.16 Transaction Statement The transaction statement behaves like the query statement but, in addition, groups a series of SQL statements into a single transaction. Example: transaction ($ordersDb) { //... } As control flow causes the thread to leave the block, the underlying database transaction is committed. Should an exception be thrown within the block, a rollback is performed and the pending database changes are not committed. Should any of the enclosed SQL statements or procedures they call issue a commit, rollback or other transaction control statements, the behavior is undefined. 10.7.17 Assert Statement The assert statement is used for documenting and verifying invariants. An invariant is a boolean condition that must hold true. For example, a method that inserts a value into a collection expects the collection not to be null: void insertProduct (vector<Product> products, Product product) { assert products != null; //... } During execution, the assert condition is evaluated and if the result is false, an error is raised. 10.8 Classes The syntax and semantics for JavaGram class definition and access closely parallel those of Java, thus enabling Java programmers to become productive in JavaGram very quickly. Key differences with Java are: A class name need not match the file name in which it appears. Multiple class definitions may appear in the same file. Core Reference 375 JavaGram classes may not be nested. JavaGram supports multiple inheritance through the use of mutual classes. There is no notion of an interface in JavaGram. Abstract classes are used to represent the same. JavaGram classes may contain local and remote methods. Remote calls are managed transparently. JavaGram supports the notion of local and remote classes. Remote class objects are managed through proxy objects in a manner that’s transparent to the programmer. In addition to field and method members, you can define text and GUI members for a JavaGram class. A text member behaves like a method and allows you to do advanced text processing. A GUI member behaves like a field and allows you to define user interface components declaratively and hierarchically. A class itself can be treated as a value, and therefore assigned to variables or passed to methods. You must use the type vague for this purpose. Example: vague klass = Person; // is valid provided Person is a defined class 10.8.1 Class qualifiers You can specify the following qualifiers by placing the keyword before a class definition: An abstract class is one that can’t be instantiated, usually because it has abstract methods whose actual definitions are deferred to subclasses. However, an abstract class does not have to contain abstract methods. A final class is one that can’t be subclassed, for example, as a security measure. A clocal class is one that’s intended to run on the client side. JavaGram prevents such classes from being executed on the server side. This qualifier has no effect in standalone deployment. An slocal class is one that’s intended to run on the server side. JavaGram prevents such classes from being executed on the client side. This qualifier has no effect in standalone deployment. A remote class is one whose objects will always reside on the server side, but can be accessed from the client side. Such access is through proxy objects that are managed by JavaGram. This qualifier has no effect in standalone deployment. A singleton class is one that may have a maximum of one instance. A mutual class is designed with the intention of use for multiple inheritance. By definition, all the super classes of a mutual class must also be mutual. You can define a class that has multiple direct super classes, provided at most one of them is nonmutual. A generated class is one that has been generated by a Wizard. 376 JavaGram Agile Development Unlike Java, you don’t specify any visibility for a class – all classes are assumed to be public. 10.8.2 Class Properties Class properties are read-only and can be accessed using the Class.Property notation. The following properties are defined: The symbol property returns the fully qualified class name as a symbol. The singleton property is only defined for singleton classes. It returns the one and only instance of the class. 10.8.3 Fields A class may contain any number of field definitions, which represent the class data. Each field is defined as a typed variable. For example, class Car { string make; string model; int year; } defines a class that has three fields. 10.8.3.1 Field Qualifiers A field may have the following qualifiers: A public field is universally visible (and hence accessible). A protected field is only visible to the class and all its subclasses. A private field is only visible to the class itself. A static field is shared across all class instances. You can think of a static field as a global variable that’s separate from class instances, but can only be accessed by them. A final field must have a defined value and this value can’t be modified. A getable field signifies that JavaGram will automatically generate a ‘get’ method for it, unless one has been explicitly defined by the programmer (e.g., getName() for a field called name). A setable field signifies that JavaGram will automatically generate a ‘set’ (as well as a ‘get’) method for it, unless one has been explicitly defined by the programmer (e.g., setName(...) for a field called name). A delayed field is one that is initialized after the class constructor (if any) has completed execution (ordinarily, class fields are initialized before any constructor is invoked). A delayed field cannot be static. Core Reference 377 A generated field is one that has been generated by a Wizard. A transient field is not transmitted between client and server when a non-static remote method of a class is invoked (null is transmitted instead for such fields and is ignored on the receiving end). Use this qualifier to improve client-server communication efficiency by excluding potentially large fields (e.g., house-keeping data) that are not persistent. Note, however, that if a class instance is passed as a remote method argument or returned by a remote method, it is fully transmitted, including its transient fields! You can use a combination of these qualifiers to define a field, so long as the combination is meaningful. Examples: public static final int SIZE = 128; // a public constant public private real r = 1.0; // invalid: can’t be public and private A field whose visibility is not explicitly defined, has domain visibility – it’s visible to all other classes in the same domain. 10.8.3.2 Field Initialization As shown above, you can initialize a field using a compatible value, in the same manner you would initialize a local variable. For static fields, the initialization is performed when the field is accessed for the first time. For non-static fields, initialization is performed when a class instance is created, but before the class constructor (if any) is executed, unless the field is delayed, in which case it’s initialized afterwards. Any uninitialized fields are automatically set to null. When initializing a field, you can refer to other static fields, because these are accessible prior to object creation. Example: static final int WIDTH = 100; // ok static final int HEIGHT = WIDTH * 2; // ok int area = WIDTH * HEIGHT; // ok real factor = area / 10.0; // invalid: refers to non-static area Car car = new Car(); // ok When initializing a field, you can use the = or the @= operator. The latter casts the initialization expression to the type of the field before performing the assignment. Example: map<symbol, real> prices @= map(); 10.8.4 Methods The behavior of a class is defined by its methods. These represent things that you can do to a class instance. Example: class Car { 378 JavaGram Agile Development protected string make; protected string model; protected int year; public Car (string make, string model, int year) { this.make = make; this.model = model; this.year = year; } public string format () { return $"{make}, {model}, {year}"; } } 10.8.4.1 Method Qualifiers A method may have the following qualifiers: A public method is universally visible (and hence accessible). A protected method is only visible to the class and all its subclasses. A private method is only visible to the class itself. A static method can be invoked directly on the class, without having an instance of it. Within a static method, you don’t have access to the implicit class instance (i.e., this is not defined). Likewise, you don’t have access to non-static class fields. However, you can access static fields. A final method is one that can’t be overridden by a subclass. An abstract method has no definition – its definition is deferred to subclasses. A synchronized method is one whose entire body is implicitly synchronized by locking the object instance. Therefore, a synchronized method can’t be static. A clocal method is intended for execution on the client side. Any attempt to invoke it on the server side will result in an error. This qualifier has no effect in standalone deployment. An slocal method is intended for execution on the server side. Any attempt to invoke it on the client side will result in an error. This qualifier has no effect in standalone deployment. A remote method is designated for execution on the server side even when it’s called by the client side, unless the program in deployed in standalone mode. When you invoke a remote method from a client side, the call is automatically serialized and sent to the server, where the actual call is performed, and the result is serialized and sent back to the client. If a remote method is called on the server side, it’s treated as a local call. A generated method is one that has been generated by a Wizard. Core Reference 379 A method whose visibility is not explicitly defined, has domain visibility – it’s visible to all other classes in the same domain. 10.8.4.2 Constructors Constructors are special methods to be invoked when an object is created using the new operator. The purpose of a constructor is to initialize the object and do any pre-processing necessary. When defining a constructor you should not specify a return type (the return type is implicit and is the class itself), and you should use the class name as the method name. Example: class Car { protected string make; protected string model; protected int year; public Car (string make, string model, int year) { this.make = make; this.model = model; this.year = year; } //... } The constructor for Car takes three arguments, which simply provide values for the corresponding class fields, and uses them to initialize the fields. With this definition in place, we can instantiate Car like this: Car c = new Car("Toyota", "Camry", 2008); This causes an instance of Car to be created and the constructor invoked on that instance. 10.8.4.3 Invoking Methods A non-static method can only be invoked on a class instance. However, a non-static class member can invoke a class method without specifying an instance. In this case, the class instance is implicit (this) and need not be specified. A static method can be invoked directly, without having a class instance. Example: class Test { public void foo1 () { //... } public void foo2 () { foo1(); // equivalent to: this.foo1() Test.foo3(); // OK because foo3() is static foo3(); // equivalent to: Test.foo3() } public static void foo3 () { foo1(); // invalid: no class instance specified Test test = new Test(); 380 JavaGram Agile Development test.foo1(); // OK } } 10.8.4.4 This Within a non-static class method, you can use the keyword this to refer to the implicit object with which the method has been invoked. In most cases, you don’t need to use this – you can just refer to class members freely – but where this may cause a name conflict with other variables (such as in the constructor for Car), using this is necessary. 10.8.4.5 Default Arguments When you define a method, you can specify default values for its parameters. All default arguments must be trailing. Example: public vector find (map criteria, int limit = 0, boolean sort = true) { vector res = vector(); // return res; } This method takes three arguments, of which two have default values. Here are some examples of invoking the method: find(m); // equivalent to find(m, 0, true) find(m, 100); // equivalent to find(m, 100, true) find(m, 100, false); find(m, false); // invalid: can’t omit intermediate parameter limit Default arguments serve as a convenience, helping you make your code more succinct. A default argument value can be any valid expression. For non-static methods, it can ever refer to this. You can use the = or the @= operator when specifying a default value for a method parameter. The latter casts the default value to the type of the parameter. 10.8.4.6 Variable Number of Arguments Sometimes it’s desirable to define a method whose number of arguments can’t be determined in advance. In such cases, you can leave the method parameters unspecified (using ellipses) and then use the arg operator to access them at runtime. Example: public static real max (...) { if (arg(null) == 0) return null; real max @= arg(0); for (int i = 1, n @= arg(null); i < n; ++i) { real val @= arg(i); if (val > max) max = val; Core Reference 381 } return max; } You can use arg(null) or arg(-1) to get the actual number of arguments passed to the method, arg(0) to get the first argument value, arg(1) to get the second, and so on. In fact, you can use the arg operator in any method, but in normal methods, named parameters are preferred as they’re self-documenting. 10.8.4.7 Method Overloading Like Java, JavaGram allows you to overload method names, in the sense that you can define multiple methods in the same class that have the same name but different parameter lists. Example: class Catalog { public void add (Item item) { // } public void add (vector<Item> items) { // } } Method overloading is especially useful in writing constructors, as it allows you to offer a variety of styles of creating class instances. However, before you overload a method, you should consider whether your objectives can be met with default arguments. The latter sometimes offers similar flexibility with less coding effort. 10.8.4.8 Type Checking When you invoke a method, the argument values are type checked against the method parameter types. If necessary, JavaGram will perform certain implicit type conversions to achieve a match. For example, if you pass an integer value for a parameter that’s specified to be real, JavaGram will automatically convert the integer to real, but it will not do the opposite. If no match can be achieved then the call will be reported as an error. If multiple methods match the call then javaGram will reconsider those methods and insist on an exact match (with no implicit type conversion). If the match can’t be narrowed down to one then JavaGram will report the call as ambiguous. This style of type checking, which is consistent with how Java operates, is intended to prevent the programmer from making inadvertent errors in method calls. 10.8.4.9 Remote Methods Remote methods (and remote classes, described later on) represent one of the most powerful features of JavaGram. They make the task of writing client-server programs exceptionally easy by removing the burden of having to deal with data communication, synchronization, hand-shaking, error handling, and so on. As a result, invoking a remote method on a server becomes as easy as invoking a local method. Hiding all this 382 JavaGram Agile Development complexity has the added benefit of allowing you to easily use the same code in different deployment models. To make a method remote, use the remote qualifier. When defining a remote method pay close attention to whether it should be static or not. Static remote methods are more efficient because there is no class object involved. For a non-static remote method, JavaGram serializes and sends the object (on which the method is invoked) to the server side. When the call is completed, this object (which may have been modified) is serialized, sent back to the client, and any changes to it are reflected back in the original object. However, GUI fields of the object (if any) are excluded from this update process, as they would be meaningless on the server side. Example: class RemoteTest { public static void localMethod (string s) { sys.println($"localMethod({s}) invoked"); } public remote static void remoteMethod (string s) { sys.println($"remoteMethod({s}) invoked"); } public static void main () { RemoteTest.localMethod("first"); RemoteTest.remoteMethod("second"); } } If you run this program in standalone mode, you’ll get this output: localMethod(first) invoked remoteMethod(second) invoked If you run the program in client-server mode, you’ll get these outputs: Client-side output: localMethod(first) invoked Server-side output: remoteMethod(second) invoked Whether a method is defined as local or remote is an important design consideration. Generally, it’s a good practice to have all your database handling on the server-side as remote, so that data access is completely hidden from the client-side. Also, any logic that’s intended to be hidden from the client-side (e.g., for security reasons) is best implemented as remote. When JavaGram code is demand-downloaded from a server to a client, the implementation of remote methods is excluded. This improves security and reduces traffic. Core Reference 383 10.8.4.10 Targeted Remote Calls By default, the server against which a remote call is executed is the one from which the method’s class has been downloaded – we call this the class’s source server. This is sufficient and secure for the following situations: When a client runs against a single server. In this case, all remote calls simply resolve to this server. When a client runs against multiple servers whose code basses are mutually exclusive. In other words, each server performs a different set of services. In this case, <load> markups indicate the target servers via their src property. A third possibility is when we want to call the same remote method against multiple servers that have the same code base. In this case, the call must explicitly specify the intended server, otherwise all such calls will be routed to the source server. The syntax for a targeted remote call is as follows. ServerStream::RemoteCall Here is an example: class Foo { public remote static void test (string str) { sys.println($"in test({str})"); } public static void main () { stream s = sys.client("localhost", 444, null, "C:/JagClient/ssl/JAG.jks"); test("first call"); null::test("second call"); s::test("third call"); sys.call(s, Foo, $test, "fourth call"); sys.close(s); sys.println("end"); } } If this is run against a localhost:443 server, the first and second call to test() will be routed to the class’s source server, and the third and fourth call to localhost:444. Note that a targeted remote call on a null stream is routed to the class’s source server. 10.8.4.11 Local Methods If a method is not declared as remote then it’s local. A local method simply executes where it resides. However, because JavaGram scripts are automatically downloaded from a server, the same code (and hence method) can reside in both clients and servers. This is usually desirable because we often need the same logic in both places. In cases where this is undesirable, you can control the access at a method level. 384 JavaGram Agile Development To restrict a method’s visibility to client-side only, declare it as clocal. Similarly, to restrict a method’s visibility to server-side only, declare it as slocal. For maximum security, JavaGram enforces these qualifiers by excluding a method from the compiled code not intended for a side. 10.8.5 Inheritance Inheritance is the cornerstone of object-oriented programming. It allows you to customize the behavior of an existing class to your specific needs. Example: class Party { //... } class Person extends Party { //... } class Organization extends Party { //... } Here, Party is called a base or super class, and Person and Organization are called derived or sub classes. The general rule is that a subclass inherits all the fields and methods of its super classes, but only has access to those that are not private. Also, any methods redefined in a subclass override the ones in the super class. 10.8.5.1 Abstract Methods Abstract methods allow you to declare methods in a super class and defer their implementation to subclasses. A class that has abstract methods (or extends an abstract class without implementing all its abstract methods) must be declared as abstract. Example: abstract class Shape { abstract public void draw (); abstract public void offset (int x, int y); } class Rectangle extends Shape { int x, y; int width, height; public void draw () { //... } public void offset (int x, int y) { //... } } Core Reference 385 10.8.5.2 Polymorphism An important feature of inheritance is polymorphism. If a method in a class hierarchy is overridden by a subclass, a call to that method is resolved at runtime by considering the type of the object on which it’s invoked. For example, consider a subclass of Rectangle which overrides the draw() method: class RoundedRectangle extends Rectangle { real curvature; public void draw () { //... } } Polymorphism results in the following behavior: Shape s = new Rectangle(); s.draw(); // invokes Rectangle.draw() s = new RoundedRectangle(); s.draw(); // invokes RoundedRectangle.draw() 10.8.5.3 Super Within a non-static method of a subclass, you can use the super keyword to refer to the members of the super class. Example: class RoundedRectangle extends Rectangle { //... public void draw () { super.draw(); // refers to Rectangle.draw() //... } } 10.8.5.4 Mutual Classes Mutual classes facilitate multiple inheritance – a derived class can have multiple base classes, provided at most one of them is non-mutual. Example: mutual abstract class Shape { Color color; abstract public void draw (); abstract public void offset (int x, int y); //... } class Region { //... } 386 JavaGram Agile Development class Polygon extends Shape, Region { //... public void draw () { //... } public void offset (int x, int y) { //... } } If a mutual class appears more than once in an inheritance hierarchy, then only one instance of it will appear in the subclass’s object. In this respect, mutual classes behave the same way as virtual base classes in C++. For example, in mutual class A { //... } mutual class B extends A { //... } class D extends A, B { //... } An instance of D will contain one instance of A, even though the latter appears twice in the inheritance hierarchy of D. As a general rule, all the base classes of a mutual class must also be mutual. 10.8.5.5 Subclass Constructor Rules When you define a constructor for a subclass, there are certain rules that you need to observe: If a direct super class has constructors then you must explicitly invoke one of the constructors at the beginning of the subclass constructor. However, if the super class has a default constructor (i.e., a constructor that takes no arguments) then you don’t need to do this – JavaGram will automatically do this for you. Alternatively, you can invoke another constructor of the subclass. If the subclass has multiple direct super classes then you must invoke a constructor of each super class (that has no default constructor) using casting to avoid ambiguity. If all the super classes have default constructors and you don’t define a constructor for the subclass then JavaGram will create one for you which will call the super class constructors. Example 1: Core Reference 387 mutual class Base1 { public Base1 (string s) { //... } } class Derived1 extends Base1 { public Derived1 (string s) { super(s); // call to base class constructor necessary } } Example 2: class Base2 { public Base2 (int i) { //... } } mutual class Base3 { public Base3 () { //... } } class Derived2 extends Base1, Base2, Base3 { public Derived2 (string s, int n) { super@Base1(s); // resolves to Base1 constructor super@Base2(n); // resolves to Base2 constructor // super#Base3() is called automatically } } 10.8.6 Text Members A text member is like a method and can be used to handle parameterized text. It’s defined using a markup notation, and can be prefixed with method qualifiers. Text members may be specified with any of the qualifiers allowed for methods. Example: static public <text string newAccEmail (string name, string id, string passwd)> Dear {name}, This is to confirm that your account has been setup. Your Userid is: {id} Your Password is: {passwd} You will be required to reset your password when you first login. </text> The definition must always follow this pattern: 388 JavaGram Agile Development <text signature properties> ... </text> The signature must declare a method that returns a string, but can take arbitrary parameters. Within the text block, you can write arbitrary expressions enclosed within a pair of braces. These expressions can refer to the text method parameters. When the method is called, the embedded expressions are evaluated and spliced into the text, and the resulting text is returned. This is somewhat similar to delayed strings, but text methods are more flexible in that the text can span across multiple lines and have additional properties. The call sys.println(newAccEmail("John Smith", "john", "xyz21!")); to the above text method will return the following text: Dear John Smith, This is to confirm that your account has been setup. Your Userid is: john Your Password is: xyz21! You will be required to reset your password when you first login. 10.8.6.1 Text Properties A text property is specified using the usual markup notation for properties: propName = propValue If propValue is not a literal then it must be written as {expression}. The following text properties are supported: The trim property can be set to true or false (defaults to false). When set to true, it trims line breaks, carriage returns, and tabs (but not spaces) from the final text. The indent property can be set to true or false (defaults to false). When false, it takes note of the indentation of the first line (counts spaces and tabs). It then removes the same amount of indentation from every text line. This has the effect of cancelling the unwanted indentation introduced as a result of how JavaGram code is formatted. The syntax property can be set to $text or $sql to denote the syntax of the actual text. 10.8.6.2 Text Qualification The text notation is extensible through qualification. The SQL package (see Chapter 12) makes extensive use of this to provide qualified text markup such as: <text.sql ... /> Core Reference 389 <text.sql.query ... /> <text.sql.prepare ... /> Qualified text markup makes it possible to customize text behavior and implement additional properties. To implement your own text markup qualification, follow the steps described in Section 9.3. 10.8.7 GUI Members Like text members, GUI members are defined using a markup notation. These are described in the next chapter. To implement your own GUI markup extension, follow the steps described in Section 9.2. 10.8.8 Static Initialization Blocks As in Java, static initialization blocks are used to perform computations before a class is instantiated. A static initialization block must appear inside a class, start with the keyword static, followed by a block of code. Example: class SalesCatalog { static map<string, Product> catalog; static vector<Product> lookupProducts () { // Lookup and return all products in the database } static { for (Product prod in lookupProducts()) catalog[prod.getId()] = prod; } } Note that code within a static block can’t refer to this; it can only refer to static class members. The position of a static block in a class is not significant. However, if there are multiple static blocks in the same class, they are executed in the relative order they appear. 10.8.9 Singleton Classes Some classes are not intended to be instantiated more than once. By declaring such classes as singleton, you ensure that this behavior is enforced. Example: singleton class PriceList { protected map items = map(); public void addItem (string name, real price) { items[name] = price; } public real priceOf (string name) { if (items[name] == null) throw new Exception("no such item: " + name); 390 JavaGram Agile Development return items[name]@real; } public int countItems () { return sys.length(items); } } You can refer to the single instance of this class as PriceList.singleton. The following rules apply: If a singleton class has no constructor or it has a default constructor (i.e., a constructor that takes no arguments) then you don’t need to create an instance of the class. The instance is created automatically the first time you use the .singleton notation. If a singleton class has one or more constructors that take arguments and no default constructor then you must create an instance of the class before using the .singleton notation. A class derived from a singleton is not singleton unless explicitly defined to be so. Attempting to create more than one instance of a singleton class results in a runtime error. Using the .singleton notation on a non-singleton class results in a load time error. 10.8.10 Remote Classes A remote class is one whose instances always reside on the server-side, but whose public members can be accessed from the client side. This can be useful in a number of situations: When we don’t want to give client-side access to the internal implementation of the object due to, for example, security or intellectual property concerns. The object is potentially too large and costly to be transported between the server and a client. The object requires access to server-side resources and can’t be operated if it resides on the client-side. You define a class as remote by placing the remote qualifier before its definition. Example: remote class Account { string id; //... public string getId () { return id; } public real getBalance () { //... } Core Reference 391 public void deposit (real amount) { //... } public static Account find (int id) { // Find and return the account } } The usual pattern for getting hold of a remote object is to have a static method in the remote class (such as find() above) or a remote method in another class that returns it. The server, however, never returns the remote object, but a reference that will uniquely identify the object from then on. On the client-side, this reference is provided as a proxy object. Any method invoked on the proxy object is transmitted to the server side and performed on the real object instead. Here is some sample client-side code for the above class: Account acc = Account.find(101); if (acc.getBalance() < 0) acc.deposit(100.0); As illustrated here, there is no syntactic difference between working with a proxy object or a real object. JavaGram hides the underlying complexity from the programmer. 10.9 Exceptions As in Java, the best way to report an error situation is to raise an exception. The Exception class is predefined and has the following simple definition: class Exception { protected string message; public Exception () {} public Exception (string msg) {message = msg;} public string getMessage () {return message;} } To define your own exception, you should subclass this class. Example: class InvalidRecordException extends Exception { //... } Because Exception has a default constructor, you don’t need to define a constructor for your exception class unless, of course, you need one. An exception class is typically instantiated for the purpose of raising an exception. Example: throw new InvalidRecordException(); 392 JavaGram Agile Development 10.10 The Sys Pseudo Class The sys pseudo class provides a set of useful attributes and methods that you’ll use in almost any JavaGram program. We call sys a pseudo class because you can’t instantiate or subclass it. Due to Flash security restrictions, not all sys methods are available in the Flash JavaGram runtime. Where a method is not available, it’s marked with the icon . However, if the method is available in Adobe AIR, it’s marked with the icon . 10.10.1 Sys Attributes System attributes denote useful values that are either set internally or at a global level using runtime options in the command line (see Chapter 7). Unless specified otherwise, system attributes are read-only. stream sys.in stream sys.out stream sys.err Summary: These three attributes represent the standard streams for, respectively, input, output, and error. sys.println(sys.out, "hello"); Example: sys.println(sys.err, "failed"); string s = sys.readln(sys.in); string sys.root Summary: Denotes the root directory path. JavaGram assumes that all relative paths are relative to this directory. In a Flash client, there is not root directory, so this attribute is set to "". string sys.airRoot Summary: Denotes the root directory path for a Flash AIR client, which is usually set to where the AIR application is installed. Otherwise, it is set to "". string sys.host Summary: Denotes the server host address (in the format host:port), where host may be specified as a domain name or an IP address. string sys.ssl Summary: Denotes the Java SLL keystore for a client. In a Flash client, this attribute is always set to null. string sys.boot Core Reference 393 Summary: Denotes the initial script for a client (i.e., the script that enables a client to boot itself). map sys.config Summary: Denotes the contents of the server configuration file. It maps configuration symbols to values. On client side, this attribute is always set to null. boolean sys.debug Summary: When true, it indicates that JavaGram is running in debug mode, allowing a debugger to set breakpoints and interrogate the JavaGram runtime stack. In a Flash client, this attribute is always set to null.This attribute is used by JADE. boolean sys.verbose Summary: When true, it indicates that JavaGram is running in verbose mode, causing additional runtime diagnostics to be sent to standard output (defaults to false). This attribute can be assigned to. boolean sys.warning Summary: When true, warning messages are displayed (defaults to true). This attribute can be assigned to. symbol sys.stage Summary: Denotes the stage of the application, which can be one of: $dev, $test, or $prod (default is $dev). vague sys.handy Summary: Handy attribute (which can be assigned to) for holding application-specific information. For example, this is used by JAG.swf for retrieving URL parameters. string sys.logfile Summary: Denotes the path of the JavaGram log file. When a log file is specified, anything sent to standard output or standard error is redirected to this file. Log files are typically used for servers. In a Flash client, this attribute is always set to null. string sys.tz Summary: Denotes the current timezone as a string (e.g., "America/Los_Angeles"). In a Flash client, this attribute is always set to null. stream sys.loader 394 JavaGram Agile Development Summary: Denotes the client-side stream that connects the client to its server. This stream is used for downloading scripts, hence the term ‘loader’. map sys.xfr Summary: Denotes a map for controlling the client-server data transfer rate. [$block=>1024, $sleep=>200] Example: Causes data transfer to be in blocks of 1k each, with a delay of 200 milliseconds in between. By adjusting these values you can emulate a slow connection, such as a modem, for testing purposes. In a Flash client, this attribute is always set to null. boolean sys.async Summary: On the server side, this attribute defaults to true if the server is configured as an asynchronous server, and false otherwise. On the client side, it defaults to true if the client is a Flash client or it’s running against an asynchronous server, and false otherwise. This attribute is typically used as the condition for writing guarded asynchronous calls so that such calls are handled asynchronously only when the capability is there. boolean sys.flash Summary: Defaults to true for a Flash client, and false otherwise. This attribute is typically used to avoid accessing JavaGram features that are not supported by a Flash client (e.g., access to local files, which is barred by Flash security). boolean sys.air Summary: Defaults to true for a Flash AIR client, and false otherwise. 10.10.2 Sys Methods 10.10.2.1 Parsing and Evaluation vague sys.parse (string code, boolean expr = true, boolean eval = false) Summary: Parses and analyzes the code denoted by the string code. If successful, the returned result is ready for evaluation. When expr is true, the code is treated as an expression. Otherwise it’s treated as a statement. When eval is true, the result of parsing is immediately evaluated and returned. Note: sys.parse(code, true, true) is not the same as sys.eval(sys.parse(code, true)). In the former, code can refer to variables visible in the method in which the call appears, whereas the latter will not have access to such variables. sys.parse("10 + 20") Example: Core Reference 395 string sys.serialize (vague object, boolean nullTransient = false) Summary: Serializes the object denoted by object and returns the resulting string. When nullTransient is true, all transient fields are output as null. sys.serialize(obj) Example: returns, for example: [@Employee givenname=>"Craig", lastname=>"Smithers"] vague sys.eval (vague code) Summary: Evaluates the code denoted by code and returns the result of evaluation. sys.eval(sys.parse("10 + 20")) Example: returns: 30 10.10.2.2 File Handling void sys.getDirPath (symbol kind, boolean asUrl = false) Summary: Returns the path of a standard directory (denoted by kind) which may be one of: $application, $appStorage, $desktop, $documents, $user. A native path is returned, unless asUrl is true (asUrl is always ignored in Java). sys.getDirPath($user) Example: void sys.createPath (string path) Summary: Creates the empty file or directory denoted by path. If path ends in / or \ it is treated as a directory; otherwise, as a file. To create a directory, this method will create any non-existent intermediate directories as well. To create a file, however, this method requires the parent directory to pre-exist. sys.createPath("C:/handy.txt") Example: void sys.deletePath (string path) Summary: Deletes the file or directory denoted by path. If path ends in / or \ it is treated as a directory; otherwise, as a file. If path denotes a directory then it must be empty for it to be deleted. sys.deletePath("C:/handy.txt") Example: void sys.renamePath (string oldPath, string newPath) Summary: Renames the file or directory denoted by oldPath to newPath. If path ends in / or \ it’s treated as a directory; otherwise, as a file. sys.renamePath("C:/handy.txt", "C:/handy2.txt") Example: boolean sys.pathExists (string path) Summary: Returns true if the file or directory denoted by path exists, and false otherwise. sys.pathExists("C:/Program Files") Example: 396 JavaGram Agile Development map sys.pathProps (string path, map props = null) Summary: Returns a map that captures path properties. The map has the following keys: $dir (the directory path) $type (one of $dir or $file) $length (in bytes) $modified (last modified timestamp) $readable (true if path is readable). Always returns null on AIR. $writeable (true if path is writable). Always returns null on AIR. When the props parameter is null, a new map is created and returned. Otherwise, props is updated and returned. Raises an exception if path doesn’t exist. sys.pathProps("C:/Program Files") Example: returns: [$dir => "C:/Program Files/", $length => 0, $modified => [#2008-02-28 16:46:20.17000000], $readable => true, $type => $dir, $writeable => false] vector<string> sys.listDir (string path) Summary: Returns a vector that contains the name of every file or directory inside the directory denoted by path. sys.listDir("C:/Program Files") Example: returns, for example: ["Adobe", "ahead", "Beyond Compare 2", "Cisco Systems", ...] void sys.copyFile (string srcFile, string dstFile, boolean urlSrc = false, symbol callback = null) Summary: Copies the file denoted by srcFile to the path denoted by dstFile. The latter is created if it doesn’t exist. When urlSrc is true, srcFile is assumed to be a URL, otherwise it’s assumed to be a file path. When urlSrc is true and callback (which may denote a method in the same class, having one vague parameter) is not null, callback is called upon completion of the copy and given the path of the target file (when successful) or an exception. sys.copyFile("C:/handy.txt", "C:/handy2.txt") Example: string sys.createTempFile (string prefix, string suffix, string dir = null, Boolean delOnExit = false) Core Reference boo 397 Summary: Creates a temporary file and returns its path. prefix must be at least 3 characters long. When dir is not specified, the default temporary directory is used. When delOnExit is specified and is true, the file is deleted when the application exits. Applications, however, should assume responsibility for removing temporary files. On AIR, all arguments are ignored and the temporary file name is determined automatically. Example: sys.createTempFile("Test", ".txt") returns something like: "C:/temp/Test8217.txt" stream sys.open (string file, string mode, string encoding = null) Summary: Opens the file denoted by path in mode and returns a stream for it. The latter must be "r" (for reading), "w" (for writing), or "a" (for appending). When encoding is not specified, the default character encoding is used (platform dependent). Valid encoding values include: "US-ASCII" (seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set). "ISO-8859-1" (ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1). "UTF-8" (eight-bit UCS Transformation Format). "UTF-8WIN" (JavaGram's variant of UTF8 for Windows platforms). "UTF-16BE" (sixteen-bit UCS Transformation Format, big-endian byte order). "UTF-16LE" (sixteen-bit UCS Transformation Format, little-endian byte order). "UTF-16" (sixteen-bit UCS Transformation Format, byte order identified by an optional byte-order mark). sys.open("C:/handy.txt", "r") Example: void sys.close (stream s) Summary: Closes the stream denoted by s, which must have been returned by an earlier call to sys.open(). sys.close(s) Example: void sys.zip (string zipFilePath, string zipRoot, ...) 398 JavaGram Agile Development Summary: Creates the ZIP file denoted by zipFilePath and adds the files/directories denoted by the third argument onward to the ZIP file. If zipRoot is null then the files/directories must be specified using their absolute path (the entry names in the ZIP file will also be named according to these absolute paths). Otherwise, they should be specified relative to zipRoot (the entry names in the ZIP file will also be named according to these relative paths). Any directory whose name starts with a period (e.g., ‘.svn’) is ignored. Each ZIP entry must be either a file name or a vector of file names (not directories). The third argument may optionally be a map, in which case it’s not treated as a ZIP entry but as a means for renaming the ZIP entries. Each ZIP entry is looked up in this map to get its new name. If present in the map, the file is accessed using its original name and the ZIP entry is written using the new name. Otherwise, the original file name is used for both purposes. Example: sys.zip("C:/handy.zip", "C:/proj/", "crm/", "sample.txt") vector<string> sys.unzip (string zipFilePath, string toDirPath = null) Summary: Unzips the ZIP file denoted by zipFilePath into the directory denoted by toDirPath. The latter may be null, in which case the file is not unzipped but its content is returned as a vector, where each element is the path of an entry. sys.unzip("C:/handy.zip", "C:/tmp") Example: void sys.load (vague files, boolean relative = true, boolean autoCallMain = false) Summary: Loads the files denoted by files. The latter may be a file path or a vector of file paths. When relative is true, the file paths are assumed to be relative to sys.root; otherwise, they are treated as absolute file paths. Loading each file causes it to be parsed and analyzed, ready for execution. When autoCallMain is true, the public static void main() method of each class (if defined) is executed upon successful loading. When specifying a file, don’t include its extension. For example, instead of Client.jag (or Client.jax) use Client. JavaGram will use the compiled version if it exists. If the source file also exists and is more recent or older than the compiled version, JavaGram will delete the compiled version and use the source version instead. sys.load("crm/Client") Example: void sys.jload (string path) Summary: Causes Java classes to be loaded as if they were part of the CLASSPATH the program was started with. path must denote a directory of Java class files or a JAR file. sys.jload("C:/drivers/MyDriver.jar") Example: Core Reference 399 10.10.2.3 Input/Output vague sys.read (stream s = null) Summary: Reads, parses, analyzes, and returns the next JavaGram expression from the stream denoted by s (or sys.in when s is null). When there is nothing left to read (i.e., end of file is reached), null is returned. The returned expression is ready for evaluation. sys.read(s) Example: string sys.readln (stream s = null, boolean incEOL = true) Summary: Reads and returns the next line from the stream denoted by s (or sys.in when s is null). When incEOL is true, EOL (if present) is included in the returned string. Otherwise, it’s excluded. sys.readln(sys.in, false) Example: native sys.readbin (string file) Summary: Reads and returns a binary object that represents the contents of the file whose path is denoted by file. sys.readbin("C:/handy/Logo.png") Example: void sys.skip (stream s, int bytes) Summary: Skips the number of bytes denoted by bytes in the input stream s. sys.skip(s, 64) Example: void sys.pp ([stream s], ...) Summary: Pretty-prints values denoted by the arguments to the stream denoted by s, or sys.out when s is not specified. Note that this method does not flush the output buffer, so unless you call sys.flush(), no output may appear. Unlike sys.print(), this method: Escapes special characters (\n, \r, \t, \b, \f, ', ", and \). Outputs characters and strings with their begin/end quotation marks included. Treats circular references safely to avoid infinite recursion. sys.pp(vec) Example: void sys.ppEscape (stream s, vague val, boolean escapeHtml = true) Summary: Similar to sys.pp() except that it always expects an output stream and a single value. When escapeHtml is true: & is output as & < is output as < > is output as > sys.ppEscape(sys.out, "<p>Test</p>", true) Example: 400 JavaGram Agile Development void sys.ppln ([stream s], ...) Summary: Same as sys.pp() except that the output is followed by a new line and flushed. sys.ppln(vec) Example: void sys.print ([stream s], ...) Summary: Outputs values denoted by the arguments to the stream denoted by s, or sys.out when s is not specified. Note that this method does not flush the output buffer, so unless you call sys.flush(), no output may appear. sys.print("balance is ", s) Example: void sys.println ([stream s], ...) Summary: Same as sys.print() except that the output is followed by a new line and flushed. sys.println("balance is ", s) Example: void sys.printEscape (stream s, vague val, boolean escapeHtml = true) Summary: This is the ‘print’ version of sys.ppEscape(). When escapeHtml is false, backslash-escaping is performed on special characters and backslash itself. sys.printEscape(sys.out, "<p>Test</p>", true) Example: void sys.flush (stream s = null) Summary: Flushes any output pending on the stream denoted by s, or sys.out if no stream is specified. sys.flush() Example: void sys.xmlExport (stream s, vague val, string rootTag = null, boolean header = true, boolean indent = true) Summary: Converts the value denoted by val to XML format and writes it to the stream denoted by s. When rootTag is not specified, the root tag will be <root>. When header is true, an <?xml...?> header is written to s first. When indent is true, the XML is properly indented. sys.xmlExport(sys.out, expr) Example: vague sys.xmlImport (vague source) Summary: Converts the XML provided by source (which may be an input stream or a string) to a JavaGram literal, and returns it. sys.xmlImport(str) Example: 10.10.2.4 Debugging string sys.stackTrace () Core Reference 401 Summary: Example: Returns the current thread’s list of stack frames as a string. Each line in the string represents the stack frame of a method call, starting with the current method and going back all the way to a main method. A call will return something like this: [2] DataSet.sample(2.3, 452.89) called at C:/JavaGram/jag/test/code/Basic2.jag (line 5 col 2-8) [1] DataSet.process(vector) called at C:/JavaGram/jag/test/code/Basic2.jag (line 13 col 5-12) [0] DataSet.main() called at C:/JavaGram/jag/test/code/Basic2.jag (line 3 col 0-5) 10.10.2.5 String Handling stream sys.buffer () Summary: Creates and returns a new buffer stream. This buffer can be used for IO purposes the same way file and channel streams are used. sys.buffer() Example: void sys.setBufStr (stream buffer, string str) Summary: Sets the contents of the buffer stream denoted by buffer to the string denoted by str. sys.setBufStr(buf, "sample data") Example: string sys.getBufStr (stream buffer) Summary: Returns the contents of the buffer stream denoted by buffer as a string. sys.getBufStr(s) Example: string sys.fileAsHex (string file) Summary: Reads the binary contents of the file whose path is denoted by file, and returns a hexadecimal representation of the data as a string. sys.fileAsHex("C:/handy/Logo.png") Example: string sys.format (vague obj, string format = null) Summary: This method is used for formatting numbers, dates, strings, and symbols. Default formatting (where format is null) works as follows. Integers are formatted by a comma appearing between every three digits (e.g., 100,000). Reals are formatted the same way but also rounded to two decimal places (e.g., 100,000.25). Dates are formatted as, for example, 2008-10-22. Symbols are stripped of their initial $ char. Strings are not altered. Where the default behavior is insufficient, use an explicit format string to specify your desired format, as exemplified below. 402 JavaGram Agile Development Example: sys.format(10000, "0:000") gives: 10:000 sys.format(10000.25282, "0,000.000") gives: 10,000.253 sys.format(sys.date(),"dd-MM-yyyy HH:mm:ss.SSS") gives: 18-02-2008 15:43:31.206 sys.format($action, "name") gives: "Action" sys.format($action, "upper") gives: "ACTION" sys.format("SIZE", "lower") gives: "size" int sys.strCmp (string s1, string s2, boolean ignoreCase = false) Summary: Compares strings s1 and s2, and returns zero if they’re equal, a negative integer if s1 is lexicographically less than s2, and a positive integer otherwise. The comparison is case-sensitive unless you pass true for ignoreCase. sys.strCmp("one", "two") Example: returns: -5 boolean sys.strLike (string s1, string s2, boolean ignoreCase = false) Summary: Returns true if s1 is a prefix of s2. The comparison is case-sensitive unless you pass true for ignoreCase. sys.strLike("man", "manner") Example: returns: true char sys.nthChar (string str, int idx) Summary: Returns the character in str denoted by the zero-based index idx. sys.nthChar("sample", 2) Example: returns: 'm' int sys.strChar (string str, char ch, boolean reverse = false) Summary: Returns the zero-based index for the first (or last, if reverse is true) occurrence of ch in str, and null otherwise. sys.strChar("sample", 'm') Example: returns: 2 string sys.strTo (string str, symbol to) Summary: Returns a string that is the result of transforming str according to the format denoted by to. The latter may be one of: $lower, $upper, or $name. Core Reference 403 Example: sys.strTo("Hello", $lower) returns: "hello" sys.strTo("Hello", $upper) returns: "HELLO" sys.strTo("ele mcpherson", $name) returns: Ele McPherson string sys.strEncode (string str, string encoding) Summary: Returns a string that is the result of encoding str according to the character encoding denoted by encoding. sys.strEncode("…some Unicode text…", "UTF-8") Example: string sys.subStr (string str, int from, int to = null) Summary: Returns a substring of str which starts at the zero-based position denoted by from and ends at the position denoted by to (or end of the string when to is null). sys.subStr("Something", 2, 5) Example: returns: "met" int sys.strHas (string str, string substr, boolean reverse = false) Summary: Returns a zero-based integer denoting the starting position of substr in str, if successful, and null otherwise. When reverse is true, the search is performed from the end of the string. sys.strHas("Something", "met") Example: returns: 2 string sys.strTrim (string str, boolean full = false) Summary: Returns the result of trimming whitespace from either end of str. When full is true, whitespace is also removed from within the string. sys.strTrim(" a lot ", true) Example: returns: "alot" vector<string> sys.strSplit (string str, string separator = null) Summary: Returns a vector which is the result of splitting str into parts that are separated by separator. When the latter is null, "." is used. sys.strSplit("jag.lang.common") Example: returns: ["jag", "lang", "common"] native sys.strPattern (string reg) Summary: Returns a pattern which is the result of compiling the regular expression denoted by reg. sys.strPattern("obj.*s") Example: 404 JavaGram Agile Development int sys.strFind (string str, vague reg, int start = 0) Summary: Checks if there is a match between str and the regular expression denoted by reg, which may be a regular expression string or a compiled pattern return by sys.strPattern(). The search starts in str at the position denoted by start. The returned value denotes the zero-based index of the matching substring, or -1 if there is no match. sys.strFind("alternatives", "nat.*s") Example: returns: 5 boolean sys.strMatch (string str, vague reg) Summary: Returns true if str matches the regular expression denoted by reg, which may be a regular expression string or a compiled pattern return by sys.strPattern(). sys.strMatch("objectives", "obj.*s") Example: returns: true string sys.strReplace (string str, vague reg, string replace, boolean all = false, vector<string> groups = null) Summary: Replaces occurrences in str that match the regular expression denoted by reg (which may be a regular expression string or a compiled pattern return by sys.strPattern()) with replace. Unless all is false, only the first occurrence is replaced. When group is not null, all replaced occurrences are added to group. vector group = vector(); Example: sys.strReplace("the last fast dog", ".ast", "xx", true, group) returns: "the xx xx dog" and group is set to: ["last", "fast"] symbol sys.strSym (string str) Summary: Returns a symbol which is the result of converting str to a symbol. sys.strSym("my symbol") Example: returns: ${my symbol} string sys.symStr (symbol sym) Summary: Returns the string part of sym (i.e., excluding the $). sys.symStr($name) Example: returns: "name" vague sys.cypher (string data, string key, string verify = null) Core Reference 405 Summary: If verify is null then it returns a string which is the result of encrypting data using key. Otherwise, it verifies that the encrypted string (denoted by verify) matches the result of encrypting data using key, and returns a boolean result. For security, the encryption is uni-directional: cypher cannot be used to decrypt an encrypted string, only to verify it. For bi-directional encryption/decryption, you can do the following: Pass "encode" for key and do not use verify. The result of encrypting data is returned. Pass "decode" for key and do not use verify. The result of decrypting data is returned. For one-way hash digests of data such as passwords, pass "digest" for the key. A SHA digest of the string is returned. Example: sys.cypher("sample", "key") returns: "dfncshzfzm" sys.cypher("sample", "key", "dfncshzfzm") returns: true string s = sys.cypher("sample", "encode")@string; sets s to: "c2FtcGxl" sys.cypher(s, "decode") returns: "sample" sys.cypher("sample", "digest")@string returns: "ykmiaiqksg" string sys.normPath (string path, string prefix = null, symbol format=$unix) Summary: Returns the result of normalizing path, such that backslashes are replaced by forward slashes. If prefix is specified then it should be a partial path. This partial path is matched within path and anything to the left of it in path is discarded. To replace forward slashes with backslashes instead, pass $dos for format. sys.normPath("C:\\proj\\crm\\core\\db.java") Example: returns: "C:/proj/crm/core/db.java" sys.normPath("C:\\proj\\crm\\core\\db.java", "proj/crm") returns: "proj/crm/core/db.java" string sys.pathConc (...) Summary: Returns the result of concatenating one or more paths. The separator / is automatically inserted or removed to ensure a valid outcome. sys.pathConc("C:/proj", "crm/rep", "sample.html") Example: returns: "C:/proj/crm/rep/sample.html" map<symbol,string> sys.pathParts (string path) Summary: Returns a map of three elements denoting the directory, the name, and the file extension of path. 406 JavaGram Agile Development Example: sys.pathParts("C:/proj/crm/test.jag") returns: [$dir => "C:/proj/crm/", $ext => "jag", $name => "test"] vector<string> sys.htmlRefs(string htmlPath, string parentPath = null) Summary: Returns a vector of files that are referenced by the HTML file denoted by htmlPath. If parentPath is specified then this is used to determine how to resolve “..” in the encountered file paths. parentPath should be usually set to the relative path of the file, or the relative path of the file in which htmlPath appears as an anchor. Currently only image files of the form <img src="..." ...> and HTML files of the form <a href="..." ...> are considered. sys.pathParts("C:/doc/help/Two.html", "doc/cue/one.html") Example: returns: ["doc/gifs/Logo.gif", "doc/gifs/Heading.gif"] 10.10.2.6 Client-server void sys.appServer (string host, int port, symbol mode = $sync) Summary: Creates and runs an application server thread that creates a channel on the host denoted by host (which must be a valid server name or IP address) and listens for input on the port denoted by port. Each time a client attempts to connect to this server, the server creates a separate session thread (with its own channel, which listens on the same port) and hands the client over to that thread. mode may be one of $sync, which causes each session to handle client requests synchronously. $async, which causes client requests to be handled asynchronously. This mode uses a separate thread to handle each incoming client request. $blaze, which uses Adobe’s BlazeDS (AMF over HTTP) instead of sockets. A server of this mode must be deployed in a servlet container (such as Apache Tomcat). The $blaze mode always implies $async. The advantage of this mode over sockets is that when Flash clients reside behind firewall proxy servers, proper HTTP tunneling is performed. A socket server thread loops indefinitely. Each session thread also loops indefinitely until its associated client exits, at which time the session thread terminates. A server is configured using the –config command line option (see Chapter 7). sys.appServer("localhost", 443, $async) Example: void sys.proxyServer (string host, int port, symbol mode, vector endServers) Core Reference 407 Summary: Creates a proxy server which directs traffic aimed at host:port to one of the application server hosts specified in endServers. As in appServer, each client connection results in a separate session. However, the role of the session is to just pass traffic through to the application server and back. Pass $failOver for mode if you want the proxy server to run in a failover mode. Pass $loadBalance for mode if you want the proxy server to load balance across multiple application servers. A client has no way of telling a proxy server which end-server it should connect to. The decision is purely up to the proxy server. A client, in fact, can’t distinguish a proxy server from an application server. In attempting to connect to application servers, a failed application server is not retried for at least a further 5 minutes. Under a failover model, a client is always connected to the first available application server in the list. Under a load-balance model, client connections are allocated to available application servers in a round-robin fashion while attempting to balance the load according to the weight of each application server. Each application server is specified as a map that may have the following keys: $host should denote the application server’s host name or IP address. $port should denote the port on which the application server listens for connections. $weight should denote the relative weight of the server (defaults to 1.0 if not specified). This must be a real number greater than zero and less than or equal to 1.0. The weight should reflect the relative capacity of the server. For example, 0.5 means that the server has half the capacity of a server that has a weight of 1.0. $keystore specifies a keystore (see Chapter 7) and is needed if the application server uses SSL. $timeout is specified in milliseconds (defaults to 15000 milliseconds if not specified). This timeout applies to each packet in transit through the proxy server. If a packet times-out and the channel is still healthy, it’s re-sent until successful. A small timeout here helps the proxy server to quickly identify dead clients and remove their sessions. $delay is specified in milliseconds (defaults to 0 if not specified). This delay is applied after each packet transmission. You can use it to emulate the speed of a modem. Example: sys.proxyServer("localhost", 444, $loadBalance, [ [$host => "localhost", $port => 442, $weight => 0.5], [$host => "localhost", $port => 443, $weight => 1.0] ] ); stream sys.client (string host, int port, int timeout = null, string keystore = null, string proxyHost = null, int proxyPort = null, string proxyAuth = null) 408 JavaGram Agile Development Summary: Creates a client channel, connects it to the server running on host and listening on port (host must be a host name or IP address), and returns a stream for it. Each request from the client is subject to a timeout (specified in milliseconds). This defaults to 60,000 milliseconds (i.e., 60 seconds). If the server against which this client runs uses SLL then you must use the keystore parameter to specify the keystore holding the certificate. The following values are allowed: null mean don’t use SSL (i.e., normal server). "" means the server uses a digitally-signed certificate, so no keystore specification is required. Any other string should give the path to the keystore which holds your self-signed certificate. The keystore parameter is usually specified via the –ssl command line option (see section below on command line options). In networks where the client application needs to go through a firewall proxy server to connect to a server outside the network, use proxyHost (name or IP address) and proxyPort (port number) to specify the proxy server. Some proxy servers also require authentication, in which case this should be specified as the last argument (proxyAuth), using the format "username:password". Don’t confuse a firewall proxy server with a JavaGram proxy server; they’re not the same thing. The stream returned by client() can be used for IO purposes in the usual fashion. To close the stream, call sys.close(). Example: sys.client("localhost", 443) void sys.download (string remotePath, string localPath, stream channel = null, boolean remoteRelative = true, symbol callback = null, list extraCBArgs = null, boolean noZip = false) Core Reference 409 Summary: This method may be used by a client to request a file from a server. remotePath denotes the server-side file path. localPath denotes the clientside file path, where the received file will be stored. channel denotes the channel stream between the client and the server (if not specified, sys.loader will be used instead). The file is sent only if the download permissions defined in the server configuration permit the transfer. The optional remoteRelative parameter indicates whether remotePath should be relative to server-side sys.root. When the download is complete, callback is called if present. This should be a method in the same class, and must have at least a vague parameter, followed by optional parameters if desired (use extraCBArgs to pass values for the additional parameters as a list of arguments). The first parameter receives either an Exception or an information map of the form: [$local=>strPath, $remote=>strPath, $size=>intVal, $timestamp=>intVal] The server may decide to compress the file for efficient transmission, in which case, the client will decompress the file upon receiving it. However, when noZip is true, no compression will be performed. Note: to force the downloaded file to be added to the partitions cache, pass an empty string or null for localPath. This is particularly useful in a Flash client, as the user will no longer be required to nominate a location for saving the file. Once added to the partitions cache, the file can be accessed using sys.use(). Because of security restrictions, sys.download()behaves differently in a Flash client: remotePath may be a HTTP or HTTPS URL or a server-side file path. When it’s a URL, channel and remoteRelative parameters are ignored. localPath is used as a suggestion for the local name of the downloaded file. Also, callback receives the actual file name selected (not its path) in the $local entry of the info map. Pass an empty string or null if you want the file to be added to the partitions cache. Because of the way the channel protocol works for Flash, sys.dowload() should always be called in a callback. To force this, you can call Util.ping() asynchronously and call sys.download() in its callback. When the file is downloaded, the user is prompted with an alert box, followed by a file browse dialog box to choose a location for saving the file. This can be avoided by passing null for localPath (see below). If the downloaded file is a ZIP file, having just one entry, it’s automatically unzipped and that entry is saved instead. The download is always asynchronous in a Flash client (as opposed to synchronous in a Java client). Only the last bullet point applies to an AIR client. Example: 410 sys.download("Report.rtf", "C:/reports/Report1.rtf") JavaGram Agile Development void sys.upload (string localPath, string remotePath, stream channel = null, boolean remoteRelative = true, symbol callback = null, list extraCBArgs = null) Summary: This method may be used by a client to push a file to a server. localPath denotes the client-side file path. remotePath denotes the server-side file path, where the received file will be stored. channel denotes the channel stream between the client and the server (if not specified, sys.loader will be used instead). The file is accepted by the server only if the upload permissions defined in the server configuration permit the transfer. The optional remoteRelative parameter indicates whether remotePath should be treated as relative to server-side sys.root. When the upload is complete, callback is called if present. This should be a method in the same class, and must have at least a vague parameter, followed by optional parameters if desired (use extraCBArgs to pass values for the additional parameters as a list of arguments). The first parameter receives either an Exception or an information map of the form: [$local=>strPath, $remote=>strPath, $size=>intVal, $timestamp=>intVal] Because of security restrictions, sys.upload() behaves differently in a Flash client: remotePath may be a HTTP or HTTPS URL or a server-side file path. When it’s a URL, it must denote a web server page that handles the uploading (in this case, channel and remoteRelative parameters are ignored). The user is presented with a file browse dialog box to choose the file to be uploaded. You can use localPath to specify a file filter. For example, "Image Files:*.gif;*.png;*.jpg;*.jpeg" may be used for selecting common image files (note the use of colon to separate the prompt from the file extension patterns). When localPath is null, the selectable file type is based on the extension of remotePath. After the file is chosen, the name (not path) of the uploaded file is denoted by the $local key of the callback info map. The upload is always asynchronous in a Flash client (as opposed to synchronous in a Java client). Only the last bullet point applies to an AIR client. Example: sys.upload("C:/reports/Report1.rtf", "Report.rtf") string sys.use (string file, stream channel = null, boolean, track = true) Core Reference 411 Summary: Example: Demand-downloads the file whose path is denoted by file. The file is downloaded over channel stream (or sys.loader if channel is not specified). If channel and sys.loader are not valid streams then the file is not downloaded and deemed to reside locally. The method returns the absolute client-side path of the file. If a hook callback for tracking the downloading of files is defined, it’s called only when track is true and the file is actually downloaded. When a file is downloaded from a server, it’s cached on the client-side and given the same timestamp as on the server-side. In a subsequent run, the file is re-downloaded only when the timestamps mismatch (implying that the server-side file has been modified). Use this method in scripts, for example, to refer to required data files (e.g., HTML and GIF files). A typical use is in the GUI package where a GUI element refers to an image: <Button title="Perform" image={sys.use("gifs/Perform.gif")}/> void sys.rload (vague files, stream channel = null, boolean autoCallMain = true) Summary: Remote-loads the file(s) denoted by files. The latter may be a file path or a vector of file paths, all of which must be relative to server-side sys.root. Remote loading is like local loading (see sys.load()) except that if the file is not present on the client or its timestamp mismatches the server-side file, it’s first downloaded. The file is downloaded over the channel stream denoted by channel (defaults to sys.loader if not specified). When autoCallMain is true, the main() method of each loaded class (if defined) is automatically invoked. sys.rload("crm/Client") Example: boolean sys.partition (string name, symbol callback, stream channel = null) Summary: Downloads the partition denoted by name over the channel stream denoted by channel (defaults to sys.loader if not specified) and then invokes callback, unless the partition has already been downloaded by an earlier call, in which case nothing will happen. The method return true if the callback is called and false otherwise. The callback must be a method in the same class with the following signature: void callback (string err) In a non-Flash client, callback may be null. Because of the way the channel protocol works for Flash, sys.partition() should always be called in a callback. To force this, you can call Util.ping() asynchronously and call sys.partition() in its callback. In standalone mode, the callback is called immediately without attempting to obtain a partition. To clear a partition from the local cache, use sys.clear(). Example: 412 sys.partition("Main", $mainPartCB) JavaGram Agile Development boolean sys.partitioned (string remotePath, boolean extract = false) Summary: Returns true if the file denoted by remotePath has been partitioned (i.e., the file resides in the client’s partitions cache as a result of earlier partition download(s) or because of an earlier call to sys.download()). When extract is true and the file exists, the file is extracted from the partition and written to the location denoted by remotePath, relative to sys.root. sys.partitioned("crm/Main") Example: boolean sys.buildPartition (string absZipPath, vector<string> source) Summary: Builds a partition (for client-side use) whose absolute path is denoted by absZipPath and recursively includes the files/directories denoted by source. The source files/directories must be specified using their relative path, but no file extension is required. Any directory whose name starts with a period (e.g., ‘.svn’) is ignored. Each source file is compiled (if an up-to-date compiled version doesn’t already exist) and then filtered for client-side consumption. The build process is identical to that used for building partitions specified in a server configuration file. This method is useful where a partition needs to be built dynamically and/or programmatically. sys.buildPartition("C:/JavaGram/parts/MyPart.zip", [...]) Example: void sys.release (vague proxyObj) Summary: Intended to be called by a client on a proxy object (has no effect in standalone mode). Releases the proxy object’s reference to its server-side remote object, causing the latter’s reference count to be decremented. Any subsequent attempt to access proxyObj on the client-side will be treated as an error. If the remote object’s reference count reaches zero and has a nonstatic release() method that takes no arguments, then this method will be called automatically on the server side, giving you the opportunity to release any resources held by the object. As a fallback measure, when a session terminates, all its proxy objects are automatically released. sys.release(acc) Example: 10.10.2.7 Maths vague sys.min (...) Summary: Returns the minimum of zero or more quantities. The arguments may be integers, reals, characters, or dates. For character arguments, the character code is used. For date arguments, the timestamp equivalent of the date is used. The returned value maintains its original type. Returns null when no arguments are provided. sys.min(10, 20.3, 'a', sys.date()) Example: returns: 10 Core Reference 413 vague sys.max (...) Summary: Same as sys.min() except that the maximum quantity is returned. sys.max(10, 20.3, 'a', sys.date()) Example: returns: 2008-02-24 13:34:16.582000000 vague sys.toNum (vague value) Summary: Returns the result of converting value to a number. value may be a string, date, integer, or real. A date is converted to its numeric equivalent expressed in milliseconds. sys.toNum("12.4") Example: returns: 12.4 vague sys.trunc (real number, int decimalPlaces = 0) Summary: Returns the result of truncating number to the number of decimal places denoted by decimalPlaces. sys.trunc(12.67569, 2) Example: returns: 12.67 vague sys.round (real number, int decimalPlaces = 0) Summary: Returns the result of rounding number to the number of decimal places denoted by decimalPlaces. This method uses the ‘round half to even’ method, as specified in http://en.wikipedia.org/wiki/Rounding. sys.round(12.67569, 2) Example: returns: 12.68 real sys.sqrt (vague number) Summary: Returns the square root of number (which may be an integer, real, or character). sys.sqrt(7) Example: return: 2.6457513110645907 real sys.log (vague number) Summary: Returns the natural logarithm of number (which may be an integer, real, or character). sys.log('A') Example: returns: 4.174387269895637 real sys.sin (vague number) Summary: Returns the trigonometric sine of number expressed in degrees (which may be an integer, real, or character). sys.sin(90) Example: returns: 1.0 414 JavaGram Agile Development real sys.cos (vague number) Summary: Returns the trigonometric cosine of number expressed in degrees (which may be an integer, real, or character). sys.cos(0) Example: returns: 1.0 real sys.tan (vague number) Summary: Returns the trigonometric tangent of number expressed in degrees (which may be an integer, real, or character). sys.tan(45) Example: returns: 1.0 real sys.distance (real lat1, real lng1, real lat2, real lng2) Summary: Returns the distance (in meters) between two geo-coordinates specified in latitude and longitude. sys.distance(0, 0, 1, 0) Example: returns: 111194.92664455873 10.10.2.8 Date string sys.timezone (string zone = null) Summary: Returns the current time zone. You can also modify the current time zone by passing a valid zone string. Valid examples are: Example: "GMT" "GMT+10" "America/Los_Angeles" "America/New_York" "Australia/Melbourne" sys.timezone("Australia/Melbourne") date sys.date () date sys.date (int year, int month, int day, int hour = 0, int minute = 0, int second = 0, int nano = 0) date sys.date (int num, boolean pure = false) date sys.date (string str) Summary: Returns the specified date. A date can be specified in a variety of formats. The first version returns the current date/time. The second version returns a date/time as specified by the arguments. The third version accepts a numeric date, which is milliseconds since UNIX epoch time (1st of Jan 1970). Similarly, casting a date to int gives its equivalent value in milliseconds. When pure is true, no time component is returned. The last version accepts a string date. Core Reference 415 Example: sys.date() returns: 2008-02-25 08:40:09.252000000 sys.date(2000, 0, 22) returns: 1999-12-22 sys.date(600000000) returns: 1970-01-08 08:40:00 sys.date("1999-1-22 14:10:55") returns: 1999-01-22 14:10:55 vague sys.dateParts (date d, map parts = null) Summary: Returns the individual parts of the date d as a map. When parts is non-null, this map is modified and returned rather than creating a new map. weekDay starts on Sunday (=1). sys.dateParts(sys.date()) Example: returns: [$day => 25, $hour => 8, $minute => 45, $month => 2, $nano => 357000000, $second => 12, $weekDay => 2, $year => 2008, $yearDay => 56] void sys.sleep (int millis) Summary: Causes the current thread to wait for millis milliseconds. sys.sleep(2000) // wait for 2 seconds Example: 10.10.2.9 Collections vector sys.insert (vector vec, int at, vague elem) Summary: Inserts elem at the position denoted by at in vec, and returns the vector. at must be in the range 0 to the length of vec. sys.insert(["one", "two"], 1, "xx") Example: returns: ["one", "xx", "two"] vector sys.append (vector vec, vague elem) Summary: Appends elem to the end of vec, and returns the vector. sys.append(["one", "two"], "three") Example: returns: ["one", "two", "three"] list sys.pair (vague head, list tail) Summary: Creates and returns a list using the specified head and tail. sys.pair(10, $(20, 30)) Example: returns: $(10, 20, 30) vague sys.head (list ls) Summary: Returns the head of the list denoted by ls. When the latter is null, null is returned. 416 JavaGram Agile Development Example: sys.head($(10, 20, 30)) returns: 10 list sys.tail (list ls) Summary: Returns the tail of the list denoted by ls. When the latter is null, null is returned. sys.tail($(10, 20, 30)) Example: returns: $(20 30) list sys.setTail (list ls, list tail) Summary: Replaces the tail of the list denoted by ls, with tail, and returns the resulting list. The latter may be null. sys.setTail($(10, 20, 30), $(100)) Example: returns: $(10, 100) vague sys.remove (vague collection, vague elem, symbol scope = $first) Summary: Removes elem from collection (which may be a vector or map) and returns the modified collection. If the latter is a map then scope is irrelevant; the key matching elem is removed from the map. If collection is a vector then elem is removed as specified by scope, which may be one of: $first (removes the first occurrence of elem) $last (removes the last occurrence of elem) $at (removes the element whose position is denoted by elem) $all (removes all occurrences of elem) sys.remove(["one", "two", "one"], "one", $all) Example: returns: ["two"] sys.remove([1 => "one", 2 => "two"], 1) returns: [2 => "two"] boolean sys.member (vague value, vague collection) Summary: Returns true if value is a member of collection. The latter may be a list, vector, or map. A value is a member of a list or vector if the latter has an element of equal value. A value is a member of a map if the map has a key of equal value. sys.member("two", ["one", "two", "three"]) Example: returns: true sys.member(2, [1=>"one", 2=>"two", 3=>"three"]) returns: true vector sys.mapKeys (map m, vector keys = null) Summary: Returns the keys of map m as a vector. When keys is non-null, this vector is updated and returned. Otherwise, a new vector is created and returned. sys.mapKeys([1=>"one", 2=>"two", 3=>"three"]) Example: returns: [1, 2, 3] Core Reference 417 vector sys.mapValues (map m, vector values = null) Summary: Returns the values of map m as a vector. When values is non-null, this vector is updated and returned. Otherwise, a new vector is created and returned. sys.mapValues([1=>"one", 2=>"two", 3=>"three"]) Example: returns: ["one", "two", "three"] vector sys.sort (vector vec, boolean ascending = true, vector keys = null) Summary: Sorts and returns the vector denoted by vec. The vector elements should be either all atomic or all maps or objects. Sorting is done in ascending order, unless you pass false for ascending. If the elements are maps or object then pass a vector of the keys to be used for sorting. Each key must be a symbol, or a list of one symbol; the latter causes that key to be sorted in the reverse order. vector v = [ Example: [$name=>"Peter", $age=>20, $sex=>$male], [$name=>"Linda", $age=>14, $sex=>$female], [$name=>"Linda", $age=>23, $sex=>$female] ]; sys.sort(v, true, [$name, ($age)]) returns: [[$age => 23, $name => "Linda", $sex => $female], [$age => 14, $name => "Linda", $sex => $female], [$age => 20, $name => "Peter", $sex => $male]] int sys.search (vector vec, vague item, boolean ascending = true, symbol key = null) Summary: Searches the vector denoted by vec, looking for item. The vector must already be sorted according to the order denoted by ascending. The vector elements should be either all atomic or all maps or objects. If the vector elements are maps or objects then key should specify the key on which the vector is sorted (and to be used for searching). Returns the index of the closest-matching element, or null if no is match found. sys.search(v, "Peter", true, $name) Example: returns: 2 int sys.find (vector vec, vague item) Summary: Returns the index of vec element that is equal to item, or null if there is no match. sys.find(["one", 10, $name, 23.2], 10) Example: returns: 1 void sys.clear (vague collection) 418 JavaGram Agile Development Summary: Clears the contents of collection (which may be a map or vector, but has no effect when given null). If collection is a string then it’s assumed to denote a partition name, in which case the partition files are cleared from the local cache, thus releasing the memory used by them in the cache. Example: sys.clear(m) int sys.length (vague value) Summary: Returns the length of value. The latter may be a collection, string, or class instance. The length of a class instance is the count of its fields (including those in its super classes, but excluding all static fields). Returns 0 when given null. sys.length("hello") Example: returns: 5 10.10.2.10 Miscellaneous symbol sys.mode () Summary: Returns the runtime mode of the current JavaGram process. This is one of: $default, $gui, $server, $ide. sys.mode() Example: string sys.clipboard (string data = null) Summary: When the parameter is null, it returns the contents of the system clipboard, or null if there is no data on the clipboard. Otherwise, it sets the system clipboard contents to the string denoted by data, and returns null. sys.exit(1) Example: native sys.env () Summary: Returns the internal environment of the current JavaGram thread as a native object. This can be used in subsequent calls to the sys.java() method. sys.env() Example: void sys.exit (int status = 0) Summary: Causes the JavaGram process to terminate with the specified status code. sys.exit(1) Example: Exception sys.getAsyncErr () Summary: Returns the exception raised by the last asynchronous call, or null. Use this in your async callbacks to get hold of the exception. sys.getAsyncErr() Example: Core Reference 419 void sys.email (string mailhost, int port, string from, string to, string cc, string bcc, string subject, string message, vector<string> attach = null, string contentType = null, string charSet = null) Summary: Sends an email message with the specified data, using the SMTP server denoted by mailhost and port. To include attachments, pass a vector of file paths for attach. For other than text/plain content, pass a value for contentType (e.g., "text/html"). For other than us-ascii character set, pass a value for charSet (e.g., "UTF-8"). sys.email("smtp.acme.com", 25, "john@acme.com", "someone@gmail.com", Example: "", "", "My Subject", "My Message...") vector<list> sys.fields (object obj, boolean incBases = true, boolean incStatics = false) Summary: Returns a vector of all the fields for obj, each of which is a list of the form: ($fieldName, "fieldType", fieldValue). When incBase is true, all base class fields are also included. When incStatics is true, all static fields are also included. sys.fields(person) Example: vague sys.get (vague data, symbol key) Summary: Returns the value in data (which may be an object, map, or GUI component) as denoted by the field, key, or property key. Any object field can be accessed by this method, regardless of its access qualifier. When data is a map and key doesn’t exist, null is returned. When data is an object and key doesn’t exist, void is returned. sys.get(person, $dob) Example: void sys.set (vague data, symbol key, vague value) Summary: Sets the value in data (which may be an object, map, or GUI component) as denoted by the field, key, or property key, to value. Any object field can be accessed by this method, regardless of its access qualifier. sys.set(person, $dob, [#1992-10-22]) Example: boolean sys.getable (vague data, symbol key) Summary: Returns true if data (which may be an object, map, or GUI component) has the field, key, or property denoted key. For an object, this is the case when key is actually defined. For a GUI component, this return true if the GUI element has the property, even if it’s write-only. sys.getable(person, $dob) Example: boolean sys.setable (vague data, symbol key) 420 JavaGram Agile Development Summary: Returns true if data (which may be an object, map, or GUI component) has the field, key, or property denoted key. For an object, this is the case when key is actually defined and not final. For a map this returns true if the key type matches that expected by the map. For a GUI component, this return true if the GUI element has the property, even if it’s read-only. Example: sys.setable(person, $dob) void sys.assign (object left, object right, vague scopeClass) Summary: Assigns the fields of right to the fields of left, according to the non-static fields of scopeClass, but excluding the base classes fields of scopeClass (if any). Both left and right must be of type scopeClass or derived from it. sys.assign(customer, person, Person) Example: vague sys.call ([stream s,] vague objOrClass, string method, ...) Summary: Dynamically calls a class method. An optional first argument can be used to specify a server stream, to which a remote call is routed (may be null). The next argument must denote either a class object (if the method is nonstatic) or a class (class name or symbol). The next argument must specify a method name (to call a constructor, use "new"). Subsequent arguments are intended for the method. If you want to pass all the arguments as a single list, add () to the end of the method name, e.g., "foo()" instead of "foo". The call is fully type checked and if it resolves unambiguously to a valid method then it’s performed. The return value is whatever the method returns. Any class method can be accessed by this method, regardless of its access qualifier. sys.call($Test, "foo", 10, "hello") Example: sys.call($Test, "foo()", $(10, "hello")) sys.call($Point, "new", 100, 120) // Constructor call boolean sys.callable ([stream s,] vague objOrClass, string method, ...) Summary: Similar to sys.call(), except that it just checks that a matching method exists. Returns true if the method can be called successfully. sys.callable($Test, "foo", 10, "hello") Example: vague sys.java (vague obj, string retType, string member, vague args = null) Core Reference 421 Summary: Causes a Java member (within JAG or a class accessible via CLASSPATH) to be accessed. The following rules apply: When args is $field, member is treated as a field. Otherwise, it’s treated as a method. When obj is a string, it is treated as a class path, and the method is assumed to be static (or a constructor). Otherwise, the method is treated as a method of obj (which should be a Java object returned by an earlier call to sys.java()). When obj is a native value (e.g., GUI component), the underlying Java value is used. For example, for a text field object, the underlying Java JTextField object is used. When retType is "native", the return value will be a Java object reference. Otherwise, it’s converted to a JavaGram value. When method is "new", it’s resolved to a class constructor. If the Java method has parameters then the type and name of each parameter must be specified in the args list or vector. Valid parameter types and corresponding JavaGram argument types are as follows: Par Type JavaGram Arg Type $boolean $byte $short $int $long $char $float $double $void $string boolean int int int int int real real void string full-class-name Java object returned by sys.java() In a Flash client, sys.java() is emulated. It works for classes, fields, and methods that are present in the Flash JavaGram runtime. Example: sys.java("jag.misc.JagTimer", "native", "new", $($string, "abc")) void sys.hook (symbol task, vague klass, string method) 422 JavaGram Agile Development Summary: This method is for use in JavaGram clients. It provides a callback hook for task. The callback is the method denoted by method in the class denoted by klass. Either the class must be singleton or the method static. The method must take one argument of type vague and return void. Valid values for task are: $ready. Callback is called when the last task has been completed. The argument is always null. $download. Callback is called when a client sends a file download request to a server. The relative file path is passed as the argument. $upload. Callback is called when a client sends a file upload request to a server. The relative file path is passed as the argument. $load. Callback is called when a client starts loading a script. The relative script path is passed as the argument. $remote. Callback is called when a client starts executing a remote method. The method name is passed as the argument. Example: singleton Class MyApp extends GuiApp { public void onLoadTask (vague script) { //... } public static void main () { sys.hook($load, MyApp, $onLoadTask); } } Core Reference 423 11 GUI Reference This chapter specifies the Graphical User Interface (GUI) features of JavaGram. Use this chapter to look up GUI topics once you’ve become familiar with the language. For a tutorial style introduction to JavaGram’s GUI capabilities please refer to Chapter 4. With a couple of exceptions, all GUI elements and methods described in this chapter are also available in the Flash JavaGram runtime. Where an element or method is not available, it’s marked with the icon . 11.1 Markup JavaGram offers a completely different style of GUI programming to Java’s Swing. Whereas GUI programming in Swing is procedural, JavaGram allows you to define a GUI declaratively. This has a number of advantages: you write a lot less code, the code is much more readable, and the code readily portrays the hierarchical structure of the GUI. As a result, creating sophisticated GUIs in JavaGram is much easier than in Java. GUI members are defined using a markup notation (similar to text members). Semantically, however, GUI members behave like class fields. Here is a simple example: <jag> class Sample { final <App app lookAndFeel=$windows> <Frame title="Sample" width=200 height=100 event=frameHandler> <Panel> <Layout.border/> <Field.text value="Sample field data" lay=$north/> <Button title="OK" lay=$south action={sys.exit()}/> </Panel> </Frame> </App> protected void frameHandler (native comp, symbol event) { sys.exit(); } public static void main () { Sample s = new Sample(); gui.run(s.app); } } </jag> Because this is a GUI program, it must be run using javaw.exe and the jag.gui.Gui Java class rather than java.exe and jag.run.Env. When run, it will display the following frame. 424 JavaGram Agile Development The <App> element specifies an application. Every GUI element can have an identifier (app in this case), which behaves like a class field. The identifier is mandatory for toplevel elements (such as <App>) and optional for inner-elements (such as <Frame> in the above example). Each element also accepts a set of properties. All properties are typed and must receive a value of their nominated type. For example, the lookAndFeel property expects a symbol value. Unlike fields, a final GUI element means that its properties are final and therefore may not be assigned to. Within <App>, we have a <Frame> element, which will display an application main frame. The event property for the frame refers to a class method that will process events generated by the frame. Within the frame, we have a <Panel> element which is specified to have a border layout. Within the panel, we have a <Field.text> element that’s laid north, and a <Button> element that’s laid south. The action property for the button is defined to exit the application when the button is pressed. Note how we run the application: we create an instance of the class and call gui.run() on the app element. There is another way of doing the same, explained later in this chapter. 11.1.1 Element Qualifiers A GUI element may be specified with any of the qualifiers allowed for fields, except for getable and settable. The delayed qualifier is particularly useful in situations where an element property value is specified as an expression that refers to class fields that are initialized by a constructor. This dependency is honored by delaying the creation of the element until the constructor has done its job. 11.1.2 Element Identifiers Within each element markup and before the element properties, you can specify an element identifier. This must be unique within the class scope, and is mandatory for toplevel elements, but optional for child elements. Once defined, you can refer to this identifier (as if it were a class field) to uniquely identify the GUI element. For example, the GUI element <Field.text name value="John Smith"/> GUI Reference 425 is uniquely identified by the name identifier. If you have a programmatic reference to an element then you can use it to access the element identifier. For example, given a variable nameField that denotes the above element, nameField.id will give $name. This is a read-only value, so you can’t assign to nameField.id. 11.1.3 Element Properties For each GUI element, you can specify a set of properties to customize its appearance or behavior. Each property is specified in the usual markup syntax: PropertyName = PropertyValue For example, the button <Button title="OK" lay=$south action={sys.exit()}/> has three properties: title specifies the title appearing on the button, lay specifies how the button is to be laid out in its parent, and action specifies the expression to be evaluated when the button is pressed. The documentation of each element type lists the permissible set of properties for the element. Where an element inherits from another element type, the properties of the latter are also supported by the former. All properties are typed. Therefore, the type of the value you specify for a property must match the type expected by the property. In most cases, a property value is a literal, but it doesn’t have to be. You can also use expressions, in which case you must enclose the whole expression in a pair of braces. Most properties are evaluated when the element is created. Some properties, however, have delayed evaluation. The action property, for example, is not evaluated when the button is created, but when the button is pressed by the user. 11.1.3.1 Event Handlers Some elements allow you to define an event handler property. The syntax for this is: event = EventMethodName The event method must be defined in the same class (or in a super class) and have the following signature: void eventHandler (native comp, symbol event) { //… } 426 JavaGram Agile Development When the element raises an event, this method is automatically called. The comp parameter is set to the element raising the event, and the event parameter is set to a symbol that represents the event. It’s perfectly valid for multiple elements (of the same type) to share the same event handler. In this case, you can use the comp parameter to find out which one has raised the event. 11.1.3.2 Data Models Some elements (such as lists, combos, tables) display composite data. For example, a combo may contain a list of names to choose from. JavaGram provides two ways of specifying this data: directly or via a data model. To use the direct approach, simply set the element’s data property to a vector that contains the values. For example: <Combo color data=["Red", "Green", "Blue"]/> or <Combo color data={getColors()}/> The direct approach is easier but less flexible because the entire data set must be available in advance. Also, if the data set changes, you’ll have to re-assign it to data for it to take effect. Using a data model overcomes these limitations. To use a data model, set the element’s model property to the name of a method in the same class that implements the data model. For example: <Combo color model=colorModel/> The signature of the data model method is dictated by the element type. For example, for a combo, the syntax is: vague colorModel (native combo, symbol cmd, int idx) { //... } The combo parameter is set to the specific combo for which the model is invoked, thus allowing you to share the same model across multiple elements of the same type. The cmd parameter is set to a model command, which depends on the element type. For a combo, for example, cmd may be $count (to return the number of entries in the combo) or $get (to return a specific entry). The idx parameter is set to the zero-based index of an entry when cmd is $get. The method must return a value in response to cmd. When an application needs to display the contents of a combo, the framework calls the data model (possibly a number of times) to get the actual items to display. Therefore, the data can be easily dynamic (e.g., it can be sourced from a database or a server). GUI Reference 427 11.1.4 Boiler Plates If many elements in a class share the same properties then you can eliminate the repetition and simplify your code by using a boiler plate. For example, <Plate Req fgColor="0xFF0000" tooltip="Required"/> defines a boiler plate named Req that has two properties. Here is an example of using it in an element: <Req:Label title="Surname"/> Semantically, this is equivalent to writing: <Label fgColor="0xFF0000" tooltip="Required" title="Surname"/> One limitation of boiler plates is that they only be used to capture properties of a <Comp> element; the latter being the base class of most elements. 11.2 Element Types Elements specified as <Elem> are containers – they can contain other elements, like this: <Menu> <MenuItem .../> </Menu> Elements specified as <Elem/> are non-containers. Where an element is specified to extend another element, this implies that the former inherits the properties of the latter. 11.2.1 Abstract Elements Abstract elements capture common behaviors shared by a number of element types. They can’t be directly instantiated. All abstract elements are shown in italics. <Comp/> SUMMARY: PROPERTY Most GUI elements inherit from this element, directly or indirectly. symbol key vague value vague handy The key for binding this element to data (e.g., $age). The actual value to which this element is bound (e.g., 21). An arbitrary value associated with the component (defaults to null). This property is also available for elements that are not derived from <Comp>. The type of the value that this element can be bound to (e.g., int). Type type 428 DESCRIPTION JavaGram Agile Development symbol lay native font string fgColor string bgColor int x int y int width int height Comp center string tooltip boolean focus symbol cursor symbol border vague enable vague visible Id event boolean valid boolean refresh native parent EVENTS: How the element is laid in its parent container. For a <Panel> or <Dialog> container, permissible values are: $center, $north, $south, $east, $west. For a <Pane.split> container, permissible values are: $north, $south, $east, $west. A <Font> for displaying any text in the element. The foreground color as a hexadecimal string representation of an RGB color (e.g., "0xFF0000" or "#FF0000" for red). If there are more than 6 hex digits, the left most digits represent an alpha value. For example, "0x44FF0000" represents a red color with an alpha value of 0x44, making it fairly transparent. The background color (follows the same format as fgColor). The left coordinate of the element (in pixels). The top coordinate of the element (in pixels). The width of the element (in pixels). The height of the element (in pixels). Centers this element relative to the element this property is assigned to. The tooltip for the element. When true, the element has the input focus. The cursor for the element; must be one of $default, $wait, or $hand. The physical border of the element; must be one of: $empty, $line, $bevel, $etched, or $round (default value depends on the current look and feel). An expression which is evaluated when gui.maintain() is explicitly or implicitly invoked. Enables the element when the expression evaluates to true, and disables it when it evaluates to false. Defaults to true. An expression which is evaluated when gui.maintain() is explicitly or implicitly invoked. Shows the element when the expression evaluates to true, and hides it when it evaluates to false. Defaults to true. The event handler for this element, which must be a method in the same class. The events raised by the element will depend on the element type. Returns true if component is valid (a component is valid when it is correctly sized and positioned within its parent container and all its children are also valid). Set to false to force the component to relayout. Setting it to true has no effect. Refreshes the element by repainting it. The direct parent of this component, or null. $bind is raised after the element is bound to data. $save is raised before changes to element data are saved. <Container> extends <Comp> GUI Reference 429 SUMMARY: GUI elements that are containers inherit from this element, which implements containment functionality. In some situations, you may want to dynamically add/remove elements to/from a container. You can do this, using the += and -= operators. PROPERTY DESCRIPTION boolean report Indicates whether this container should take part in report generation (see gui.toHtml()). Defaults to true. When set to true, it enables the container’s binding (as denoted by its key) to be a vector. The container is bound to a vector’s element whose index is denoted by the zero-based index of the container relative to its parent container. boolean indexed EXAMPLE: node1 += node2 node1 -= node3 // add node2 as a child of node1 // remove child node3 from node1 <Layout/> SUMMARY: Creates a layout for a panel. A layout must appear as the first element inside the panel. <AbsButton/> extends <Comp> SUMMARY: All button elements inherit from this element. PROPERTY DESCRIPTION string title vague image The title displayed inside the button. Optional image to be displayed to the left of the button title. This may be either an <Icon> or the path of a GIF file. The alignment of button contents; must be one of $center, $north, $south, $east, $west, $northEast, $northWest, $southEast, or $southWest (defaults to $center). The alignment of button text relative to image; must be one of $center, $north, $south, $east, $west, $northEast, $northWest, $southEast, or $southWest (defaults to $east). The horizontal inner-margin for the left and right of the button. The vertical inner-margin for the top and bottom of the button. When true, the button is selected (pushed, ticked, etc.). Defaults to false. An arbitrary expression (action handler) which is evaluated when the button is pressed. Push buttons don’t have event handlers; use the action property instead. For other buttons, please note that if you define the action property as well the event property, only the most recently-set of these two will handle an activation of the button. For clarity, it’s advisable to use only one of these two properties. $select is raised when a check box or radio button is selected or deselected. symbol align symbol textPos int hGap int vGap boolean select vague action EVENTS: <AbsText/> extends <Comp> 430 JavaGram Agile Development SUMMARY: PROPERTY boolean readOnly boolean lineWrap string insert boolean ime EVENTS: DESCRIPTION When true, the text can’t be edited (defaults to false). When true, lines wrap around (defaults to false). Setting this property to a string causes the string to be inserted at the current caret position. You cannot get this property. Useful for fields that require Chinese, Korean, or Japanese input, as activated by the IME setting of the machine. Setting this property to true causes a text dialog to be displayed that’s capable of receiving keystrokes in the aforementioned languages. When the user OK’s the dialog, the field is updated accordingly and an $ime event is raised. You cannot get this property. $key is raised when a keyboard input occurs. $active is raised when the field is activated (e.g., by clicking in it). $focus is raised when the field loses focus. $ime is raised when the text is changed via the IME text dialog. <AbsNumeric/> extends <Comp> SUMMARY: All numeric fields inherit from this element. Data within the field is formatted and non-numeric data is not accepted. Pressing the right mouse button on the field brings up a pop-up menu of three options: Calculator (enabled if the field is not a date or time field). Displays a visual calculator for calculating a value for the field. Calendar (enabled if the field is a date field). Displays a visual calendar for choosing a date for the field. Clock (enabled if the field is a time field). Displays a visual clock for choosing a time for the field. Clear (always enabled). Clears the contents of the field. As a shortcut, double-clicking an editable numeric field brings up the calculator or the calendar, depending on the field type (see derived field types). PROPERTY DESCRIPTION boolean readOnly When true, the field is read-only. GUI Reference 431 symbol align string format boolean auto EVENTS: The alignment of data within the field; must be one of $center, $east, or $west (defaults to $west). The format of the data within the field, having a similar effect to sys.format(). When set to true, a single left-click in the field also causes the field’s corresponding edit dialog to open (defaults to false). $active is raised when the field is activated (e.g., by clicking in it). $modified is raised when the field value is modified. $focus is raised when the field loses focus. <AbsGraph/> extends <Comp> SUMMARY: All graph elements inherit from this element. PROPERTY DESCRIPTION string title boolean legend symbol legendLay The graph title. When true, a legend is displayed for the graph (defaults to true). Specifies where the graph legend should appear; must be one of: $south, $southWide or $east (defaults to $east). $southWide causes the legend to be drawn as one line, rather than stacked as multiple lines. $select is raised when the user clicks on a graph canvas. $drill is raised when the user double-clicks on a graph canvas. EVENTS: 11.2.2 Application Elements <App> SUMMARY: Represents a Swing application. The only element allowed inside an <App> is a <Frame>, which represents the application’s main frame. PROPERTY DESCRIPTION string title symbol lookAndFeel native stdOut native stdErr The application title. The application look and feel; must be one of $default, $windows, $motif, or $metal (default is platform dependent). When true, the application frame is displayed (defaults to true). When true, the application frame is centered on the screen (defaults to false). An <Area.text> for redirecting standard output to. An <Area.text> for redirecting standard error to. EXAMPLE: <App myApp title="My App" lookAndFeel=$windows/> boolean visible boolean center <Font/> SUMMARY: PROPERTY Creates a font with the specified properties. string name The font name (defaults to "System"). 432 DESCRIPTION JavaGram Agile Development Symbol style int size boolean embed The font style; must be one of: $plain, $bold, $italic, $boldItalic (defaults to $plain). The font size (defaults to 10). Used by Flash clients (has no effect in native clients). If you use an embedded font (see gui.loadFont()), you must set this to true (defaults to false). EXAMPLE: <Font codeFont name="Courier" size=12/> 11.2.3 Window Elements <Frame> extends <Container> SUMMARY: Creates an application main frame. You typically add a <MenuBar> and a <Panel> to a frame, and use the latter as the container for whatever needs to be displayed in the frame. You can also add a <Layout> to a frame to define the frame content layout. PROPERTY DESCRIPTION boolean sizeable string title vague image When true, the frame can be resized by the user (defaults to true). The title displayed at the top of the frame. Optional icon image to be displayed next to the title. This may be an <Icon> reference or the path of an image GIF file. Defines the default button for the frame. This button is pressed when the user hits the enter key. The visual state of the frame, which may be one of: null (normal), $max (maximized), $min (minimized/iconified). $close is raised when the user attempts to close the frame. Button enter symbol state EVENTS: EXAMPLE: <Frame myFrame title="Test" width=800 height=600> <MenuBar> //... </MenuBar> <Panel> //... </Panel> </Frame> <Dialog> extends <Container> SUMMARY: Creates a dialog window. PROPERTY DESCRIPTION native owner boolean fixed boolean modal boolean show string title vague image The dialog owner; this is typically set to a <Frame> or another <Dialog>. When true, the dialog has a fixed size (defaults to false). When true, the dialog is modal (defaults to false). When true, the dialog is visible (defaults to false). The title displayed at the top of the dialog (defaults to ""). Optional icon image to be displayed next to the title. This may be an <Icon> reference or the path of an image GIF file. Defines the default button for the dialog. This button is pressed when the user hits the enter key. Button enter GUI Reference 433 EVENTS: EXAMPLE: $close is raised when the user attempts to close the dialog. <Dialog d owner={myFrame} width=400 height=300 enter={okButn}> <Layout.border/> //... <Button okButn lay=$south title="OK" action={d.show=false}/> </Dialog> <Alert/> SUMMARY: PROPERTY Displays an alert dialog window for providing feedback to the user. string string native symbol The title displayed at the top of the alert box. The message displayed inside the alert box. The alert owner; this is typically set to a <Frame> or a <Dialog>. This may be set to one of: $error (for displaying an error message) $info (for displaying feedback information) $warning (for displaying a warning message) $question (for asking a question) $plain (for any other purpose) A vector of up to three strings to be used as button labels in the alert box. The zero-based index of the default button (pressed when the user hits the enter key). The result handler for this element, which must be a method in the same class, having one int parameter. When the alert is dismissed, this method is called with the zero-based index of the pressed button (or null if the alert has just one button or no button is pressed) as argument. In a synchronous GUI, you don’t need such a handler – use the value returned by the show property instead. You can’t set this property. When you get this property, the alert is displayed and the zero-based index of the button the user presses is returned. When the alert has just one button or when it’s closed without pressing a button, null is returned. In a Flash client, always null is returned. title message owner kind vector options int enter Id result int show EXAMPLE: DESCRIPTION <Alert alert owner={myFrame} title="Sample" message="Alert Message" kind=$warning options=["One", "Two", "Three"] enter=1/> <Alert.ync/> extends <Alert> SUMMARY: Same as <Alert> except that the kind property is set to $question and the options property is set to ["Yes", "No", "Cancel"]. EXAMPLE: <Alert.ync alert owner={myFrame} title="Sample" message="Alert Message" enter=1/> <FileChooser/> 434 JavaGram Agile Development SUMMARY: Displays a File Chooser dialog window for choosing one or more files or directories from the file system. PROPERTY DESCRIPTION boolean multiSelect When true, allows the user to select multiple items (defaults to false). When true, allows the user to select a file (defaults to true). When true, allows the user to select a directory (defaults to false). The title appearing at the top of the dialog. The dialog owner; this is typically set to a <Frame> or a <Dialog>. This may be set to $open (for opening a file) or $save (for saving a file). Defaults to $open. The initial path for the File Chooser (defaults to the current directory). The file extension(s) to act as filter (defaults to none). This may be a string (e.g., "doc") or a vector of strings. The description for the file filter (defaults to "Filtered Files"). The custom label appearing on the dialog’s main button. The preferred file path format. May be set to $unix or $dos (defaults to $unix). The result handler for this element, which must be a method in the same class, having one vague parameter. When the dialog is closed by pressing the OK button, this method is called with the selection result as argument. In a synchronous GUI, you don’t need such a handler – use the value returned by the show property instead. You can’t set this property. When you get this property, the dialog is displayed and the absolute path of the file or directory selected is returned. When multiSelect is true, the result is a vector of paths. If nothing is selected, null is returned. In a Flash client, always null is returned. boolean file boolean dir string title native owner symbol kind string path vague ext string desc string label symbol format Id result vague show EXAMPLE: <FileChooser fc owner={myFrame} kind=$open title="Choose file(s)" path="C:/JavaGram/" ext=["doc","zip"] multiSelect=true desc="DOC and ZIP files" label="Do it"/> <ColorChooser/> SUMMARY: Displays a Color Chooser dialog window for selecting a color from a color menu. PROPERTY DESCRIPTION string title native owner string color The title appearing at the top of the dialog. The dialog owner; this is typically set to a <Frame> or a <Dialog>. The selected color as a hexadecimal string (e.g., "0x00FF00"). You can also set the initial color selection by assigning to this property. GUI Reference 435 Id result The result handler for this element, which must be a method in the same class, having one string parameter. When the dialog is closed by pressing the OK button, this method is called with the selected color as argument. In a synchronous GUI, you don’t need such a handler – use the value returned by the show property instead. string show You can’t set this property. When you get this property, the dialog is displayed and the selected color is returned. If no color is selected, null is returned. In a Flash client, always null is returned. EXAMPLE: <ColorChooser cc owner={myFrame} title="Choose your color" color="0xAABBFF"/> 11.2.4 Layout Elements <Layout.flow/> extends <Layout> SUMMARY: Creates a flow layout for its <Panel> parent. Panel children are arranged left to right and top to bottom. This is the default layout for a panel with unspecified layout. PROPERTY DESCRIPTION int hGap int vGap symbol align The horizontal gap for the left and right of the layout (defaults to 0). The vertical gap for the top and bottom of the layout (defaults to 0). Specifies how elements are to be aligned in the panel, and may be one of $east, $center, or $west. EXAMPLE: <Layout.flow align=$west/> <Layout.border/> extends <Layout> SUMMARY: Creates a border layout for its <Panel> parent. Panel children are arranged north, south, center, east, or west according to the lay property of each element. PROPERTY DESCRIPTION int hGap int vGap The horizontal gap for the left and right of the layout (defaults to 0). The vertical gap for the top and bottom of the layout (defaults to 0). EXAMPLE: <Layout.border hGap=2 vGap=2/> <Layout.card/> extends <Layout> SUMMARY: Creates a card layout for its <Panel> parent. Panel children (which must be containers, each having a unique identifier) are stacked like a deck of card so that only one can be visible at a time. Initially, the first card is visible, but the visible card can be changed by setting the top property. PROPERTY DESCRIPTION int hGap int vGap native top The horizontal gap for the left and right of the layout (defaults to 0). The vertical gap for the top and bottom of the layout (defaults to 0). Denotes the currently visible child container. EXAMPLE: <Layout.card/> <Layout.grid/> extends <Layout> 436 JavaGram Agile Development SUMMARY: Creates a grid layout for its <Panel> parent. Panel children are arranged left to right and top to bottom in a fixed grid of a given number of rows and columns. PROPERTY DESCRIPTION int int int int The horizontal gap for the left and right of the layout (defaults to 0). The vertical gap for the top and bottom of the layout (defaults to 0). The number of rows for the grid (defaults to 1). The number of columns for the grid (defaults to 1). hGap vGap rows cols EXAMPLE: <Layout.grid rows=2 cols=3/> <Layout.gridBag/> extends <Layout> SUMMARY: Creates a grid bag layout for its <Panel> parent. Panel children are arranged within the grid bag using the <Lay> markup. EXAMPLE: <Layout.gridBag/> <Layout.horizontal/> extends <Layout> SUMMARY: Creates a horizontal layout for its <Panel> parent. Panel children are laid out along a horizontal axis. PROPERTY DESCRIPTION int gap The horizontal gap between the components (defaults to 0). EXAMPLE: <Layout.horizontal/> <Layout.vertical/> extends <Layout> SUMMARY: Creates a vertical layout for its <Panel> parent. Panel children are laid out along a vertical axis. PROPERTY DESCRIPTION int gap The vertical gap between the components (defaults to 0). EXAMPLE: <Layout.vertical/> <Lay> SUMMARY: This is used for laying out a child element inside a panel that has grid bag layout. It represents a cell of the grid bag. PROPERTY DESCRIPTION int row int col int rowSpan int colSpan symbol align The zero-based row of the cell in the grid bag. The zero-based column of the cell in the grid bag. The number of rows covered by the cell (defaults to 1). The number of columns covered by the cell (defaults to 1). The alignment of an element within the cell. It may be one of $north, $south, $center, $east, $west, $northEast, $northWest, $southEast, or $southWest (defaults to $center). May be one of the following: $none (this is the default). $horizontal (the element fills the horizontal space of the cell). $vertical (the element fills the vertical space of the cell). $both (the element fills the horizontal and vertical space of the cell). symbol fill GUI Reference 437 int margin real weight The margin on the four sides of the cell (defaults to 0). The weight of the cell in relation to other cells (defaults to 1.0). When the panel is resized, this value is used to determine how the cell should be resized in relation to other cells. Use 0.0 for very tight packing, or a value greater than 1.0 for generous packing. EXAMPLE: <Lay row=0 col=0 weight=0.1 margin=4 align=$east> <Label title="Name"/> </Lay> <Filler/> extends <Comp> SUMMARY: Creates an invisible element to be used for filling space when laying out elements in a panel. Use the width and/or height properties of <Comp> to specify the physical size of the filler. EXAMPLE: <Filler width=20> 11.2.5 Container Elements <Panel> extends <Container> SUMMARY: Creates a panel. A panel is the most commonly-used container because it can layout various elements in a number of different ways. Unless you specify a layout for a panel, it assumes a flow layout. If specified, a layout must appear as the first element within the panel. A panel is drawn without a visible border, unless it has a title or an explicit border. PROPERTY DESCRIPTION string title The title displayed at the top-left of the panel. A panel that has a nonempty title is drawn with a title bar. The link displayed at the top-right of the panel, provided the panel also has a non-empty title. $add event is raised when an element is added to the panel. $remove event is raised when an element is removed from the panel. $resize event is raised when the panel is resized. $link event is raised when the panel has a link and it’s clicked by the user. string link EVENTS: EXAMPLE: <Panel title="Client Details"> //... </Panel> <Panel.toggle> extends <Panel> SUMMARY: Creates a panel for containing a group of radio buttons or toggle buttons. Initially, none of the elements is selected. When an element is selected (by the user or programmatically) the previously selected element (if any) is automatically deselected. PROPERTY 438 DESCRIPTION JavaGram Agile Development vague select The zero-based index or the title of the selected element. The type of this property is dependent on the type property of the toggle panel. If the type is unspecified or is specified to be integer, then select is of int type. Otherwise, it is of string type. This is the same as the value property. EXAMPLE: <Panel.toggle options> //... </Panel> <Pane.scroll> extends <Container> SUMMARY: Provides scrolling capability for its single child element. The horizontal and vertical scrollbars are displayed on demand (i.e., when there is insufficient space in the scroll pane to display the child. PROPERTY DESCRIPTION boolean horizontal When false, the horizontal scrollbar is never shown (defaults to true). When false, the vertical scrollbar is never shown (defaults to true). The current value (thumbnail position) of the horizontal scrollbar. This is a number in the range hMin to hMax. The current value (thumbnail position) of the vertical scrollbar. This is a number in the range vMin to vMax. The minimum value of the horizontal scrollbar (defaults to 0). The maximum value of the horizontal scrollbar. The minimum value of the vertical scrollbar (defaults to 0). The maximum value of the vertical scrollbar. $hScroll event is raised when the user scrolls horizontally. $vScroll event is raised when the user scrolls vertically. boolean vertical int hValue int vValue int int int int hMin hMax vMin vMax EVENTS: EXAMPLE: <Pane.scroll> //... </Pane> <Pane.split> extends <Container> SUMMARY: Creates a split pane, which must contain two child elements. The pane is split into a top and bottom or left and right parts, the divider in between which can be adjusted by the user. The lay property of the child elements may be set to one of $north, $south, $east, or $west. PROPERTY DESCRIPTION vague divider When an integer, it denotes the location of the divider. When a real, it denotes the relative location of the divider as a value in the range 0.0 to 1.0. The latter is effective only after the split pane has been rendered. A value in the range 0.0 to 1.0 which specifies the relative weight of the left (or top) part for resizing purposes. real weight GUI Reference 439 boolean autoExpand native native native native west east north south EXAMPLE: When true, two small arrows are displayed on the divider which, when clicked, cause it to expand/contract (defaults to false). The west child of the split pane. The east child of the split pane. The north child of the split pane. The south child of the split pane. <Pane.split weight=0.5 autoExpand=true> <Panel lay=$west> //... </Panel> <Panel lay=$east> //... </Panel> </Pane> <Pane.tabbed> extends <Container> SUMMARY: Creates a multi-tab pane. Each child element must be a <Tab>. PROPERTY DESCRIPTION int count int select native front symbol align Returns the number of tabs in the pane. You can’t set this property. The zero-based index of the currently selected tab (defaults to 0). The currently-selected tab (defaults to the first tab). The alignment of the tab buttons, which may be one of $north, $south, $east, or $west (defaults to $north). When true, each tab button includes a ‘close’ check box to the right of its title which, when clicked, removes the tab (defaults to false). $select event is raised when a tab is selected. boolean closable EVENTS: EXAMPLE: <Pane.tabbed> <Tab> //... </Tab> //... </Pane> <Pane.accordion> extends <Container> SUMMARY: Similar to <Pane.tabbed> except that the tabs are shown as an accordion – a vertically arranged set of buttons where each button represents a tab and only one is expanded at a time. PROPERTY DESCRIPTION int count Returns the number of tabs in the accordion. You can’t set this property. The zero-based index of the currently selected tab (defaults to 0). $select event is raised when a tab is selected. int select EVENTS: 440 JavaGram Agile Development EXAMPLE: <Pane.accordion> <Tab> //... </Tab> //... </Pane> <Tab> extends <Container> SUMMARY: Creates a tab element (to be used as a child of a <Pane.tabbed>). PROPERTY string title vague image vague action EVENTS: EXAMPLE: DESCRIPTION The title displayed in the tab button. Optional image to be displayed to the left of the tab title. This may be either an <Icon> or the path of a GIF file. An arbitrary expression (action handler) which is evaluated when the tab is selected. $close event is raised when the tab’s parent is specified to be closable and the user clicks in the close button of the tab. The tab is not automatically closed, but you can remove it in response to this event. <Tab title="Details"> //... </Tab> <MenuBar> extends <Container> SUMMARY: Creates a menu bar to contain one or more menus. EXAMPLE: <MenuBar> <Menu> //... </menu> //... </MenuBar> <Menu> extends <MenuItem> SUMMARY: Creates a menu to contain zero or more menu items, or separators. EXAMPLE: <Menu> <MenuItem title="Open..."/> <Separator/> //... </Menu> <Menu.popup> extends <Comp> SUMMARY: Creates a popup menu to contain zero or more menu items, or separators. The menu is displayed when the right mouse button is held down in any of the menu’s owners. PROPERTY DESCRIPTION string title vague owner The menu title. The menu owner(s). This may be an element or a vector of elements. GUI Reference 441 native invoker You can’t set this property. You can get it to find out the owner element that has invoked the menu. EXAMPLE: <Menu.popup popup title="Poupup" owner={panel}> <MenuItem title="Open..."/> <Separator/> //... </Menu> <ToolBar> extends <Container> SUMMARY: Creates a toolbar to contain zero or more buttons or separators. PROPERTY DESCRIPTION boolean floatable symbol orientation When true, the toolbar can be floated (defaults to true). The toolbar’s orientation, which may be $horizontal or $vertical (defaults to $horizontal). This causes the toolbar to use a horizontal or vertical layout. Alternatively, you can define an explicit layout for the toolbar. For example, to arrange the toolbar buttons in a grid, define a <Layout.grid/> for it. As with a panel, the layout must be the first element within the toolbar’s definition. EXAMPLE: <ToolBar tb floatable=false> <Button title="Submit"/> <Separator/> //... </ToolBar> 11.2.6 Menu Item Elements <MenuItem/> extends <AbsButton> SUMMARY: Creates a menu item. This must be a child element of a menu. PROPERTY DESCRIPTION string accelerator string mnemonic A keyboard shortcut for the menu item. Valid examples are: "INSERT", "control DELETE", "alt shift X", "typed A". A virtual key shortcut for the menu item (e.g., "A", "DELETE", "F1"). EXAMPLE: <MenuItem title="Open..." accelerator="control O"/> <MenuItem.tick/> extends <MenuItem> SUMMARY: Creates a tick-able menu item. Use the select property to find out if the item is ticked. EXAMPLE: <MenuItem.tick title="Verbose"/> <MenuItem.radio/> extends <MenuItem> SUMMARY: Creates a radio menu item. Use the select property to find out if the item is selected. EXAMPLE: 442 <MenuItem.radio title="Tabular"/> JavaGram Agile Development 11.2.7 Decorative Elements <Label/> extends <Comp> SUMMARY: Creates a label for decorating other elements. PROPERTY DESCRIPTION string title vague image The label’s title (defaults to ""). Optional image to be displayed to the left of the label title. This may be either an <Icon> or the path of a GIF file. symbol align The alignment of the label; must be one of $center, $north, $south, $east, $west, $northEast, $northWest, $southEast, or $southWest (defaults to $west). symbol textPos The alignment of label text relative to image; must be one of $center, $north, $south, $east, $west, $northEast, $northWest, $southEast, or $southWest (defaults to $east). EVENTS: $click event is raised when the mouse pointer is clicked on the label. EXAMPLE: <Label title="Name" align=$east/> <Icon/> SUMMARY: Creates an image icon, which you can subsequently use to set the image property of other elements. This is more efficient than using the image file path, as it causes the icon image to be created once and reused many times. PROPERTY DESCRIPTION int width int height string image Returns the image width. You can’t set this property. Return the image height. You can’t set this property. The full path of the image file. The recommended practice is to use the sys.use() method to translate the relative image path to its absolute path, which will also demand-download the image file if necessary. EXAMPLE: <Icon icon image={sys.use("gifs/Apply.gif")}/> <Image/>/> extends <Comp> SUMMARY: Creates an image component for displaying an image and optional markers on the image. When the cursor is placed over a marker it changes to a ‘hand’ cursor to signify that it’s selectable. PROPERTY DESCRIPTION string image The full path of the image file. The recommended practice is to use the sys.use() method to translate the relative image path to its absolute path, which will also demand-download the image file if necessary. GUI Reference 443 vector<map> markers The image markers, where each marker is specified as a map having the following keys. (All color values must be specified in hexadecimal format, e.g., "0x00AA00"). $shape may denote one of $square, $circle, $triangle, or $diamond (defaults to $circle). $bounds denotes the boundary rectangle of the marker as a list of the form: (x, y, width, height). $title may specify an optional title for the marker as a list of the form: ("...", fontSize, color, style). To use the default fontSize, pass null for it. The style must be one of $plain, $bold, $italic, or $boldItalic. The title is always drawn in the middle of the marker. $line may specify an optional outline for the marker as a list of the form: (weight, color). The weight must be specified as a real value. int select EVENTS: EXAMPLE: $fill may specify an optional fill color for the marker as a list of the form: (transparency, color). The transparency must be specified as a value in the range 0.0 to 1.0, where 0.0 means totally transparent (which gets defaulted back to 1.0) and 1.0 means completely opaque. Specifies the index of the currently selected marker. Setting this property to a new value causes the $select event to be raised. $select event is raised when the mouse pointer is clicked on the image or a marker. <Image myImage image={sys.use("images/Tree.jpg")} Markers=[...]/> <Separator/> extends <Comp> SUMMARY: Creates a separator for use in menus, toolbars, and containers. symbol orientation The separator’s orientation, which may be $horizontal or $vertical (defaults to $horizontal). For menus and toolbars, you don’t need to define this property, as JavaGram automatically adjusts the orientation to suit them. EXAMPLE: <Panel> <Layout.border/> <Separator lay=$north/> <Area.text lay=$center/> </Panel> 11.2.8 Field Elements <Field.text/> extends <AbsText> SUMMARY: Creates a free-format text field. PROPERTY 444 DESCRIPTION JavaGram Agile Development int cols symbol align The number of logical columns in the field. The alignment of text within the field, which may be one of $west, $center, or $east (defaults to $west). EXAMPLE: <Field.text category align=$center/> <Field.password/> extends <Field.text> SUMMARY: Creates a password field which, for security, obscures user input. EXAMPLE: <Field.password passwd/> <Field.number/> extends <AbsNumeric> SUMMARY: Creates a numeric field. Numeric fields can be edited in two ways: directly by typing into the field, or via a visual editor by double clicking the field or clicking on the button to the right of the field. The visual editor is dependent on the field type – for a number field, a calculator is displayed; for a date field, a calendar is displayed; for a time field, a clock is displayed (see <AbsNumeric>). PROPERTY DESCRIPTION real min int precision The minimum possible value for the field (not applicable to date fields). Defaults to 0.0. Setting this property to null will cause to assume the largest negative real number. The maximum possible value for the field (not applicable to date fields). Defaults to the largest real number. Setting this property to null will cause to assume the largest positive real number. The default value for the field (not applicable to date fields). Defaults to 0.0. The number of decimal places allowed in the field (defaults to 0). EXAMPLE: <Field.number temperature min=0 max=1000/> real max real def <Field.percent/> extends <Field.number> SUMMARY: Creates a number field for displaying percentage values. Values are formatted according to the format string "0,000.0%". EXAMPLE: <Field.percent rate/> <Field.currency/> extends <Field.number> SUMMARY: Creates a number field for displaying currency values. Values are formatted according to the format string "$0,000.00". EXAMPLE: <Field.currency loan/> <Field.time/> extends <Field.number> SUMMARY: Creates a time field, which displays time values in the format hh:mm:ss. EXAMPLE: <Field.time departure/> <Field.date/> extends < AbsNumeric> GUI Reference 445 SUMMARY: vector limit Creates a date field, which displays date values in the format yyyy-MMdd. The format property can be used to display date in other formats, including date-and-time. Optionally specifies the potential dates that can be accepted by the field. Each vector element can be either a date or a list of the form (fromDate, toDate). The field’s popup calendar highlights the allowed dates and dims the rest so that they can’t be selected. EXAMPLE: <Field.date dob/> 11.2.9 Selection Elements <Combo/> extends <Comp> SUMMARY: Creates a combo box, allowing the user to select from a list of values. If the combo values are numbers (as provided by the data or model property) and the combo has binding (as denoted by its key property), set its type property to int or real. This enables the binding process to use the correct type when saving the combo’s selected value into an object. PROPERTY DESCRIPTION vector data Direct data for the combo. The combo uses this to build an internal data model (defaults to null). The data model for the combo, which must be a method in the same class (defaults to null). It must have the following signature: Id model vague myModel (native combo, symbol cmd, int idx) Possible values for cmd are: $count (return the number of items in the combo). $get (return the item denoted by idx). int select string selectVal boolean readOnly int minPopupWidth EVENTS: The zero-based index of the currently selected row (or null). The string representation of the currently selected row. When true, the combo can’t be edited (defaults to false). Specifies a minimum width for the combo popup (defaults to 0). $select is raised when a value is selected from the combo. EXAMPLE: <Combo status model=statusModel/> <List/> extends <Comp> SUMMARY: Creates a list box. PROPERTY DESCRIPTION vector data Direct data for the list. The list uses this to build an internal data model (defaults to null). The data model for the list, which must be a method in the same class (defaults to null). It must have the following signature: Id model vague myModel (native comp, symbol cmd, int idx) Possible values for cmd are: $count (return the number of items in the list). $get (return the item denoted by idx). 446 JavaGram Agile Development vague select EVENTS: The index of the currently-selected item(s). This is a vector of integers (may be empty) when multiSelect is true, or an integer (or null) otherwise. For convenience, you can assign an integer to this property even when multiSelect is true. Set this property to true to select all items in the list. Set it to false, to deselect all items. Getting this property always returns null. The currently-selected item(s). This is a vector (may be empty) when multiSelect is true, or an integer (or null) otherwise. When true, the user can select multiple values in the list by holding the shift or control key down and clicking. When false, only one value can be selected at a time. $select is raised when a value is selected from the list. EXAMPLE: <List color data=["Red", "Green", "Blue"]/> boolean selectAll vague selectVal boolean mutiSelect <List.tick/> extends <List> SUMMARY: Creates a list box of tickable items. PROPERTY DESCRIPTION vector<list> data Same as <List> direct data, except that each item should be a list of two elements: (name, tick), where tick is true of false. You can also use a list of one item (name) in which case tick defaults to false. Same as <List> model, plus the following commands: $tick (return true if the item denoted by idx is ticked). $toggle (toggle the tick state of the item denoted by idx). Set this property to true to tick all items in the list. Set it to false, to untick all items. Getting this property always returns null. $select is raised when a value is selected from the list. $toggle is raised when one or more items change their tick state. Id model boolean tickAll EVENTS: EXAMPLE: <List.tick remind data=[("Today", true), ("Tomorrow", false), ("Next Week", true)]/> <Option.tick/> extends <AbsButton> SUMMARY: Creates a checkbox. EXAMPLE: <Option.tick married title="Married"/> <Option.radio/> extends <AbsButton> SUMMARY: Creates a radio button. EXAMPLE: GUI Reference <Panel.toggle sex> <Option.radio title="Male"/> <Option.radio title="Female"/> </Panel> 447 <Slider/> extends <Comp> SUMMARY: Creates a slider. PROPERTY DESCRIPTION map title The slider’s title map (defaults to [=>]). This may be used to map slider values to labels on the slider. The minimum possible value for the slider. The maximum possible value for the slider. The slider’s orientation, which may be $horizontal or $vertical. An arbitrary expression (action handler) which is evaluated when the slider’s value changes. Sliders don’t have event handlers; use the action property instead. int min int max symbol orientation vague action EXAMPLE: <Slider risk title=[0=>"min", 1=>"low", 2=>"medium", 3=>"high", 4=>"max"] min=0 max=4 value=2 orientation=$horizontal action={doAction()}/> <RangeBar/> extends <Comp> SUMMARY: Creates a range bar. Like a slider, a range bar has a minimum and a maximum value, but its thumb denotes two values: start and finish, both of which fall in between min and max, and start is always less than finish. The thumb size is proportional to the ‘distance’ between start and finish. PROPERTY DESCRIPTION real min real max real start real finish symbol align The minimum value for the range bar (defaults to 0.0). The maximum value for the range bar (defaults to 100.0). The start value for the thumb (defaults to 40.0). The finish value for the thumb (defaults to 60.0). Determines the bar’s orientation and relative position of labels, which may be one of: $north (horizontal orientation and labels displayed above the bar), $south (horizontal orientation and labels displayed below the bar), $west (vertical orientation and labels displayed to the left of the bar), or $east (vertical orientation and labels displayed to the right of the bar). It defaults to $south. A string representing the format for rendering the labels, or a method in the same class returning a formatted label. The method must have the following signature: vague format string formatFun(native rangeBar, real val) This property defaults to null and, if not set, the labels are not vague action boolean dynamic 448 displayed. An arbitrary expression (action handler) which is evaluated when the thumb is moved or resized. Range bars don’t have event handlers; use the action property instead. When true, the action handler is called as the user drags or resizes the thumb. Otherwise, the action handler is called only once when the user releases the mouse button. It defaults to false. JavaGram Agile Development int pause Specifies the delay (in milliseconds) between animation frames (defaults to zero). When set to a non-zero value (e.g., 1000), it causes a set of tools to appear next to the range bar, allowing the user to animate the range bar. From left to right, the tools are: Animation speed (slow = half the speed specified by pause, normal= same speed as pause, fast = twice as fast as pause). Defaults to normal. The speed can be changed by clicking on the icon. Loop mode (causes the animation to loop). Defaults to true. It can be changed by clicking on the icon. Reverse play. Starts the animation is reverse order. The button changes to pause. Forward play. Starts the animation if forward order. The button changes to pause. EXAMPLE: <RangeBar range min=0 max=200 start=20 finish=50 fgColor="0x0000DD" format="0.00m" align=$north/> 11.2.10 Button Elements <Button/> extends <AbsButton> SUMMARY: Creates a push button. <Button title="Apply" action={doApply()}/> EXAMPLE: <Button.toggle/> extends <AbsButton> SUMMARY: Creates a toggle button. <Button.toggle title="Edit Mode"/> EXAMPLE: 11.2.11 Feedback Elements <ProgressBar/> extends <Comp> SUMMARY: Creates a progress bar. The progress can be updated by incrementing the value property progressively. PROPERTY DESCRIPTION string title int min int max symbol orientation The progress bar’s title. The minimum possible value for the progress bar. The maximum possible value for the progress bar. The progress bar’s orientation, which may be $horizontal or $vertical. When true, the progress bar gives indefinite feedback. Otherwise, it progresses from min to max. Defaults to false. An arbitrary expression (action handler) which is evaluated when the progress bar’s value changes. Progress bars don’t have event handlers; use the action property instead. boolean indefinite vague action EXAMPLE: GUI Reference <ProgressBar progress title="Updating" min=0 max=10 orientation=$horizontal action={doAction()}/> 449 11.2.12 Area Elements <Area.text/> extends <AbsText> SUMMARY: Creates a text area, suitable for displaying multi-line text. PROPERTY DESCRIPTION string file int lineCount Loads the contents of file whose path is denoted by file into the text area. The file path may be absolute or relative to sys.root. In a Flash client, the file must be present in a loaded partition. The width of the tab character, in terms of spaces (defaults to 8). The number of logical rows in the text area. The number of logical columns in the text area. You can append a string or a vector of strings to the text area by setting this property. You can’t get this property. It’s more efficient to append a vector of strings. Maximum number of lines a text area will hold. When greater than zero and exceeded, lines will be removed from the beginning to keep the size within limit. Number of lines in a text area. EXAMPLE: <Area.text comment value="sample comment"/> int tab int rows int cols string/vector add int maxLines <Area.url/> extends <AbsText> SUMMARY: Creates a URL pane, populated with data from the nominated URL. PROPERTY DESCRIPTION string url The URL for the pane, which may denote a file or web page (defaults to ""). Scrolls to the HTML reference denoted by the value assigned to this property. The value must be a # style HTML anchor. You can’t get this property. The event handler for this component has a different signature from the standard event handler: string scrollTo EVENTS: void handler (native comp, string url) When the user clicks on a HTML anchor, the event handler is called and the anchor reference is passed to the url parameter. EXAMPLE: 11.2.13 <Area.url readOnly=true event=myHandler url="www.acme.com/Sample.html"/> Tree Elements <Tree> extends <Comp> SUMMARY: Creates a tree, suitable for visualizing hierarchical information. You can specify the tree nodes in three ways: By defining <Node> child elements. By specifying direct data. The data must be a vector of <Node> elements, and can be nested. By defining a data model. PROPERTY 450 DESCRIPTION JavaGram Agile Development vector data Id model Direct data for the tree. The tree uses this to build an internal data model (defaults to null). The data model for the tree, which must be a method in the same class (defaults to null). It must have the following signature: vague myModel (native tree, symbol cmd, native node, vague subnode) For the root node, node will be null. Possible values for cmd are: $count (return the count of the children of node). $get (return the child of node denoted by the subnode index; vague select vague root boolean multiSelect boolean reload int rowHeight EVENTS: EXAMPLE: the latter being an integer). $index (return the index of subnode in node, as an integer). $leaf (return true if node is a leaf node). The currently-selected node(s). When multiSelect is true, the result is a vector (may be empty). Otherwise, it’s a node (may be null). For convenience, you can assign a node to this property even when multiSelect is true. The invisible tree root node. All tree nodes are descendants of this node. When true, multiple nodes can be selected by holding down the control key and clicking. Otherwise, only one node can be selected at a time. When set to true, the tree is reloaded so that any change to the data or data model is reflected. You can’t get this property. The height of each row in pixels. In a non-Flash application, when set to zero, the height of each cell is worked out dynamically based on its icon height. $select is raised when a node is selected or deselected. $drill is raised when a node is double-clicked. $expand is raised when a node is expanded. $collapse is raised when a node is collapsed. <Tree tree event=sampleHandler multiSelect=true> <Node title="Customer"> <Node title="Account 1"/> <Node title="Account 2"/> </Node> </Tree> <Node> SUMMARY: PROPERTY Creates a tree node. This can be used as data within a tree. string title vague image The node’s title (defaults to ""). Optional image to be displayed to the left of the node title. This may be either an <Icon> or the path of a GIF file. GUI Reference DESCRIPTION 451 vague altImage string tooltip vague presentation vague content vector children native parent boolean modified boolean expand boolean refresh boolean reload EXAMPLE: 11.2.14 Same as image, except that this is used when the node is selected. Otherwise, image is used for both when the node is selected and when it’s not selected. The node’s tooltip. The presentation for this node’s content (defaults to null). Typically, this will be a GUI element, such as a panel that can display the node’s content. The logical data associated with the node (defaults to null). Typically, when a node is selected, your application should present a view of this data using the node’s presentation (e.g., populate a panel). The children for this node, as a vector of <Node> elements. You can also specify child elements by directly enclosing them in the node. The parent of this node, or null if a root node. When true, it means that the node has been modified. A modified node is highlighted by a ‘*” appearing before its title. When true, the node is expanded so that its children are visible. Refreshes the node and its children by redrawing them. When set to true, the node (and its children) are reloaded so that any changes are reflected. You can’t get this property. This is similar to the reload property of a tree, except that it only affects the node (and its children), not the rest of the tree. <Node title="Help" image={sys.use("gifs/Book.gif")}> <Node title="Topics"/> </Node> Table Elements <Table/> extends <Comp> SUMMARY: Creates a table for visualizing tabular data. PROPERTY DESCRIPTION vector data Direct data form the table. This must be a vector of maps or a vector of objects. The table uses this to build an internal data model (defaults to null). 452 JavaGram Agile Development Id model The data model for the table, which must be a method in the same class (defaults to null). It must have the following signature: vague myModel (native table, symbol cmd, int row, int col) Possible values for cmd are: $rows (return the number of rows in the table). $cols (return the number of columns in the table). $get (return the data for the cell denoted by row and col). $put (update the model as a result of the user editing a cell). The modified cell is denoted by row and col. The modified cell value is denoted by the put property of the table. $put is not used unless the editable property of the table is set to true. $style (return the style of the cell denoted by row and col). The style must be either null (no style) or a vector of these values: $plain, $bold, $italic, and hexadecimal representation of a color value (e.g., "0xFF00FF"). $style is not used unless the styled property of the table is set to true. $icon (return null or an <Icon> value). The icon is displayed only when the column format has its $icon key set to true. The vector format GUI Reference icon is displayed inside the cell and to the left of the cell value. $sortAscend (sort the underlying data in ascending order of the column denoted by col). $sortDescend (sort the underlying data in descending order of the column denoted by col). A vector that specifies the format of each column as a map. If not specified, columns are formatted using default formatting rules that are based on the type of data in cells. The map may have any combination of the following keys: $key is used for data binding, and must map to a symbol in the row data intended for the column. $title specifies the column title as a string. $width specifies the column width as an integer. $align specifies the alignment of the column, and must be $east, $center, or $west. $format specifies the desired format of the cell as a string. See sys.format(). $icon optionally specifies whether the column cells have icons (boolean value). $editor specifies an editor for the column cells. This must be an element (e.g., <Field.text>) to be used for editing the cell. 453 vague select boolean selectAll int column boolean multiSelect boolean autoSize boolean editable boolean styled list hitCell vague sortAscend vague sortDescend int headingHeight int rowHeight vague put string bgColorOdd string bgColorEven EVENTS: 454 The indices of the currently-selected table rows. When multiSelect is true, this is a vector of integers (may be empty). Otherwise, it’s an integer (or null). For convenience, you can assign an integer to this property even when multiSelect is true. When true, all rows are selected, provided multiSelect is also true. Returns the currently selected column, or null. You cannot set this property. When true, multiple rows can be selected by holding down the shift or control key when clicking on rows. Otherwise, only one row can be selected at a time (defaults to false). When true, the columns are automatically resized to fill all the available horizontal space for the table (defaults to false). When true, the table cells can be edited by the user (defaults to false). When true, the cells can be individually styled according to the styles specified by the data model (defaults to false). A list of the format $(row, col) that denotes the last clicked cell. The table is sorted in ascending order of the column (column index or key) assigned to this property. Set this property (or sortDescend) to null if you want to disable sorting. Set it to -1 to signify that the table is not sorted. Returns the zero-based column number in which the table is ascending-sorted, -1 if the table is not ascending-sorted, or null if sorting is not allowed. The table is sorted in descending order of the column (column index or key) assigned to this property. Set this property (or sortAscend) to null if you want to disable sorting. Set it to -1 to signify that it’s not sorted. Returns the zero-based column number in which the table is descending-sorted, -1 if the table is not descending-sorted, or null if sorting is not allowed. The height of the table heading in pixels. The height of each row in pixels. The last modified cell value in the table, or null. Background color for odd-numbered rows. The color must be in hexadecimal format (e.g., "0x00FF00"). Defaults to light grey. Background color for even-numbered rows. The color must be in hexadecimal format (e.g., "0x00FF00"). Defaults to white. $select is raised when a row is selected or deselected. $drill is raised when a row is double-clicked. $hitCell is raised when a cell is clicked. JavaGram Agile Development <Table myTable model=tableModel format={myFormat} editable=true multiSelect=false styled=true event=sampleHandler/> EXAMPLE: Example of format vector: vector<map> tformat = vector( map($key => $name, $title => "Name", $width => 200, $align => $west, $editor => textEditor) ,map($key => $age, $title => "Age", $width => 50, $align => $east, $editor => numberEditor, $icon => true) ,map($key => $sex, $title => "Sex", $width => 100, $align => $west, $editor => sexEditor) ); Examples of cell editors: static <Field.text textEditor/> static <Field.number numberEditor precision=0/> static <Combo sexEditor data=["Male", "Female"]/> 11.2.15 Grid Elements <Grid> extends <Container> SUMMARY: Creates a grid element. This is similar to a table but more flexible, in that rows, columns, and cells can be individually controlled. The following rules apply: If a cell doesn’t define a specific property then it’s inherited from the cell’s column. If the column doesn’t define it either then it’s inherited from the cell’s row. If the row doesn’t define it either then it’s inherited from the default cell. If the default cell doesn’t define the property either then the default value of the property is used. These rules are useful in avoiding repetitions. For example, if most of the cells in a column are dates, set the column’s kind to be $date, and explicitly set the kind property of those cells that are not dates. PROPERTY DESCRIPTION boolean fixedWidth When true, column boundaries are fixed. Otherwise, they can be dragged by the user to resize them (defaults to false). When true, row boundaries are fixed. Otherwise, they can be dragged by the user to resize them (defaults to false). When true, hot links within cells are underlined (defaults to true). When true, $hitCell events are generated even on locked cells (defaults to false). boolean fixedHeight boolean underlineLink boolean hitCellAll GUI Reference 455 vague recalc Id edit When true, forces the grid to recalculate all cells that have a calc property. This will not raise events, nor will it refresh the cells. Returns the position of the cell being calculated as a list of the form $(row, col). The grid’d edit handling method. This must be a method in the same class, with the following signature. vague myEditHandler (native grid, int row, int col, vague oldVal, vague newVal) int rows int cols list hitCell list modCell EVENTS: EXAMPLE: When a cell is edited by the user, this method is automatically invoked for that cell. The last four arguments denote the cell position and its old and new values. This method must return the desired new value for the cell, which can be oldVal, newVal, or something else. Return $ignore if you want the change to be ignored (e.g., when failing validation). The number of rows in the grid (not settable). The number of columns in the grid (not settable). The position of the hit (activated cell) as a list of the form $(row, col), or null. The position of the last modified cell as a list of the form $(row, col), or null. You can’t set this property. $hitCell is raised when an editable cell is clicked. $modified is raised when a cell’s value is modified. $calc is raised before calculation is performed as a result of a cell having been modified. $postCalc is raised after calculation is performed as a result of a cell having been modified. <Grid grid hitCellAll=true fixedWidth=false fixedHeight=true underlineLink=true edit=myEditHandler event=myEventHandler> //... </Grid> <GridElem> SUMMARY: PROPERTY Abstract base for grid rows, columns, and cells. symbol kind The kind of the grid element data, which may be one of: $text (render as <Field.text>) $textArea (render as <Area.text>) $combo (render as <Combo>) $number (render as <Field.number>) $date (render as <Field.date>) $time (render as <Field.time>) $tick (render as <Option.tick>) $icon (render as an icon, specified via the image property of a cell) Title for the element where relevant (e.g., for a tick box). string title 456 DESCRIPTION JavaGram Agile Development vector data Id model boolean lock boolean dim int size symbol align native font string format string fgColor string bgColor string egColor string lkColor int border string tooltip real min real max int precision int minPopupWidth Data for the element where relevant (e.g., combo items). Data model for the element where relevant (e.g., combo data model). When true the element is locked so that its content can’t be changed (defaults to false). When true the element is displayed as dimmed (defaults to false). The width of the element (if a column or cell), or its height (if a row). The alignment of element content, which must be one of $center, $north, $south, $east, $west, $northEast, $northWest, $southEast, or $southWest (defaults to $west). The desired font for rendering the element content (defaults to null). The format of the element content (defaults to null). See sys.format(). Foreground color for the element (see <Comp> for color format). Background color for the element (see <Comp> for color format). Edge color for the element (see <Comp> for color format). Color for the hot link in the element (see <Comp> for color format). The thickness of the element border in pixels (defaults to 1). Use 0 for a borderless element. The tooltip for the element. The minimum value for a numeric element (defaults to 0). Follows the same rules as <Field.number>. The maximum value for a numeric element (defaults to the largest real number). Follows the same rules as <Field.number>. The number of decimal places for a numeric element (defaults to 2). The minimum popup width for a combo element (defaults to 0). <Column> extends <GridElem> SUMMARY: Creates a grid column element, which must be a child of a <Grid>. EXAMPLE: <Column kind=$text size=200/> <Row> extends <GridElem> SUMMARY: Creates a grid row element, which must be a child of a <Grid>. PROPERTY DESCRIPTION symbol key The key (may be null) for binding this element to data (e.g., $details). To bind multiple rows that have the same structure to a vector, give them the same key and make sure that the indexed property is set to true. For example, if the grid contains three rows that all have the key $products, then $products will denote a vector (within the grid’s binding) of three elements each of which, for example, is a map that represents a product. boolean indexed When true, rows with the same key are indexed into a binding vector (defaults to true). GUI Reference 457 Type type The type of the value that this element can be bound to (defaults to map). The zero-based index of the row in its parent grid, or -1 if the row has no parent. You cannot set this property. int row <Row key=$details type=map> //... </Row> EXAMPLE: <Row.default> extends <Row> SUMMARY: Creates a default row, which must be a child of a <Grid>. This is internally used as a template for creating new rows to be inserted into the grid. To add a new row to a grid, use this syntax: grid += rowIdx, where grid is the identifier for a <Grid> element and rowIdx is the zero-based index for the new row. To delete a grid row, use this syntax: grid -= rowIdx. EXAMPLE: <Row.default> <Cell value="New Row"/> <Cell value=200/> <Cell value=true title="Valid"/> <Cell value="Delete" link={grid -= grid.hitCell[0]}/> </Row> NOTE: New rows can also be created and added at run time using this syntax: grid += vector of lists grid += list grid += gridRow where list’s first element is a new row and optional second element is an index specifying the insert position. If the latter is not specified or is greater then the size of the grid, the new row will be appended to the end of the grid. EXAMPLE: grid += vector( list((new GridRow()).getGridRow(), rowsInsIdx), list((new GridRow()).getGridRow()) ); grid += list((new GridRow()).getGridRow(), rowsInsIdx); // the following lines add 2 rows at the end: grid += list((new GridRow()).getGridRow()); grid += (new GridRow()).getGridRow(); class GridRow { <Row gridRow> //... </Row> public native getGridRow() { return gridRow; } } 458 JavaGram Agile Development <Cell/> extends <GridElem> SUMMARY: Creates a grid cell. This must be a child of a <Row>. PROPERTY DESCRIPTION symbol key vague value vague calc The key (may be null) for binding this cell to data (e.g., $product). The data binding (may be null) for this cell. The calculation expression for this cell. This expression is evaluated when any grid cell is modified or when the grid recalc property is set to true, to produce the binding value for the cell. To remove a cell’s calc effect, set it to null. The column span for the cell (defaults to 1). The hot link expression for the cell. When the cell is clicked, this expression is evaluated. A cell that has a non-null link property is highlighted similarly to a HTML hot link. To remove a cell’s link effect, set it to null. Image to be displayed within the cell, provided the cell’s kind is $icon. This may be either an <Icon> or the path of a GIF file. int colSpan vague link vague image <Cell lock=false align=$east tooltip="Price" min=0.0 max=200.0 precision=2 calc={calcPrice()}/> EXAMPLE: <Cell.default/> extends <Cell> SUMMARY: Creates a default cell to hold default property values for all cells. If a cell doesn’t have a property specified explicitly or implicitly (in its parent <Column> or <Row>) then it inherits it from this element. <Cell.default egColor="0x0000CC"/> EXAMPLE: 11.2.16 Graph Elements <Graph.line/> extends <AbsGraph> SUMMARY: Creates a line graph, where data points are joined by straight lines. The graph can support multiple data series (i.e., multiple lines). The minimum and maximum values for either axis are automatically calculated from the data series. If the default values (e.g., minKey) lie outside the calculated range, they are used instead. PROPERTY DESCRIPTION symbol kind Denotes the kind of the graph, as one of $line or $bar (defaults to $line). By setting this property, you change a line graph to a bar graph and vice-versa. Default minimum key for the x-axis (defaults to null). Default maximum key for the x-axis (defaults to null). Default minimum value for the y-axis (defaults to null). Default maximum value for the y-axis (defaults to null). When true, the graph is editable: the user can drag the data points up or down to change their y-axis values, but not side-ways. Defaults to false. real minKey real maxKey real minValue real maxValue boolean editable GUI Reference 459 boolean grid When true, background grid lines are shown behind the graph (defaults to false). boolean hint When true, the (x,y) value denoted by the mouse pointer is displayed above the graph (defaults to true). boolean pad When true, pads all axes on either side by one major position (defaults to false). This is especially useful for bubble graphs so that the bubbles don’t obstruct the axes. boolean When true, displays minor division steps on the axes when there showMinorSteps is enough room (defaults to true). Otherwise, only major division steps are shown. string xTitle The title for the x-axis (defaults to null). string yTitle The title for the y-axis (defaults to null). int xMargin The margin below the x-axis (defaults to 0). int yMargin The margin to the left of the y-axis (defaults to 0). symbol xOrientation The orientation of labels for the x-axis (defaults to $horizontal). Setting it to $vertical causes x-axis labels to be drawn downwards. vague xFormat A string representing the format for rendering x-axis labels (defaults to "0.0"), or a method in the same class returning a formatted x-axis label. The method must have the following signature: string xFormatFun(native graph, real val) vague yFormat vague yEastFormat Id bandFormat vague select Similar to xFormat, but for rendering y-axis labels. Similar to yFormat, but for rendering east-side y-axis labels (if any). An east-side y-axis is created if at least one of the series is specified to use it (see the $east key of the style property). A method in the same class returning a formatted description of a band. The method must have the following signature: string bandFormatFun(native graph, int bandIdx) Get this property in response to a $select or $drill event. If the user has clicked on a band then the list will of the form (bandId, bandIndex). If the user has clicked on a graph series control point then the list will of the form (seriesIndex, pointIndex). Otherwise, it will return null. All indices are zero-based. Setting this property has no effect. 460 JavaGram Agile Development vector<map> style A vector of maps, each of which specifies the style for a data series (defaults to null). The map may have the following keys: $title specifies the title for the data series (defaults to null). $color specifies the line color (defaults to black). $dash specifies the dash size for the line (defaults to 0, i.e., solid). $weight specifies the thickness of the line (defaults to 1). Setting it to 0 causes no lines to be drawn, but the data points will still be visible if $marker is specified. $marker specifies the marker for each data point, which may be one of $none, $circle, $square, $triangle, or $diamond (defaults to $none). $fill specifies whether the marker should be drawn as solid or outline (defaults to false). $east specifies whether the series y-axis should be on the eastern side (defaults to false). $type optionally overrides the type of a series, and may be one of: $line, $bar, or $bubble. When unspecified, it defaults to the graph type. For example, in bar graph you can draw a series as a line by setting its type to $line. $label (a boolean) specifies whether a series can have labels at its value points (defaults to false). The actual labels are provided by the graph model. vector<map> bands Similar to style but specifies bands for x and/or y-axis (defaults to null). Each x-axis band is drawn as a vertical line, and each yaxis band is drawn as a horizontal line. Each band is described by a map, which may have the same keys as described for style, plus the following keys: $x specifies position of an x-axis band. $y specifies the position of a y-axis band. $id specifies the band’s unique ID. vector<vector> data Direct data for the graph (defaults to null). This is used to build an internal data model. The data must be a vector of vectors, where each contained vector represents a series. The vector indices serve as x-axis values, and the elements as the y-axis values. GUI Reference 461 Id model The data model for the graph, which must be a method in the same class (defaults to null). It must have the following signature: vague myModel (native graph, symbol cmd, int series, int idx, double newVal) Possible values for cmd are: $series (return the number of data series). $samples (return the number of samples in the series denoted by series). $key (return the key for series at idx position, for x-axis). $value (return the value for series at idx position, for y-axis). $label (return the label(s) for series at idx position). This can be either a single value or a list of up to two values. The first value is displayed above the graph point, and the second value (if present) is displayed below the graph point. In bar and bubble graphs, the second value is displayed at the center of the bar or bubble. $putable (return true if the value for series at idx can be updated). When series is negative, idx denotes the zero-based index of a band; otherwise, it denotes a zero-based index into the series. This command is not invoked unless the editable property of the graph is set to true. Using $putable, $putNext, and $putLast gives you a much finer control over what part of a graph can be edited than using direct data. $putNext (update a series or band at idx position to newVal in response to the user dragging a series or band control point). See also $putable for a description of series and idx. This command is not invoked unless the $putable command returns true. Use this command to provide additional feedback (if needed) as the user drags a point. $putLast (similar to $putNext but invoked when the user finishes dragging a control point by releasing the mouse button). Use this command to commit or cancel the edit. EXAMPLE: <Graph.line bgColor="0xFFFFFF" xFormat=xFormatFun yFormat=”HH:mm:ss” style={graphStyles} model=graphModel/> where: vector<map> graphStyles = [ [$title=>"Age", $color=>"0xFF0000", $dash=>0, $weight=>1, $marker=>$circle], ,[$title=>"Income", $color=>"0x0000FF", $dash=>0, $weight=>1, $marker=>$square] ]; string xFormatFun(native graph, real val) { return val@int@string; } 462 JavaGram Agile Development <Graph.bar/> extends <Graph.line> SUMMARY: A variant of line graph where the data is displayed as vertical bars instead of lines (the kind property defaults to $bar). The bars for values belonging to different series are displayed next to each other. <Graph.stack/> extends <Graph.line> SUMMARY: A variant of line graph where the data is displayed as vertical bars instead of lines. The bars for values belonging to different series are stacked on top of each other. <Graph.bubble/> extends <Graph.line> SUMMARY: A variant of line graph where the data points are displayed as bubbles (solid circles). Bubble graphs are not editable. Whereas (for each series) a line graph specifies a y-value for a given xvalue, a bubble graph specifies a y-value as well as a bubble size and an optional bubble color for a given x-value. Bubble graphs are useful for visualizing multi-faceted data. For example, a line graph may be used to show the GDP (y-axis) of countries (series) over a 10 year period (xaxis). A bubble graph can represent the same, but also show the average per-capita income (bubble size), and the trade balance (bubble color) of each country. The latter can range from green (trade surplus) to red (trade deficit). You should always use a data model (not direct data) for a bubble graph. The data model should support additional cmds: $bubble (return the ‘bubble’ data). This can be a size number, or a list of the form (size,color), where size denotes the relative size of the bubble, and color optionally specifies a hexadecimal color string for drawing the bubble. If color is not specifies, the style color of the corresponding series is used instead. $bubbles (optionally return a legend vector). By default, a graph legend is tied to the graph series. However, for a bubble graph whose bubble colors are not tied to the series, you may want to provide a different legend that specifies the meaning of each color. To do so, return a vector in response to this model command. Each vector element must be a map of the form [$color=>"...”, $title=>"..."], where $color specifies a hexadecimal color string and $title denotes a string describing the meaning of the color. For example, [$color=>"#FF0000", $title=>"Trade Deficit"]. <Graph.pipe/> extends <AbsGraph> GUI Reference 463 SUMMARY: A graph where data is represented by horizontal pipes (is especially useful for displaying heat maps). Each pipe may consist of zero or more segments, where the segments are typically drawn in different colors. A pipe graph has a single horizontal axis (x-axis). Pipe graphs are not editable. It’s recommended that you always enclose a pipe graph in a <Pane.scroll horizontal=false>. This ensures that when there are more pipes than can be displayed in the graph’s canvas, a vertical scrollbar will appear. PROPERTY DESCRIPTION real minValue real maxValue boolean grid Default minimum value for the x-axis (defaults to null). Default maximum value for the x-axis (defaults to null). When true, background x-axis grid lines are shown behind the graph (defaults to false). When true, the title, value, and hint of the segment under the mouse pointer is displayed above the graph (defaults to true). When true, the pipe segments are gradient-filled (defaults to true). The title for the x-axis (defaults to null). A string representing the format for rendering x-axis labels (defaults to "0.0"), or a method in the same class returning a formatted x-axis label. The method must have the following signature: boolean hint boolean gradient string xTitle vague xFormat vague select string xFormatFun(native graph, real val) Get this property in response to a $select or $drill event. If the user has clicked on a segment then the list will of the form (rowIdx, segmentIdx). Otherwise, it will be null. All indices are zero-based. Setting this property has no effect. vector<map> legend A vector of maps, specifying the graph legend, which describes the meaning of the colors used in the graph. The map may have the following keys: $color specifies a color. $title specifies the meaning of the color. symbol pipeTitlePos Denotes the position of pipe titles as one of: $north (title drawn above the pipe), $west (title drawn to the left of the pipe), or $none (no pipe title drawn). Defaults to $north. vector<vector> data Direct data for the graph (defaults to null). This is used to build an internal data model. The data must be a vector of vectors, where each contained vector represents a pipe and is of the format: [pipeTitle, segment1Map, segment2Map, …]. Each segmentMap may have these keys: $value (segment value). $color (segment color). $title (segment title). $hint (segment hint). 464 JavaGram Agile Development Id model The data model for the graph, which must be a method in the same class (defaults to null). It must have the following signature: vague myModel (native graph, symbol cmd, int row, int segment) Possible values for cmd are: $count (when row is null, return the number of pipes; otherwise return the number of segments for the pipe denoted by row). $value (return the value for the segment denoted by row and segment). $color (return the color for the segment denoted by row and segment). $title (when segment is null, return the title of the pipe denoted by row; otherwise return the title of the segment denoted by row and segment). $hint (return the hint for the segment denoted by row and segment). EXAMPLE: <Graph.pipe bgColor="0xFFFFFF" title="Sample Pipe Graph" legend={pipeLegend} minValue=0 xTitle="Weight" xFormat="0.0%" data={pipeData}/> where: vector<map> pipeLegend = [ [$color=>"0xFF0000", $title=>"Hot"], [$color=>"0x00FF00", $title=>"Cold"], [$color=>"0x0000FF", $title=>"Neutral"] ]; vector<vector> pipeData = [ ["First Bar" , [$value=>20.5, $color=>"0xFF0000", $title=>"A1"] , [$value=>70.5, $color=>"0x0000FF", $title=>"A2"] , [$value=>30.5, $color=>"0x00FF00", $title=>"A3"] ] , ["Second Bar" , [$value=>55.2, $color=>"0xFF0000", $title=>"B1"] , [$value=>86.1, $color=>"0x0000FF", $title=>"B2"] ] , ["Third Bar" , [$value=>29, $color=>"0xFF0000", $title=>"C1"] , [$value=>45.4, $color=>"0x0000FF", $title=>"C2"] , [$value=>65, $color=>"0xFF0000", $title=>"C3"] ] ]; <Graph.pie/> extends <AbsGraph> SUMMARY: Creates a pie chart graph. PROPERTY DESCRIPTION boolean drillable When true, the pie segment under the cursor is highlighted (defaults to false). GUI Reference 465 string format vague select vector data Id model EXAMPLE: The format for rendering pie labels (defaults to null). Returns the zero-based index of the currently-selected pie segment, or null. This property is settable. Direct data for the pie chart (defaults to null). This is used to build an internal data model. The data must be a vector of maps, where each map may have these keys: $value (the data value). $title (the title of the pie segment). $color (the color of the pie segment). The data model for the pie chart, which must be a method in the same class (defaults to null). It must have the following signature: vague myModel (native pie, symbol cmd, int idx) Possible values for cmd are: $count (return the number of data values). $value (return the value for the segment denoted by idx). $title (return the title for the segment denoted by idx). $color (return the color for the segment denoted by idx). <Graph.pie format="0" model=pieModel drillable=true/> <Graph.flow/> extends <AbsGraph> SUMMARY: Creates a flow graph, consisting of places and directed links interconnecting the places. Places and links can be individually selected or drilled. Flow graphs are useful for visualizing data flow and work flow concepts. PROPERTY DESCRIPTION boolean editable When true, the graph can be edited (defaults to false). Editing operations include: dragging a place to reposition it, dragging a knob of a place to resize it, creating a link between two places by dragging from the edge of a place to the edge of another place, dragging a link segment to bend it (add a knob), dragging a knob of a link to reconfigure it, dragging an end point of a link to another place to change its source/target place. When true, background grid lines are shown behind the graph (defaults to false). When set to $all, it resizes every place so that it nicely fits around its title/description. Alternatively, you can set this property to the index of a place, or a list or vector of place indices, to limit resizing to those places only. You cannot get this property. boolean grid vague autoSize 466 JavaGram Agile Development vector data Id model Direct data for the flow graph (defaults to null). This is used to build an internal data model. The data must be a vector of maps, where each map specifies a place or link, and may have these keys: $type (may be $place or $link). $shape (may be $box or $queue for a place, and $line for a link). $title (the title of the link or place). $desc (additional description, appearing below the title and separated from it by a horizontal line). $color (the color of the boundary of place or the line of a link, as a hexadecimal string). $bounds (a list of four integers identifying the boundary of a place in (x, y, width, height) order). $ends (a list of two integers, denoting the zero-based index of the source and target places for a link). If the link has intermediate points then these follow in the format (x, y). $invert (a boolean which, when set to true, causes the place or the link text to be displayed with an inverted background). Use this as a way of highlighting an object. The data model for the flow graph, which must be a method in the same class (defaults to null). It must have the following signature: vague myModel (native flow, symbol cmd, int idx, map edit) Possible values for cmd are: $count (return the number of graph objects). $type (return the type of the object denoted by idx). $title (return the title for the object denoted by idx). $desc (return the description for the object denoted by idx). $shape (return the shape for the object denoted by idx). $color (return the color for the object denoted by idx). $bounds (return the bounds for the place denoted by idx). $ends (return the source/target place indices for the link denoted by idx). $invert (return the invert status for the object denoted by idx). $meta (return the meta data for the object denoted by idx). $data (return the data for the object denoted by idx). $edit (update the object denoted by idx using the edit map, which may contain any key/value pair allowed in the data vague click GUI Reference property). When set to true, it puts the graph in ‘create’ mode. Provided the graph is editable, clicking anywhere on the graph canvas causes a $create event to be raised. The create mode is cancelled after the event is fired or when you set this property to false. 467 EVENTS: $create is raised when click is set to true and the user clicks on the graph canvas. You can obtain the click point as a list of (x, y) format by getting the click property. $link is raised when the user holds the mouse button down on a place edge and drags to another place and releases the mouse button. You can obtain the source/target places as a list of (srcIdx, tarIdx) format by getting the click property. $relink is raised when the user drags an end point of a link to another place. You can obtain the old/new places as a list of (oldIdx, newIdx) format by getting the click property. EXAMPLE: <Graph.flow editable=true data={fgData} event=fgHandler/> 11.2.17 Gadget Elements <Gauge/> extends <Comp> SUMMARY: Creates a meter-style gauge, similar to those used in a car dashboard. Use this component to visualize a value and colorcode its bands. The gauge value (denoted by its needle) can be accessed via the value property (defaults to 0.0). PROPERTY DESCRIPTION real min real max real target The minimum value for the gauge (defaults to 0.0). The maximum value for the gauge (defaults to 100.0). The target value for the gauge (defaults to null). When specified, this is shown as a white line on the gauge’s color band. Optional title for the gauge, which is displayed below it (defaults to null). Direct data for the gauge (defaults to null). This is used to build an internal data model. The data must be a vector of maps, where each map represents a band on the gauge and may contain the following keys: string title vector<map> data $value must map to a real value, denoting the end of the band. $title must map to a string, denoting the label for the end of the band. Id model $color must map to a hexadecimal color string (e.g., "0xFF0000"), denoting the color of the band. The data model for the gauge, which must be a method in the same class (defaults to null). It must have the following signature: vague myModel (native gauge, symbol cmd, int idx) Possible values for cmd are: $count (return the number of bands). $value (return the end value of the band denoted by idx). $title (return the title of the band denoted by idx). $color (return the color of the band denoted by idx). 468 JavaGram Agile Development <Gauge gauge lay=$center title="Sample Gauge" gaugeData={gaugeData} value=75/> EXAMPLE: where: vector<map> gaugeData = [ [$value=>0.0, $title=>"0%", $color=>"0xFFFFFF"] , [$value=>25.0, $title=>"25%", $color=>"0x990000"] , [$value=>50.0, $title=>"50%", $color=>"0xFF0000"] , [$value=>75.0, $title=>"75%", $color=>"0x0000FF"] , [$value=>100.0, $title=>"100%", $color=>"0x00FF00"] ]; 11.2.18 Map Elements <GoogleMap/> extends <Comp> SUMMARY: Creates an element for displaying a Google map. Note: To use this component, you must have JDICplus.jar and JDICplus_native_applet.jar in your class path (same is required for JADE). Also, because this component uses the IE DLL, if you use a proxy server for Internet access, you must ensure that it’s correctly specified in your Internet Explorer configuration. PROPERTY DESCRIPTION string key Denotes the map key. To get a valid map key for your domain, visit http://code.google.com/apis/maps/signup.html. Please note that a key only works for the specific domain for which it’s generated (e.g., ‘http://www.mydomain.com’). The map key must be set before accessing any other property. list at Specifies the center of the map as a list of two elements: (latitude, longitude), both of which must be real numbers. This property should not be accessed until the map is ready. boolean navControl When set to true, the map will include a navigation control for zooming and panning (defaults to false). boolean typeControl When set to true, the map will include a map type control for selecting the type of the map (defaults to false). int zoom Denotes the map zoom level, as a number in the range 1-19 (defaults to 1). vague geocode To find out the geo-coordinate of a location (e.g., country, state, suburb, address), set this property to a string representation of it. This property should not be accessed until the map is ready. The response (reported via the $geocode event) can be accessed by getting this property, which will be a map having these keys: $request (denotes the original request string), $error (denotes an error string when an error occurs), $response (denotes a vector when the request is successful). Each element of the vector denoted by $response is a list of three elements: (resolvedAddress, latitude, longitude). GUI Reference 469 vector markers EVENTS: Denotes the markers for the map, each being an instance of <MapMarker>. This property should not be accessed until the map is ready. $ready is raised when the map is ready for use. $geocode is raised when the response to a geo-coordinate request (by setting the geocode property) becomes available. EXAMPLE: <GoogleMap key="..." event=mapHandler/> <MapMarker/> extends <AbsComp> SUMMARY: Creates a marker for a Google map. Because a marker can’t be added to a map until the map is ready, it’s best to create a marker using gui.create() after the map becomes ready. To add a marker to a map, use the syntax map += marker. To remove it, use the syntax map -= marker. You can also create a collection of markers and add them in one go by setting the markers property of a map. PROPERTY DESCRIPTION list at Specifies the position of the marker on the map as a list of two elements: (latitude, longitude), both of which must be real numbers. The alignment of the marker image relative to the map coordinate it represents; must be one of $center, $north, $south, $east, $west, $northEast, $northWest, $southEast, or $southWest (defaults to $northWest, i.e., the marker’s top-left corner coincides with the coordinate). See description in <Comp>. For a default marker, this represents the border color of its image. See description in <Comp>. For a default marker, this represents the fill color of its image. Denotes the marker radius. When true, the marker is drawn with a shadow (defaults to true). Optional image to be used instead of the default marker image. This may be either an <Icon> or the path of a GIF file. Note that if title is also set, the default marker is used instead. Denotes the title that appears on the marker. When this property is set, the effect of image is cancelled and the default marker is used instead. Denotes the marker tooltip text. Denotes the <Font> to be used for title. Specifies whether the marker is visible on the map (defaults to true). Setting this property to a string causes an information window to appear above the marker, containing the string. Setting it to null causes the window to disappear. If the string starts with <html>, it will be rendered as HTML. symbol align string fgColor string bgColor int radius boolean shadow vague image string title string tooltip native font boolean show string info 470 JavaGram Agile Development boolean clickable boolean draggable Id event EVENTS: EXAMPLE: Specifies whether the marker is clickable (defaults to true). Markers that are not clickable generate no $click or $drill event, nor cause the hand cursor icon to appear when the cursor hovers over them. Specifies whether the marker can be dragged around the map (defaults to false). Markers that are not clickable generate no $click, $drill, or $drag event, nor cause the hand cursor icon to appear when the cursor hovers over them. The event handler for this element, which must be a method in the same class. $select is raised when the marker is clicked, provided the clickable property is true. $drill is raised when the marker is double-clicked, provided the clickable property is true. $drag is raised when the marker is dragged, provided the draggable property is true. $rollOver is raised when the mouse rolls over the marker. $rollOut is raised when the mouse rolls out of the marker. gui.create($MapMarker, map($at=>$(48.85, 2.34), $image=>sys.use("lib/gifs/House.gif"), $event=>Util.strToId("markerHandler")), this); <MapMarker.default/> extends <MapMarker> SUMMARY: Defines a map marker whose properties provide default property values for subsequent markers of any map (excluding at, show, image, info, and event properties). The default values take effect when the marker is added to a map (any map!), and lose their effect when it’s removed from a map. <MapRegion/> extends <AbsComp> SUMMARY: Creates a region for a Google map. A region is a closed polygon and behaves similarly to a marker. PROPERTY DESCRIPTION vector<list> bounds Specifies the region’s boundary as a vector of coordinates, each of which must be a list of two elements: (latitude, longitude), both of which must be real numbers. To test if a coordinate is within the region, set this property to the coordinate as a list of the format (latitude, longitude), and then get the property as a boolean. See description in <Comp>. See description in <Comp>. Specifies whether the region is visible on the map (defaults to true). The event handler for this element, which must be a method in the same class. vague test string fgColor string bgColor boolean show Id event GUI Reference 471 gui.create($MapRegion, map($bounds=>vec), this); EXAMPLE: 11.2.19 Jade Elements <Jade.editor/> extends <Comp> SUMMARY: Creates a syntax highlighting editor for viewing and editing JavaGram code, and real-time highlighting of errors. The editor can be configured to show three things: Editing pane (center) Gutter for showing line numbers (left) Ruler for showing errors/warnings and navigating to them (right). PROPERTY DESCRIPTION boolean readOnly When false, the code displayed by the editor can be edited (defaults to false). When true, the gutter to the left of the editor pane is shown (defaults to true). When true, the ruler to the right of the editor pane is shown (defaults to true). When true, tabs and end-of-lines are shown as visible characters (defaults to false). When true, a dimmed horizontal line is drawn before each class and each method, and a dimmed vertical line is drawn to mark the position of cols (defaults to true). The position of the guide column (defaults to 120). This is not a physical limit on line length, but simply a guide. The size of the tab character in terms of spaces (defaults to 4). The validation level, which may be one of these symbols: null (no validation) $syntax (syntax rules validation only). This is the default. $semantics (syntax and semantics rules validation) A list of two integers that, respectively, denote the beginning and end of the current selection; both are zero-based offsets from the start of the file. The selection is shown as: A blinking caret if the two offsets are the same (i.e., empty selection). A highlighted text segment (can expand over multiple lines) if the two offsets are different. boolean showGutter boolean showRuler boolean showMarkers boolean showGuides int cols int tabSize symbol validation list select 472 JavaGram Agile Development vague selectVal EVENTS: Getting this property returns null when the current selection is empty, and a string representation of the selection otherwise. You can set this property to one of these: null or $delete (causes the current selection, if any, to be deleted). $copy (copies the current selection to the clipboard). $cut (removes the current selection and places it on the clipboard). $paste (replaces the current selection with what’s on the clipboard). When true, all text is selected (defaults to false). To load a file into the editor, set this property to the file path. Returns the path of the currently-loaded file. Defaults to null. To save the editor content to a file, set this property to the file path. Returns the path of the currently-saved file. Defaults to null. When true, it means that there are unsaved changes (defaults to false). To undo editing events, set this property to the count of events you want undone. Returns the number of events that can be undone (defaults to 0). To redo editing events, set this property to the count of events you want redone. Returns the number of events that can be redone (defaults to 0). $modified is raised when the content is modified. EXAMPLE: <Jade.editor editor event=editHandler/> boolean selectAll string open string save boolean modified int undo int redo 11.2.20 Reference Elements <Indirect/> extends <AbsComp> SUMMARY: Allows you to use an element created elsewhere (in the same or another class). You can also specify any of the <Comp> properties in this element. However, these must appear after the ref property, and are assigned to the element denoted by ref. PROPERTY DESCRIPTION native ref The element being referenced. The effect is as if the referenced element had been defined in place of <Indirect>. EXAMPLE: <Indirect ref={gridPanel.panel}/> where gridPanel is an object whose panel field denotes a <Panel>. <NativeComp/> extends <Comp> SUMMARY: Allows you to create an element whose implementation resides externally (e.g., in a JAR file). PROPERTY GUI Reference DESCRIPTION 473 native ref The externally-implemented component. This is usually set to the result of a sys.java() call. EXAMPLE: <NativeComp ref={sys.java("com.metaphaseeditor.MetaphaseEditor", $native, "new")}/> 11.2.21 Invisible Elements <Null/> extends <Comp> SUMMARY: Creates an invisible element. This is useful when we need a valid but ‘empty’ component (e.g., inside a <Pane.split>). EXAMPLE: <Null/> <Plate/> extends <Comp> SUMMARY: Creates a boiler plate. This is useful for specifying properties such that they can be shared by other elements. EXAMPLE: <Plate Req fgColor="0xFF0000" tooltip="Required"/> <Req:Label title="Surname"/> <Timer/> SUMMARY: Creates a timer, which is a simple wrapper of Java’s Swing Timer class. This is similar to Timer, except that the event handler executes on the Swing event dispatching thread. PROPERTY DESCRIPTION int delay EVENTS: The delay, in milliseconds, between timer events firing (defaults to 10,000 milliseconds). The initial timer delay, in milliseconds (defaults to delay). When true, the timer fires only once (defaults to false). When set to true, the timer starts. Returns true if the timer is still running. When set to true, the timer stops. Returns true if the timer has stopped running. May be set to one of $auto, $manual, or $now (defaults to $manual). When set to $auto, the timer automatically restarts as a result of explicit or implicit gui.maintain(). When set to $manual, the timer’s activation can only be controlled via the start and stop properties. Setting this property to $now causes the timer to restart immediately. The timer event handler, which must be a method in the same class. $do is raised when the timer fires. EXAMPLE: <Timer timer delay=2000 event=sampleHandler once=true/> int initialDelay boolean once boolean start boolean stop symbol restart Id event <Worker/> 474 JavaGram Agile Development SUMMARY: Creates a worker, which is a simple wrapper of Java’s SwingWorker class. This is useful for performing lengthy tasks that would otherwise cause the GUI to freeze while the task is executing. Unlike normal threads, SwingWorker executes on Swing’s event dispatcher thread, thus enabling it to access GUI components. Because of the single-threaded nature of the Flash Player, this element will execute synchronously in a Flash client. PROPERTY DESCRIPTION boolean run When set to true, the worker is created and executes. Returns true if the worker is still running. When set to true, the worker aborts (if it’s still running). Returns true if the worker has been cancelled. This can be set to a value in the range 0 to 100, which causes the worker method to be later invoked (cmd = $progress). Returns its last value (or 0). You can set this property to an arbitrary value, causing the worker method to be later invoked (cmd = $process). Returns its last value (or null). Returns the value returned by the worker method in response to $do. Caution: getting this property blocks the thread until the worker completes execution. You can’t set this property. The worker’s handler, which must be a method in the same class, having the signature: boolean cancel int progress vague publish vague get Id worker vague worker (symbol cmd, vague val) This method is invoked by the JavaGram framework. Possible values for cmd are: $do is raised when the worker is run, in which case val is null. You should not attempt to do GUI operations in response to $do. $done is raised when the worker has finished, in which case val is null. $progress is raised when a value is assigned to the progress property, in which case val denotes the progress value. This gives you the opportunity to visually display the level of progress so far. You can safely do GUI operations in response to $progress. $process is raised when a value is assigned to the publish property. It’s possible that $process is raised once for multiple assignments. Therefore, val is always a vector of the latest assigned values. This gives you the opportunity to process data published so far. EXAMPLE: GUI Reference <Worker search worker=searchWorker/> 475 11.3 The Gui Pseudo Class 11.3.1 Gui Attributes GUI attributes denote useful values that are either set internally. Unless specified otherwise, GUI attributes are read-only. list gui.screen Summary: Denotes the screen size in pixels, as a list of two integers: (width, height). 11.3.2 Gui Methods void gui.run (native app) SUMMARY: Runs the <App> denoted by app in a separate thread and returns immediately. EXAMPLE: <App app> //... </App> //... gui.run(app) symbol gui.tag (native comp) SUMMARY: Returns the tag of an element as a symbol. EXAMPLE: <Option.radio r/> //... gui.tag(r) // returns: ${Option.radio} native gui.create (symbol tag, map<symbol,vague> props = null, vague context = null) 476 JavaGram Agile Development SUMMARY: Creates a GUI element procedurally (rather than declaratively) and returns it. The element’s tag name must be specified as a symbol. For qualified elements, use the escape notation (e.g., ${Area.text}), or use underscore instead of dot (e.g., $Area_text). The desired properties should be provided as a map, where each property name is specified as a symbol. If the component refers to a class method (e.g., event handler), you should pass the class context, which may be a class name (sufficient for static method) or an object (required for non-static methods). For a property that expects an ID (e.g., event), use Util.strToId() (as defined lib/lang/Common.jag) to construct the ID. Note: when you create a GUI element declaratively, the properties are applied in the order declared. Because a maps keys are always sorted, gui.create() will apply the properties denoted by props not in the order you write them, but in lexicographic order. To force a different order, exclude the properties that you want to be set last and then set them after the element is created. Wherever possible, you should create GUI elements declaratively. Use gui.create() only when you need to create an element dynamically, such as in response to some user action. On rare occasions, you may need to create a GUI element on the server side (e.g., to produce an image from a graph, for insertion into a report). Because declarative GUI elements are filtered out of server code, gui.create() is the only way you can create server-side GUI elements. EXAMPLE: gui.create($Node, [$title=>"Sample"]) vague gui.get (native comp, symbol prop) SUMMARY: Returns comp GUI element’s property value denoted by prop. EXAMPLE: gui.get(node, $title) vague gui.set (native comp, symbol prop, vague value) SUMMARY: Sets comp GUI element’s property value denoted by prop to value. EXAMPLE: gui.set(node, $image, sys.use("gifs/OpenFolder.gif")) native gui.getCell (native grid, int row, int col) SUMMARY: Returns the cell in grid denoted by row and col, both of which are zerobased, or null if the values are out of range. EXAMPLE: gui.getCell(grid, 0, 2) boolean gui.bind (native comp, vague record) GUI Reference 477 SUMMARY: Binds the element denoted by comp to the data denoted by record. The types must be consistent. For example, when comp is a <Field.text>, record must be a string, and when comp is a <Panel>, record must be a map or object. For composite elements, binding is done recursively and hierarchically, by matching the key property of each child element against the same map key or field name in the data. EXAMPLE: gui.bind(panel, data) boolean gui.save (native comp, vague record) SUMMARY: Performs the reverse of gui.bind() by saving the information present in comp into record. The same rules apply. EXAMPLE: gui.save(panel, data) void gui.maintain (native comp, boolean force = false) SUMMARY: Causes the visible and enable properties of comp (and its children, if any) to be evaluated, thus changing the visual state of the element(s). This is normally called by the framework for you, but there may be situations where you want to explicitly call it. When force is true, the two properties are evaluated regardless of the state of the element. EXAMPLE: gui.maintain(panel) void gui.saveImage (native comp, string imagePath, int width = null, int height = null, symbol callback = null, list extraCBArgs = null) SUMMARY: Saves the image of the element denoted by comp in the file whose absolute path is denoted by imagePath. The currently supported formats are GIF and PNG, so imagePath must have one of these two extensions. You can specify the desired size of the image using width and height. Otherwise, the actual size is used. When width and height are zero (e.g., the element not yet rendered) these default, respectively, to 600 and 400. In a Flash client, width and height are ignored. Furthermore, the image is sent to the server to be saved as a file whose relative location is denoted by imagePath. Upon saving the image, callback is called if present. This should be a method in the same class, and must have at least a vague parameter, followed by optional parameters if desired (use extraCBArgs to pass values for the additional parameters as a list of arguments). The first parameter receives either an Exception or an information map of the form: EXAMPLE: [$local=>strPath, $remote=>strPath, $size=>intVal, $timestamp=>intVal] gui.saveImage(panel, "C:/temp/PanelImage.gif") void gui.transformImage (string srcImage, list transform, string dstImage, int dstSize = null, symbol readyCallback = null, list extraCBArgs = null) 478 JavaGram Agile Development SUMMARY: Transforms srcImage according to transform and writes the result to dstImge path with the specified width and height, where transform may be a list of the following transformation instructions: ($translate dx dy) translates the image by real values dx and dy. ($scale sx sy) scales the image by real values sx and sy. ($rotate angle) rotates the image real angle expressed in radians (positive angle rotates in clockwise direction). ($rotate angle x y) rotates the image about real coordinates x and y by real angle expressed in radians. When dstSize is not specified, it’s inherited from the source image, but adjusted for scaling. When transform is null but dstSize is specified, the image is scaled so that its diagonal size matches dstSize. When readyCallback is specified, it is called when the target image has been converted. You should use it in AIR deployments, because of asynchronous behavior. The callback can also be declared with extra parameters, in which case, matching arguments must be provided as list for extraCBArgs. Note that source and target images need not be of the same format. For example, if the source image is GIF and the target is PNG, format conversion will take place. Supported image formats include: GIF, JPG, and PNG. EXAMPLE: gui.transformImage("C:/temp/PanelImage.gif", $(($scale, 0.5, 0.5)($rotate, 3.14)), "C:/temp/PanelImage2.jpg") string gui.imageAsHex (native comp, int width = null, int height = null) SUMMARY: Same as gui.saveImage() except that instead of saving the image to a file, a string version of the image is returned. This string is encoded as readable hexadecimal values, suitable for embedding in a file. The assumed image format is PNG for Java runtime and JPG for Flash runtime. EXAMPLE: gui.imageAsHex(panel) void gui.onPreview (symbol callback) SUMMARY: This method is for Flash clients (has no effect in Java clients). It defines a callback for handling component preview requests, which should have the signature: void previewHandler (native comp) The component may be a graph or a Google map component. The handler is invoked when the user double-clicks the component, receiving the component as argument. EXAMPLE: gui.onPreview($previewHandler) void gui.toHtml (native comp, stream out = null, string dir = null) GUI Reference 479 SUMMARY: Writes an HTML representation of comp and its data to the stream denoted by out, or sys.out when out is null. It ignores containers whose report property is set to false. The dir parameter specifies the directory into which the caller intends to place the HTML file (defaults to the current user directory) – any generated image files are placed in this directory. EXAMPLE: gui.toHtml(panel) symbol gui.alert (string title, string message, native owner, symbol kind, symbol res SUMMARY: This is a dynamic version of <Alert/>. It’s useful for one-off situations where you don’t want the overhead of creating an element. Only four of the <Alert/> properties are supported. The alert box contains one button, unless kind is set to $question, in which case it contains three buttons: “Yes”, “No”, and “Cancel”. The method returns a symbol that denotes the pressed button (i.e., one of: $ok, $yes, $no, $cancel). If the alert is simply close, $ok or $cancel is returned. In a Flash client, always null is returned. You can optionally provide a resultCallback, which should denote the name of a method in the same class, as a symbol, having the signature: void callback (symbol result) When the alert is closed, this method is called with the alert result as argument. The callback can also be declared with extra parameters, in which case, matching arguments must be provided as list for extraCBArgs. EXAMPLE: gui.alert("Save", "Save changes?", mainFrame, $question) void gui.loadFont (string fontSwfFile, string fontName, symbol callback = null) SUMMARY: This function is intended for Flash clients (in native clients it won’t do anything) and allows you to load an embedded font that has been packaged as an SWF file, whose path is denoted by fontSwfFile. You can optionally provide a callback, which should denote the name of a method in the same class, having the signature: void callback () If the font is successfully loaded, the callback (if any) is called. EXAMPLE: 480 gui.loadFont("C:/flash/_MSGothic.swf", "_MSGothic", $onLoad) JavaGram Agile Development NOTE: To package a font as an SWF file, create an ActionScript project in the Flex IDE for Eclipse and define an ActionScript class like this: package { import flash.display.Sprite; import flash.text.*; public class _MSGothic extends Sprite { [Embed(source='C:/fonts/msgothic.ttf', fontName='_MSGothic')] private static var font:Class; public static function getFont():Class { return font; } } } The fontName should be chosen such that it doesn’t clash with device fonts. To ensure this, it’s recommended that you start it with an underscore. boolean gui.camera (symbol action, symbol callback = null) SUMMARY: This method is only available for AIR on a mobile device. To register a camera, pass $register for action and a valid callback of the signature: Void cameraCallback (string image) If the registration is successful, the method will return true. Otherwise (e.g., if the device has no accessible camera) it will return false. To take a picture, pass $launch for action. This will display the device’s user interface for taking a picture. Once the picture is taken and accepted, the callback method will be called with the URL of the image file. EXAMPLE: gui.camera($register, $camCallback) boolean gui.geoLocation (symbol callback) SUMMARY: This method is only available for AIR on a mobile device. To register a listener pass a callback of the signature: void geoCallback (map<symbol,real> details) If the registration is successful, the method will return true. Otherwise (e.g., if the device has no accessible GPS) it will return false. When the device’s geo-coordinate changes, the callback is called with a map having the following keys: $latitude denotes latitude in meters. $longitude denotes longitude in meters. $altitude denoted altitude in meters. $speed denotes speed in meters per second. $heading denotes the direction of movement (with respect to true north) in integer degrees (null on Android). $hAccuracy denotes the horizontal accuracy in meters. $vAccuracy denotes the vertical accuracy in meters. EXAMPLE: GUI Reference gui.geoLocation($geoListener) 481 12 SQL Reference This chapter specifies the database access features of JavaGram. Use this chapter to look up SQL topics once you’ve become familiar with the language. For a tutorial style introduction to JavaGram’s SQL capabilities please refer to Chapter 5. None of the SQL facilities described in this chapter is available in the Flash JavaGram runtime. 12.1 Query and Transaction Statements The query and transaction statement blocks respectively detailed in Sections 10.7.15 and 10.7.16 delimit scope around database operations to control the allocation and deallocation of connections, statements, and result sets, and finally the timing of database commits. Failure to enclose queries and transactions within appropriate delimiting blocks will result in runtime exceptions. 12.2 Markup Qualified <text> members can be used to create parameterized SQL. In addition to the facilities described for text members in Section 10.8.6, the substitution of parameters within dynamic SQL and prepared statements is catered for. 12.2.1 Field Translation Field names in queries are automatically translated according to these rules: An all-lowercase field will remain unchanged (e,g., salary). A field containing underscores is translated such that it becomes all-lowercase, except for letters immediately succeeding underscores, which become uppercase (e.g., total_amount and Total_AMOUNT will both become totalAmount). To keep an underscore unchanged, use a dash instead (e.g., TOTAL-AMOUNT becomes total_amount). The above translations are applied to the field names as specified by the query, including any renaming specified using the SQL as command (e.g., in select name as client_name from client, the effective field name is client_name which is then translated to clientName; to keep the underscore, write the query as select name as "client-name" from client). 12.2.2 Text.sql This element is used to return a string of dynamic SQL. An arbitrary number of parameters are substituted into the text, with automatic escaping for strings where parameters are enclosed within {` and }. In the interests of avoiding inadvertent errors, 482 JavaGram Agile Development enclosing between { and } is not permitted. For the infrequent cases where substitution without escaping is required, the variable should be enclosed between {= and }. Example: <text.sql string deleteTable(string tableName)> drop table {=tableName} </text.sql> <text.sql string getUpdateSQL(int orderNum, string comment)> update order_table set comment = {`comment}, visited = visited + 1, updated_at = now() where order_num = {`orderNum} </text.sql> Usage: transaction ($oracleDb) { string sqlCmd = getUpdateSQL(13162, "urgent delivery"); int rowCount = sql.execUpdate($oracleDb, sqlCmd); } 12.2.3 Text.sql.query This element offers the capabilities of the text.sql element in creating dynamic SQL but rather than returning the string, it executes the generated query against the specified database. Specification of the database through the db property is mandatory with this element. The method is defined with the return type being native, because a result set is expected to be returned. Example: <text.sql.query native findUser(string user) db=$mysqlDb> select * from application_user where user_name = {`user} </text.sql.query> Usage: query ($mysqlDb) { native rs = findUser("jan.brown"); if (sql.next(furs)) sys.println(sql.getRow@map(rs)); } SQL Reference 483 12.2.4 Text.sql.update This element is very similar to the text.sql.query element, except that it’s used for updates rather than queries. Accordingly, it returns an int representing the number of rows affected rather than a native. Example: <text.sql.update int updateSurname(string user, string surname) db=$mysqlDb> update application_user set surname = {`surname} where user_name = {`user} </text.sql.update> Usage: query ($mysqlDb) { int rowsUpdated = s.updateSurname("u1234", "Atkins"); } 12.2.5 Text.sql.prepare This element is used to create a prepared statement that may later be executed one or more times with different parameters. Parameters escaped by being enclosed between {? and } generally refer to member variables within the class. These are not substituted at the time of preparing the statement but rather are assigned at the time the SQL is executed. Example: class MyClass { final private Delivery d = new Delivery(); <text.sql.prepare native getInsertStmt() db=$oracleDb> insert into deliveries (consignment, distance, rate, note_date) values ({?d.consignment}, {?d.kms}, {?d.rate}, now()) </text.sql.prepare> //... } Usage: transaction ($oracleDb) { native ps = getInsertStmt(); sql.execUpdate(ps); } 12.2.6 Text.sql.callable This element is used to create a callable statement, enabling the calling of stored procedures. This element is used very much like text.sql.prepare, the difference being that the return value can later be used to set output as well as input parameters. Example: <text.sql.callable native getCallableStmt() db=$oracleDb> 484 JavaGram Agile Development call sp_get_account_transactions ( {?b.accountNum}, {?b.dateFrom}, {?b.dateTo}, {?b.out} ) </text.sql.callable> Usage: query ($oracleDb) { native callable = getCallableStmt(); sql.registerOutParam(callable, 4, "oracle.jdbc.OracleTypes.CURSOR"); sql.exec(callable); vague result; // either an int or a result set while ((result = sql.nextResult(callable)) != null) { if (result instanceof int) sys.println("Update count: ", result); else while (sql.next(result)) sys.println(sql.getRow@map(result)); } result = sql.get(callable, 4); // output param 4 is a result set while (sql.next(result)) sys.println(sql.getRow@map(result)); } 12.2.7 Element Properties For all but the text.sql element, the db property must be specified to allow the element to be applied to the correct database. In most cases, additional properties are not required but can optionally be specified. Here is an example showing many of the available properties: <text.sql.prepare native getPrepStmt() db=$mysqlDb timeout=10 // Timeout set to 10 seconds concurrency=$readOnly holdability=$closeCursorsAtCommit type=$forwardOnly> //... </text.sql.prepare> The following table summarizes the available properties. Property db timeout SQL Reference Description This property is set to a symbol that refers to a section within the application configuration file specifying database URL, username, password, and the like. This property allows specifying a timeout period in seconds for when the statement is executed. 485 concurrency This property controls the capabilities of result sets returned after the query is executed. Possible values are $readOnly the default value, result set may not be updated. $updatable result set can be updated. This property is not normally needed but can be used for fine control of the timing of closing result sets relative to commits. Possible values are: $holdCursorsOverCommit $closeCursorsAtCommit The default is determined by the underlying database and JDBC driver. By default, result sets are accessed only by using sql.next(), moving from the first row forward. This property can be used to allow the result set to be accessed in other ways, using one or more of sql.previous(), sql.first(), and sql.last(). Possible values are: $forwardOnly is the default. $scrollInsensitive allows the other access methods but is generally insensitive to data changes underlying the result set. $scrollSensitive is generally sensitive to underlying data changes. holdability type 12.2.8 Capability Summary The following table summarizes the return type, permissible properties, and valid escape syntax for each SQL element type. Element: Returns: text.sql text.sql.query text.sql.update text.sql.prepare text.sql.callable string native int native native (result set) (prepared stmt) (callable stmt) Property db required required required required timeout optional optional optional optional concurrency optional optional optional holdability optional optional optional type optional optional Escaping {?…} {`…} {=…} 486 JavaGram Agile Development 12.3 Database Connection Configuration The –config command line option can be used to nominate a program configuration file, specified as a map. Within this map, the $database key points to a map of maps, each specifying the configuration of a database connection. In the example below, there is one database connection specification called $app2Db, which depicts a standard set of keys known to the JavaGram runtime: $name (a descriptive name) $driver (the JDBC driver class name) $url (the database URL) $user (username for database login) $password (password for database login). This password string may optionally be enclosed in parentheses to signify that it is encrypted. Where using encrypted passwords, the command line parameter, -passKey, must be used when starting the server to specify the key to be used to decipher. [$database => [$app2Db => [$name => "Oracle App2 DB" ,$driver => "oracle.jdbc.OracleDriver" ,$url => "jdbc:oracle:thin:@acme:1213:app2" ,$user => "test" ,$password => "secret" ] ] ] The second example below adds the set of keys which are each optional: $schema (nominates a schema other than the default database schema) $timeout (specifies login and/or query timeouts in seconds) $pool (allows more control over the behavior of the JDBC connection pool) $queryRows (specifies the maximum number of rows before warnings and errors are reported from the lib/*AsVector methods) [$database => [$app2Db => [$name => "Oracle App2 DB" ,$driver => "oracle.jdbc.OracleDriver" ,$url => "jdbc:oracle:thin:@acme:1213:app2" ,$user => "test" ,$password => "secret" ,$schema => "App2" SQL Reference 487 ,$timeout => [$login=>30, $query=>20] ,$pool => [$min=>1, $core=>3, $max=>10, $reduce=>[$inact=>120]] ,$queryRows => [$error => 2000, $warn => 500] ] ] ] Where the application connects to more than one database, some configuration details may be common. One set of details may be used as a template within another to avoid the need to repeat information. Example: [$database => [$mydefaults => [$timeout => [$login=>30, $query=>20] ,$driver => "oracle.jdbc.OracleDriver" ] ,$oracleDb => [$defaults => $mydefaults ,$name => " Oracle App2 DB" ,$url => "jdbc:oracle:thin:@acme:1213:app2" ,$user => "test" ,$password => "secret" ,$schema => "App2" ] ] ] 12.4 The Sql Pseudo Class native sql.execQuery (native tagOrConnOrStmt, string sqlCmd = null) SUMMARY: This method is used to execute a database query, returning a result set. One usage of this method is to pass a database connection and a string containing dynamic sqlCmd. Under this usage, the required statement is implicitly opened and closed. Where the result set should have some special property, a statement object can be created prior to the call and passed in as an alternative to the connection. It’s then the responsibility of the programmer to close this statement. Where executing a prepared or callable statement, this method is called with just that one parameter. Immediately prior to executing the query, this method internally sets all of the parameters within the statement. EXAMPLE: native rs1 = sql.execQuery(conn, "select now()"); native rs2 = sql.execQuery(prepStmt); int sql.execUpdate (native tagOrConnOrStmt, string sqlCmd = null) 488 JavaGram Agile Development This method is used to execute a database update, returning a count of records affected. One usage of this method is to pass a database connection and a string containing dynamic sqlCmd. Where executing a prepared or callable statement, this method is called with just that one parameter. Prior to executing, this method internally sets all of the parameters within the statement. EXAMPLE: int rc1 = sql.execUpdate(conn, insertSql); int rc2 = sql.execUpdate(prepStmt); int sql.exec (native stmt) SUMMARY: This method is more complex to use than other methods, but can handle multiple result sets from callable statements. It’s called with just one parameter – a prepared or callable statement. Prior to executing, this method internally sets all of the parameters within the statement. EXAMPLE: sql.exec(stmt); native sql.createStmt (vague tagOrConn, map opts = null) SUMMARY: Returns a new statement object. Where opts is specified, the type, holdability, and concurrency of any result sets created is further controlled. Possible values for these are the same as those used for the text.sql.prepare element. EXAMPLE: native s = sql.createStmt($myDb); native s2 = sql.createStmt($myDb, [$type => $forward_only, $concurrency => $read_only, $holdability => $close_cursors_at_commit]); native sql.prepare (vague tagOrConn, string sqlCmd, map opts = null) SUMMARY: Often a text.sql.prepare element is the simplest choice for creating a prepared statement. This method provides additional flexibility as it can be used where the sqlCmd is dynamically formed. Where opts is specified, the type, holdability, and concurrency of any result sets created is further controlled. Possible values for these are the same as those used for the text.sql.prepare element. EXAMPLE: string sqlCmd = "select * where surname = ? and firstname = ? and dob = ?"; native ps = sql.prepare($myDb, sqlCmd); native ps2 = sql.prepare($myDb, sqlCmd, [$type => $forward_only, $concurrency => $read_only, $holdability => $close_cursors_at_commit]); native sql.callable (vague tagOrConn, string sqlCmd, map opts = null) SQL Reference 489 SUMMARY: Often a text.sql.callable element is the simplest choice for creating a callable statement. This method provides additional flexibility as it can be used where the sqlCmd is dynamically formed. Where opts is specified, the type, holdability, and concurrency of any result sets created is further controlled. Possible values for these are the same as those used for the text.sql.prepare element. EXAMPLE: string sqlCmd = "{call getCustomer(?)}"; native cs = sql.callable($myDb, sqlCmd); native cs2 = sql.callable($myDb, sqlCmd, [$type => $forward_only, $concurrency => $read_only, $holdability => $close_cursors_at_commit]); native sql.set (native ps, int columnNum, vague value) SUMMARY: Sets the parameter values corresponding to question mark placeholders in the original SQL of the prepared or callable statement. EXAMPLE: sql.set(ps, 1, "Smith"); sql.set(ps, 2, "Peter"); sql.set(ps, 3, [#1978-06-30]); native sql.setBinFile (native ps, int columnNum, string filePath) SUMMARY: Similar to sql.set(), except that this is used for storing a binary file into a binary/blob table column. EXAMPLE: sql.setBinFile(ps, 1, "Sample.gif"); native sql.getBinFile (native rs, int columnNum, string filePath) SUMMARY: Similar to sql.get(), except that this is used for retrieving a binary file from a binary/blob table column. EXAMPLE: sql.getBinFile(rs, 1, "Sample.gif"); void sql.registerOutParam (native callableStmt, int column, vague type = null, boolean alsoIn = false) SUMMARY: Registers the specified column as an output parameter to the callable statement. The column is specified as a one-based column index. type is one of the type codes from the Java class java.sql.Types (including INTEGER, DATE and VARCHAR) or optionally specified as a Java class name. type is case sensitive. alsoIn allows the parameter to be used both for input and output (default is output only). EXAMPLE: sql.registerOutParam(cs, 3, "INTEGER", true); sql.registerOutParam(cs, 4, "oracle.jdbc.OracleTypes.CURSOR"); boolean sql.next (native rs) 490 JavaGram Agile Development SUMMARY: Returns true if the result set contains an as-yet unseen row and moves to that next row; otherwise, it returns false. In other words, the first call returns true if one or more rows exist and positions to the first row. EXAMPLE: while (sql.next(rs)) { … } boolean sql.previous (native rs) SUMMARY: Attempts to move the result set to the previous row, returning true if successful and false otherwise. EXAMPLE: if (sql.previous(rs)) { … } boolean sql.first (native rs) SUMMARY: Attempts to move the result set to the first row, returning true if successful and false otherwise. EXAMPLE: while (sql.first(rs)) { … } boolean sql.last (native rs) SUMMARY: Attempts to move the result set to the last row, returning true if successful and false otherwise. EXAMPLE: while (sql.last(rs)) { … } vague sql.nextResult (native prepStmt) SUMMARY: For prepared statements, this method steps through multiple result sets. Each call may return a result set, an integer representing an update count, or null when the results are exhausted. The number of result sets returned is dependent on the query performed. EXAMPLE: while ((result = sql.nextResult(callable)) != null) { if (result instanceof int) sys.println(result); else while (sql.next(result)) sys.println(sql.getRow@map(result)); } vague sql.get (native resultSetOrCallStmt, vague column) SUMMARY: Retrieves the specified column from the current row of the result set or output parameters of a callable statement. The column may be given as a symbol representing the column name or as a one-based column index. EXAMPLE: string s @= sql.get(rs, $surname); int i @= sql.get(rs, 4); // column 4 is a database NUMBER(5) vague sql.getRow (native rs) SQL Reference 491 SUMMARY: Returns a representation of the current row of the result set. By default, this is a map where each entry maps a column name symbol to a corresponding value whose type is automatically deduced from the database column type. Where a method cast is specified, an object of the required type is created, and where column names and object field names match, the columns from the result set are assigned to the object’s fields. EXAMPLE: vague m = sql.getRow(rs); // returns a map Customer c = sql.getRow@Customer(rs); void sql.update (native rs, vague column, vague newValue) SUMMARY: In preparation for updating the current row of the result set, this method allows modification of the value of one column. A series of calls to alter different columns may be made. The column may be specified as a symbol representing the column name or as a one-based column index. EXAMPLE: sql.update(rs, $height, 123.456789); void sql.updateRow (native rs) SUMMARY: Commits to the database the changes to the result set row earlier made using sql.update(). EXAMPLE: sql.updateRow(rs); native sql.getGeneratedKeys (native stmt) SUMMARY: Returns a result set containing the values of all of the auto generated keys created at the last insert on this statement. EXAMPLE: native rs = sql.getGeneratedKeys(stmt); void sql.setMaxRows (native stmt, int rows) SUMMARY: Sets a limit on the number of rows in any result set created by executing this statement. Any rows in excess of this limit are silently dropped. EXAMPLE: sql.setMaxRows(stmt, 50); int sql.getQueryRowsError (native tagOrConnOrStmt) SUMMARY: Gets the limit on the number of rows retrieved from a result set before an error is thrown. By default, this value is 0, indicating an infinite number of rows may be handled before an error of this type is raised. EXAMPLE: int rows = sql.getQueryRowsError($myDb); void sql.setQueryRowsError (native tagOrConnOrStmt, int rows) SUMMARY: Sets the limit on the number of rows retrieved from a result set before an error is thrown. This limit can be used to prevent programming errors that lead to enormous memory allocations. EXAMPLE: 492 sql.setQueryRowsError($myDb, 2000); JavaGram Agile Development int sql.getQueryRowsWarn (native tagOrConnOrStmt) SUMMARY: Gets the limit on the number of rows retrieved from a result set before warning messages are logged. By default, this value is 0, indicating an infinite number of rows may be handled before a warning of this type is given. EXAMPLE: int rows = sql.getQueryRowsWarn($myDb); void sql.setQueryRowsWarn (native tagOrConnOrStmt, int rows) SUMMARY: Sets the limit on the number of rows retrieved from a result set before warning messages are logged. This limit can be used to prevent programming errors that lead to enormous memory allocations. EXAMPLE: sql.setQueryRowsError($myDb, 200); string sql.getCursorName (native rs) SUMMARY: Returns the name of the SQL cursor underlying this result set. EXAMPLE: string name = sql.getCursorName(rs); int sql.getColumnCount (native rs) SUMMARY: Returns the number of columns within this result set. EXAMPLE: int c = sql.getColumnCount(rs); vague sql.getColumnMetaProperty (native rs, int column, symbol property) SQL Reference 493 SUMMARY: Gets a metadata value for a property of a column (specified using its onebased index). Valid properties include: Property EXAMPLE: Type Sample Value $autoIncrement boolean true $caseSensitive boolean false $catalogName string null $columnClassName string $java.lang.Long $columnDisplaySize int 10 $columnLabel string $ID $columnName string $ID $columnType int 4 $columnTypeName string ${INTEGER UNSIGNED} $currency boolean false $definitelyWritable boolean true $nullable int 0 $precision int 10 $readOnly boolean false $searchable boolean true $scale int 0 $schemaName string null $signed boolean false $tableName string $ORDERS $writable boolean true vague cn = sql.getColumnMetaProperty(rs, i, $columnName); native sql.loadDriver (string classPath , int timeout = 0) SUMMARY: Loads the JDBC driver(s) found in the specified classPath. The login timeout is specified in seconds, (defaults to zero, meaning no timeout). This method and sql.connect() are not normally required because query and transaction blocks, together with configuration information automatically take care of loading the driver and obtaining a connection. EXAMPLE: sql.loadDriver("com.mysql.jdbc.Driver"); native sql.connect (symbol configSym) native sql.connect (string url, string user, string password) SUMMARY: Returns a connection to a database. The preferred usage of this method is to pass a symbol referring to a section in the application’s configuration file. This method is best used only in test programs because the query and transaction blocks provide equivalent functionality in a more convenient and robust form. EXAMPLE: 494 native conn = sql.connect($oracleDb); native conn = sql.connect("jdbc:mysql://server2/db_c", "app", "password1"); JavaGram Agile Development void sql.close (native dbResource) SUMMARY: Closes a database statement, result set, or connection. Many developers will never need to call this as it’s not required when the database resource is created in a query or a transaction block, as these automatically close any resources created. May be used to close resources before the block end is encountered. EXAMPLE: native conn = sql.connect("jdbc:mysql://server2/db_c", "app", "password1"); sql.close(conn); 12.5 Business Object Manager The Business Object Model (bom) is a persistence layer that simplifies the storage and retrieval of JavaGram application data held in a database. This is achieved by subclassing lib/bom/Object and using its methods, which include persist() and find(). To enable these simple interfaces, you need to specify the mapping between a database table and the corresponding JavaGram class. Example: <jag> <load>"lib/bom/Object"</load> <load>"lib/bom/UserLockableObject"</load> class Customer extends UserLockableObject { int myid; string lastname, givenname, email, hphone, mphone, path, type; static { defineTable(Customer, [$db => $mysqlDb2, //$oracleXeDb, $table => $CUSTOMER, $columns => [ $id => $myid, // long form, identical to $surname => $lastname: $surname => [$fields => $lastname], // implicit, not required: //$givenname => $givenname, $rest => [$fields => [$email, $hphone, $mphone]], $contract_image => [$fields => $path, $viaFile => $binary, $extend => true] ], $lock => [$column => $locked_by] ]); } public slocal static <text.sql.update int createTable () db=$mysqlDb2> create table if not exists customer ( id integer not null auto_increment, surname varchar(32) not null, givenname varchar(32) not null, contract_image mediumblob, SQL Reference 495 rest text, locked_by integer, primary key (id) ) </text.sql> … } Recognized tags within the database table definitions are shown below in bold. Tag Example Usage $db $table $columns $myOracleDb $ACCOUNT $surname => $lastname $surname => [$fields => $lastname] $other => [$fields => [$email, $phone]] $other => [$fields => [$email, $phone], $extend => true] $other => [$fields => [$email, $phone], $compress => true] $image => [$fields => $path, $viaFile => $binary] $lock 496 [$column => $locked_by] Description Database tag from Jag configuration. Database table name. Set of mappings from database column name to Jag field name. Causes the database column surname to map to the member variable lastname. Long form, with the same meaning as the previous example. This form allows the introduction of additional qualifiers. Causes the email and phone member variables to be automatically serialized and de-serialized together on placement and retrieval from the database column other. The addition of $extend causes the database column $other to be regarded as extended information, that is not inserted, updated or selected by default. To persist (or retrieve) an extended field, setExtendOn(true) should be called on the object. The addition of $compress causes the data to be automatically compressed before insertion into the database and decompressed on retrieval. On insert/update, the $viaFile clause causes content to be read from the file named by $path. Thus, $path might be set to “/temp/portrait6602.jpg” by the programmer prior to insert. On select, as each row is retrieved, Object.getViaFilePath() is called, allowing the programmer to set a filename the data should be read into. The value of $viaFile may be $binary or $char. The $lock directive allows this table to be locked with the locking user id stored in the $locked_by column. JavaGram Agile Development 12.6 The Bom Pseudo Class The bom pseudo class underlies the lib/bom/Object class. Most users will use the facilities of lib/bom/Object and have no need to make direct use of the methods described here. Therefore, the descriptions here are of interest mainly in assisting understanding of the lib/bom/Object class. vector<vague> bom.find (vague klass, vague criteriaOrField, vague value = null) SUMMARY: This method uses the database mappings for klass to search the database table for matching records and return a vector of objects of type klass. When criteriaOrField is a map, its conditions are matched against the database rows that are to be included in the returned vector. Possible map keys are $exact or $like, either of which should denote a map of object field symbols (that correspond to some table column) against their desired value. When criteriaOrField is null or empty or its $exact and $like entries are both empty, a wildcard search is performed. When criteriaOrField is a symbol, all rows whose nominated field match value are returned (this is similar to bom.findOne() except that a vector of matching objects is returned). EXAMPLE: vector<Customer> vec @= bom.find(Customer, [$exact => [$givenname => "Bob", ...], $like => [$lastname => "Sm%", ...]] ); Object bom.findOne (vague klass, symbol field, vague value) SUMMARY: This method uses the database mappings for klass, together with an object field name and required value. The first matching row (if any) is returned. Otherwise null is returned. EXAMPLE: Customer cust @= bom.findOne(Customer, $id, 100); vague bom.getId (Object object) SUMMARY: This method returns the unique identifier (primary key) of object. EXAMPLE: vague id = bom.getId(this); void bom.persist (Object object) SQL Reference 497 SUMMARY: This method saves object to the underlying database. A database update is performed if this object originated from a bom.find() call or if there has been a previous bom.persist() call. Otherwise a database insert is performed. Where an insert is being performed, bom.persist() calls Object.handlePrimaryKey(), giving the application class the opportunity to set the database primary key. This method may either set the primary key and return true or simply return false, signaling that the database will automatically assign a primary key as the object is inserted. EXAMPLE: bom.persist(this); void bom.delete (Object object) SUMMARY: This method deletes object from the underlying database. EXAMPLE: bom.delete(this); void bom.refresh (Object object) SUMMARY: This method reads object from the underlying database again, synchronizing to the latest database content, discarding any in-memory changes. EXAMPLE: bom.refresh(this); void bom.lock (Object object, vague byUserId) SUMMARY: This method attempts to assert a lock at the database level. An exception is thrown if this operation fails. The exact action taken depends on the underlying locking mechanism chosen for this business object. In the default locking model, the database record for this object is updated with the supplied user ID stored in a lock column. EXAMPLE: bom.lock(this, "tom.maxwell"); void bom.unlock (Object object, vague byUserId) SUMMARY: This method removes a lock previously asserted at the database level. An exception is thrown if this operation fails. The exact action taken depends on the underlying locking mechanism chosen for this business object. In the default locking model, the database record for this object is updated to remove the supplied user ID stored in the lock column, leaving the database record in its original state. EXAMPLE: bom.unlock(this, "tom.maxwell"); vague bom.lockedBy (Object object) SUMMARY: This method queries the database for the current locker of object. Either the current locker or null is returned to indicate that the record is not currently locked. EXAMPLE: 498 vague locker = bom.lockedBy(this); JavaGram Agile Development SQL Reference 499 13 Library Reference This chapter describes the standard library scripts that are bundled with every JavaGram release. These scripts provide a variety of classes that make JavaGram programming easier and more productive. They also promote design patterns that represent best practice. Use them wherever appropriate in your projects. The scripts are organized by domain names that mirror their directory structure, and are illustrated here as UML diagrams. 13.1 Lib/lang/ This directory contains scripts that provide ‘linguistic’ support. Lib/lang/Clipboard.jag contains the singleton Clipboard class which provides a generic way of storing and retrieving clipboard data (indexed via symbols) for your applications. Lib/lang/Common.jag contains a number of classes for performing common tasks in applications: Util provides a set of static members for memory, file, and string handling. Desktop provides methods for opening, editing, and printing files. Thread serves as the base class of application-defined threads. Simply subclass it and implement its abstract run() method. ThreadLocal provides a way of defining static variables that are local to threads. Timer provides a mechanism for defining non-GUI timers, so it’s especially suitable for servers. Simply instantiate it and schedule timer tasks using its schedule() method. TimerTask serves as the base class of all timer tasks to be scheduled with a Timer. Simply subclass it and implement its run() method. Process provides a mechanism for creating and handling processes. Simply instantiate it and call its exec() method. Callback serves as a base class for defining anonymous callback for asynchronous calls. Lib/lang/Event.jag contains classes that you would subclass for the purpose of application-level event handling: 500 Event serves as the base class of all application events. JavaGram Agile Development EventInitiator serves as a mutual base class of any class that raises application events. To register an event listener, call the addListener() method. To raise an event, call the raiseEvent() method. EventListener serves as a mutual base class of any class that listens to application events. Simply subclass it and implement its consumeEvent() method. Lib/lang/Format.jag contains the Format class for formatting typed data. Lib/lang/Interfaces.jag contains the Comparable and Cloneable interfaces. Lib/lang/ML.jag contains classes for multi-lingual support. Library Reference 501 13.2 Lib/svr/ This directory contains scripts for deploying servers. To deploy a server, nominate one of these as the main script. AppServer.jag defines AppServer whose main() method boots a generic application server. ProxyServer.jag defines ProxyServer whose main() method boots a generic proxy server. 502 JavaGram Agile Development BlazeServer.jag defines BlazeServer whose main() method boots a BlazeDS application server for deployment into a servlet container such as Tomcat. 13.3 Lib/io/ This directory contains scripts for performing input/output operations. Cookie.jag provides access to browser cookies. DateParser.jag supports parsing of dates expressed in many different formats. Export.jag enables you to export application data in JAG or XML format. FTPClient.jag provides FTP client functionality for accessing an FTP server. Library Reference 503 IOUtil.jag provides useful IO utility methods. Matkup.jag provides methods for the transformation of markup data (e.g., HTML). Report.jag defines classes that provide template-driven report generation capability. Mf3270.jag defines a class of the same name for mainframe screen scraping. Soap.jag provides classes for creating and sending SOAP messages. XML.jag provides classes for processing XML data. 13.4 Lib/gui/ This directory contains a variety of scripts for defining and working with GUI elements. For ease of description, they’ve been divided into a number of logical categories below. GuiUtil Upgrade #w ait: boolean = true #mjvMajor: int #mjvMinor: int #remotePath: string #localPath: string #s: stream -app: <App> +check(mjvMajor:int, mjvMinor:int, remotePath:string, localPath:string, s:stream): void #proceed(): void #frameHandler(comp:native, event:symbol): void #getRebootCmd(jarPath:string): string #exit(code:int): void +TICK_STR: string = "\u2713" +DISABLE_COLOR: string = "0x999999" +DATE_FORMAT: string = "dd MMM yyyy" +DATE_TIME_FORMAT: string = "dd MMM yyyy, hh:mm:ss" #mainFrame: native +blankIcon: <Icon> +openFolderIcon: <Icon> +closedFolderIcon: <Icon> +addComp(container:native, comp:native, lay:symbol): void +setMainFrame(frame:native): void +getMainFrame(): native HtmlGenerator PickList PickListMgr #namedCombos: map<symbol,vector<native>> = map() #modified: boolean #itemEdited: boolean #lastSelIdx: int -dialog: <Dialog> +getList(name:symbol): PickList +registerList(name:symbol, combo:native): vector<string> +editList(name:symbol, visual:boolean): void +refreshLists(): void +show (pickList:PickList): void #fieldHandler(comp:native, event:symbol): void #listModel(comp:native, cmd:symbol, idx:int): vague #listHandler(comp:native, event:symbol): void #applyItemCmd(): void #cancelItemCmd(): void #new Cmd(): void #canDelete(): boolean #deleteCmd(): void #canMoveUp(): boolean #moveUpCmd(): void #canMoveDow n(): boolean #moveDow nCmd(): void #okPressed(): void #cancelPressed(): void #setModified(mod:boolean): void 504 pickList +generateCustom(): Document +generateAuto(path:string, screen:Screen, title:string): void +generateDirect(path:string, data:string, title:string): void -_generateAux(path:string, src:vague, title:string): void +textToHtml(text:string): string +htmlHeader(title:string): <text>string +htmlTrailer(): <text>string Screen InfoBar -w heelIdx: int = 0 -READY_GIF: string = "lib/gifs/Ready.gif" -LOCK_GIF: string = "lib/gifs/Lock.gif" -UNLOCK_GIF: string = "lib/gifs/Unlock.gif" -WHEEL_GIFS: vector<string> = [...] -view : <Panel> -timer: <Timer> HtmlScreen -urlPrefix: string -view : <Panel> +getView (): native +activate(): native +startTask(name:string): void +endTask(): void #formatInfo(): string +updateMemory(): void #gc(): void +setSecurity(secure:boolean): void #timerHandler(comp:native, event:symbol): void +HtmlScreen(urlPrefix:string) #anchorHandler(comp:native, url:string): void #urlKind(url:string): symbol #doHttpRef(url:string): void #doExprRef(refStr:string): void #doLocalRef(path:string): void #doFileRef(path:string, isLocal:boolean): void #getRefFiles(path:string, local:boolean): string +bind(content:Object): void +setFile(path:string, local:boolean): void JavaGram Agile Development GuiUtil.jag contains the GuiUtil class which defines a number of useful static members. The setMainFrame() and getMainFrame() methods are the most-frequently ones, providing a convenient way of registering and accessing an application’s main frame. Upgrade.jag contains the Upgrade class which implements an automatic method for upgrading JAG.jar according to an application-specified ‘minimum required version’. InfoBar.jag contains the singleton InfoBar class which implements a generic information bar for display at the bottom of an application. It displays progress (for downloads and remote calls), memory usage, and security information. PickListMgr.jag contains the singleton PickListMgr class for managing pick-lists (lists displayed for customizable combo boxes). Html.jag contains classes for generating and viewing HTML reports: HtmlGenerator serves as a mutual base class of any screen that requires HTML report generation capability. HtmlScreen provides a screen for viewing HTML content. You can use it ‘as is’ or subclass it and add features. Library Reference 505 EventInitiator EventListener ShowContentEvent +content: Object +Show ContentEvent(screen:Screen, content:Object) NavTree #lastSelNode: native #navChain: vector = vector() #navIndex: int = -1 #ignoreChain: boolean = false #selFirstTab: boolean = false #modNodes: vector = vector() #tree: <Tree> -saveBeforeExitAlert: <Alert.ync> +NavTree(app:GuiApp) +getTree(): native #onShowContent(stage:symbol): void +reload(): void #createNode(parent:native, title:string, icon:native, presentation:Screen, content:Object): vague +selectNode(node:symbol): void #selectParentNode(node:vague): void +getContent(node:vague): Object +setContent(node:vague, content:Object): void +findChildNode(parent:vague, w ithContent:Object): vague #getScreen(node:vague): Screen #treeHandler(comp:native, event:symbol): void #onDrillNode(node:native): void #setModified(): void #maintainModNodes(node:vague, modified:boolean): void +persistNodeCmd(maintain:boolean): boolean +persistAllNodesCmd(maintain:boolean): boolean #persistNode(node:vague): void +unlockModifiedNodes(nodes:vector): void +unlockNodeCmd(maintain:boolean): void +hideNodeCmd(maintain:boolean): boolean +hideNode(node:native, check:boolean, selectSibling:boolean): void +hideChildNode(parent:native, w ithContent:vague, selectSibling:boolean): void +hideChildren(node:native): boolean #hasModified(nodes:vector): boolean +deleteNodeCmd(maintain:boolean): boolean +reloadNodeCmd(maintain:boolean): boolean +goBackCmd(maintain:boolean): boolean +goForw ardCmd(maintain:boolean): boolean #updateNavChain(node:vague): void #removeFromNavChain(node:vague): void +show NodeContentCmd(maintain:boolean, syntax:symbol): boolean +consumeEvent(event:Event): boolean +canExit(): boolean Event ShowNoContentEvent GuiApp -app: native app +GuiApp(frame:native) +getNativeApp(): native +run(): void +exit(): void +beginLengthy(): void +endLengthy(): void +getUserId(): vague GuiApp.jag contains the GuiApp class which serves as the base class of all GUI applications. To implement a GUI application, subclass it and call its run() method. NavTree.jag contains classes that support navigation trees: NavTree serves as the base class of a navigation tree. It provides a rich set of methods for managing tree nodes, binding, and persistence. ShowContentEvent is used by NavTree to tell the rest of the application that a selected node’s ‘content’ should be displayed. ShowNoContentEvent is used by NavTree to tell the rest of the application that a selected node has no ‘content’. 506 JavaGram Agile Development parent EventInitiator Event Object content Screen content #content: Object #editable: boolean = true #autoApply: boolean = true #ignoreEdits: boolean = false #defFocusElem: native #modified: boolean = false #isBinding: boolean = false #Hot: <Plate> +Screen() +Screen(parent:Screen) +getView (): native +maintain(): void +clear(): void +bind(content:Object): void #bindView (content:Object): void +save(): void #saveView (): void +persist(): void +reflectDeletedObj(obj:Object): void #onModified(): boolean #onBind(stage:symbol): void #onSave(stage:symbol): void #hotEnable(): boolean #hotHandler(comp:native, event:symbol): void +toHtml(buffer:stream, indent:int): void #indentHtml(buffer:stream, indent:int): void ShowContentEvent ScreenModifiedEvent screen +WizardCard(w izard:Wizard, panel:native) +getView (): native +getName(): symbol +getBackName(): symbol +getNextName(): symbol +canFinish(): boolean +onShow (show :boolean): void +ScreenModifiedEvent(screen:Screen) HtmlScreen screen ScreenContainer #parts: vector<Screen> = vector() +ScreenContainer(parent:Screen, parts:vector<<Screen>) +setParts(parts:vector<<Screen>): void +addPart(part:Screen): void #bindView (content:Object): void #saveView (): void #onBind(stage:symbol): void #onSave(stage:symbol): void #tabsHandler(comp:native, event:symbol): void EventListener Wizard WizardCard #panel: native #name: symbol ShowNoContentEvent currCard firstCard +BACK_TITLE: string = "Back" +NEXT_TITLE: string = "Next" +FINISH_TITLE: string = "Finish" +CANCEL_TITLE: string = "Cancel" #cardMap: map<symbol,WizardCard> = map() #firstCard: WizardCard #currCard: WizardCard -w izard: <Dialog> +Wizard(title:string, w idth:int, height:int, modal:boolean) +getDialog(): native +addCard(card:WizardCard): void +show (b:boolean): void +show Card(card:WizardCard): void +show FirstCard(): void #maintainButtons(): void -backPressed(): void -nextPressed(): void -finishPressed(): void -cancelPressed(): void #onFinish(): void #onCancel(): void +consumeEvent(event:Event): boolean +onModified(): void Screen.jag contains classes for managing generic screens: Screen serves as the base class of any screen that requires binding and persistence. The ‘content’ of a screen is represented by an Object. Screens may be nested – each screen keeps track of its parent for the purpose of ‘propagating’ actions. ScreenModifiedEvent is used by Screen to notify the rest of the application that a screen’s ‘content’ has been modified. Library Reference 507 ScreenContainer.jag contains the ScreenContainer class which can be used to group a set of screens together to form a larger one. Wizard.jag contains classes for defining wizard style dialogs: Wizard sequences a set of WizardCard instances together to form a wizard, allowing the user to move from one card to the next/previous card, to enter data, or make selections. The cards are linked dynamically. WizardCard serves as the base class of any screen that’s to be used a wizard card. Table Screen #tableData: vector = [] #tableFormat: vector<map> = [] #lastSelRow s: vague #table: <Table> +Table(tableFormat:vector<<map>, itemScreen:Screen, parent:Screen) +getTable(): native +getTableData(): vector +setTableData(tableData:vector): void +getColFormatMap(row :int): map +isEmpty(): boolean +isSorted(): boolean +hasSelection(): boolean +countSelected(): int +selectAll(): void +getSelectedRow (): int +selectRow (row :int): void +getSelectedItem(): vague +getSelectedItems(): vector +appendRow (row Data:vague): void #tableModel(comp:native, cmd:symbol, row :int, col:int): vague #getCell(row :int, col:int): vague #formatCell(obj:vague, key:symbol): vague #updateCell(comp:native, item:vague, key:symbol, new Val:vague): void #validateCell(comp:native, item:vague, key:symbol): boolean #cellStyle(row :int, col:int): vague #cellIcon(row :int, col:int): vague #sort(col:int, ascend:boolean): void #tableHandler(comp:native, event:symbol): void #row ToObject(row :vague): Object #onSelectRow (): void #applyImplicit(): void +clear(): void +refresh(): void +detailsCmd(maintain:boolean): boolean #drillRow (bo:Object): Object +new Cmd(maintain:boolean): boolean #createRow (): vague +applyCmd(maintain:boolean): boolean +deleteCmd(maintain:boolean): boolean #deleteRow (idx:int): vague +cutCmd(maintain:boolean): boolean +copyCmd(maintain:boolean): boolean +pasteCmd(maintain:boolean): boolean +findMatching(prefix:string): int +expandRow (row :int, refresh:boolean): void +collapseRow (row :int, refresh:boolean): void TableButtons itemScreen +ALL_BUTNS: vector<symbol> = [details,new ,delete,jump] #buttons: vector<symbol> +view : <Panel> +TableButtons(table:Table, buttons:vector<<symbol>) +TableButtons(buttons:vector<<symbol>) +setTable(table:Table): void +clearJump(): void #jumpHandler(comp:native, event:symbol): void EmbedTickTable #tickKey: symbol #tickCol: int -tickEditor: <Option.tick> +EmbedTickTable(tableFormat:vector<<map>, tickKey:symbol, parent:Screen) #reformat(formats:vector<<map>, tickKey:symbol): vector<map> #tableHandler(comp:native, event:symbol): void #enabled(row :int): boolean #toggle(row :int): boolean #cellStyle(row :int, col:int): vague +doAll(action:symbol): void #_doAll(action:symbol): void +countTicked(): int +getTickedRow s(): vector<int> +getTickedData(key:symbol): vector TickTable #ticks: vector<boolean> = vector() +TickTable(tableFormat:vector<<map>, tickKey:symbol, parent:Screen) #getCell(row :int, col:int): vague -syncTickVec(): void #toggle(row :int): boolean #_doAll(action:symbol): void +countTicked(): int +getTickedRow s(): vector<int> +getTickedData(key:symbol): vector Table.jag contains classes for defining and working with tables: Table serves as the base class of all tables that require binding. It provides methods for drilling into rows, adding new rows, and clipboard operations on rows. TableButtons provides a set of standard table button for drilling, adding, and deleting rows. The action handlers for these buttons are linked to the corresponding table methods. 508 JavaGram Agile Development TickTable.jag contains classes that support ‘tickable’ tables (i.e., a table where a column is nominated to contain tick boxes): EmbedTickTable serves as the base class for a tickable table where the tick data is embedded within the table data. If table format has a column that specifies the tick key then it’s used. Otherwise, one is created in position 0. If each row is an object, then the object must have a tick key as a field. Otherwise, toggling the tick is not allowed. If each row is a map, then the tick key is added dynamically when needed. TickTable serves as the base class for a tickable table where the tick data is kept separate from the table data. The tick key can be null, in which case the table format should not define the tick column. Library Reference 509 SearchCriteriaPanel #search: SearchScreen #criteria: map = map() #Hot: <Plate> -view : <Panel> +SearchCriteriaPanel(search:SearchScreen, criteriaPanel:native) +getCriteria(): map +save(): void +clear(): void #hotHandler(comp:native, event:symbol): void criteriaPanel Screen search SearchScreen -TICK_DATA: vector<string> = ["","Yes","No"] -view : <Panel> +SearchScreen(criteria:SearchCriteriaPanel, result:SearchResultScreen, parent:Screen) +findCmd(): void +canFind(): boolean #find(criteria:map): vector +clearCmd(): void +canClear(): boolean +reflectDeletedObj(obj:Object): void search resultScreen Table table TableButtons tableButns SearchResultScreen #search: SearchScreen #tableButns: TableButtons #DEFAULT_TITLE: string = "Search Result" -view : <Panel> +SearchResultScreen(search:SearchScreen, tableFormat:vector<<map>, tableButns:TableButtons) +setResult(result:vector): void +clear(): void +reflectDeletedObj(obj:vague): void #setTitle(title:string): void TableAndPieSearchResultScreen #PIE_COLORS: vector<string> = [...] #pieKeys: vector<symbol> #pieVec: vector<map> = vector() -view : <Pane.tabbed> +TableAndPieSearchResultScreen(search:SearchScreen, tableFormat:vector<<map>, tableButns:TableButtons, +setResult(result:vector): void +clear(): void #setTitle(title:string): void #pieComboModel(combo:native, cmd:symbol, idx:int): vague #pieKeyToColIdx(key:symbol): int #pieComboHandler(comp:native, event:symbol): void +rebuild(): void #buildPieData(): void #cycleColor(idx:int): string #pieModel(combo:native, cmd:symbol, idx:int): vague Search.jag contains classes that support customizable search user interface behavior: SearchCriteriaPanel serves as the base class of a search criteria panel, with find and clear buttons. 510 JavaGram Agile Development SearchScreen serves as the abstract base class of a search screen. Subclass it and implement its find() method. The constructor expects a SearchCriteriaPanel and a SearchResultScreen. SearchResultScreen serves as the base class of a screen that displays search results. This is typically subclassed anonymously when invoking the SearchScreen constructor. TableAndPieSearchResultScreen is a variant of SearchResultScreen that displays the search result as a tabbed pane, where the first tab displays the result as a table and the second tab displays it as a pie chart. 13.5 Lib/bom/ This directory contains scripts that support Business Objects (BOs). Library Reference 511 Object -_tableDef: map<symbol,map> = map() -_persisted: boolean = false ThreadLocal extendOn PickListDetails -id: int -editable: boolean -name: string -description: string -items: vector<string> #Object() #Object(dbTableDef:map) +defineTable(objKlass:vague, dbTableDef:map): void +getId(): vague +title(): string +persisted(): boolean +isExtendOn(): boolean +isExtendOn(klass:symbol): boolean +setExtendOn(value:boolean): void +setExtendOn(klass:symbol, value:boolean): void +findOne(klass:vague, field:symbol, value:vague): Object +find(klass:vague, field:symbol, value:vague): vector<Object> +find(klass:vague, criteria:map): vector<Object> +onModified(): void +persist(): void #handlePrimaryKey(): boolean #getViaFilePath(dbColumn:symbol, field:symbol, row Num:int): string +delete(): void #_delete(): void +refresh(): void +isLockable(): boolean #isLockable(tableDef:map): boolean #getTableDef(): map #getTableDef(klass:symbol): map #getDbTag(): symbol #getDbTag(tableDef:map): symbol #getDbTable(): symbol #getDbTable(tableDef:map): symbol +dropTable(klass:vague, db:symbol): void PickLists PickList +createTable(): <text.sql.update>int +PickList(details:PickListDetails) +title(): string +getId(): vague +countItems(): int +getItem(idx:int): string +setItem(idx:int, item:string): void +sw apItems(idx1:int, idx2:int): void +appendItem(item:string): void +deleteItem(idx:int): void -findById(id:vague): PickList -findByName(name:string): PickList -findAll(): vector<PickList> Lockable +isLockable(): boolean +lock(tag:vague): void +unlock(tag:vague): void Document #plVec: vector<PickList> #plMap: map<string,PickList> +PickLists() +getByName(name:string): PickList +getAll(): vector<PickList> +clearCache(): void UserLockableObject #path: string #name: string #isLocal: boolean #pathMap: map<string,Document> = map() +Document(path:string, isLocal:boolean) +title(): string +getId(): vague +delete(): void +getByPath(path:string): Document +view (): void -_lockedByUser: vague = null #UserLockableObject() +lock(byUserId:vague): void #_lock(byUserId:vague): void +unlock(byUserId:vague): void #_unlock(byUserId:vague): void +lockedBy(): vague +lockedBy(probable:boolean): vague #_lockedBy(): vague Exception CantLockException +CantLockException(reason:string) OutOfDateException CantUnlockException +CantUnlockException(reason:string) Object.jag defines the abstract mutual Object class that serves as the base class of all BOs. It supports persistence and database search functionality via the bom pseudo class. Lockable.jag defines classes for implementing locking functionality for BOs: Lockable is the abstract mutual base class (interface) for any BO that needs to be lockable. CantLockException is raised when a BO can’t be locked. CantUnlockException is raised when a BO can’t be unlocked. OutOfDateException is raised when a lockable BO is found to be older than what’s in the database. 512 JavaGram Agile Development UserLockableObject.jag defines the UserLockableObject mutual class which is a variant of Object that has built-in support for locking by users. Document.jag defines the Document class which represents disk-based documents. PickList.jag contains classes for managing pick lists: PickListDetails captures pick list data. PickList defines a pick list as a persist-able object. PickLists represents all pick lists in an application. 13.6 Lib/sql/ SqlUtil.jag defines the SqlUtil class which provides helper methods for SQL programming. Exception TooManyRowsException -TooManyRow sException(limit:int) SqlUtil +queryAsVector(connOrStmt:vague, q:string, klass:vague): vector +prepStmtAsVector(prepStmt:native, klass:vague): vector +resultSetAsVector(resultSet:native, klass:vague): vector -log(msg:string): void -w arn(row s:int, w arn:int, complete:boolean): void Library Reference 513 14 JADE – JavaGram IDE JADE is the Integrated Development Environment (IDE) for JavaGram, making JavaGram programming much easier and more productive than resorting to plain text editors and command lines. JADE allows you to organize your work as a project, define project components, visually edit color-coded scripts, define run configurations, debug your code, and a variety of other tasks. JADE automatically builds your project in the background as you develop your code, so that you always have an up-to-date view. 14.1 Overview The visual structure of JADE is shown below (with the Quest sample project loaded). A menu bar and a toolbar at the top of the frame provide access to JADE’s commands. The rest of the frame is split into three panes: The top left pane displays the project navigation tree. The root of this tree points to the project root directory. The top right pane displays files that are currently open for editing. 514 JavaGram Agile Development The bottom pane (task pane) displays various tabs for visual feedback, as well as the run/debug tabs. JADE’s frame can be viewed in one of three configurations: Press the Full Project button in the main toolbar or choose the same from the View menu to view all three panes. This is the default view. Press the Project and Editor Only button in the main toolbar or choose the same from the View menu to hide the task pane. Press the Editor Only button in the main toolbar or choose the same from the View menu to maximize the editor’s real estate. The status bar at the very bottom of JADE’s frame provides real-time feedback. On its left, you see feedback from JADE’s incremental build process. For example, when you open a project, you’ll notice that its parsing and analysis progress is shown there. The next segment shows the read/write status of the current file and its line count. The next segment shows the line and column number of the cursor within the editor. The last segment shows the amount of memory used versus allocated, and a trashcan button which, when pressed, forces garbage collection (you don’t really need to do this, as garbage collection is performed automatically). 14.2 Projects JADE requires that you organize your work in projects. A project’s definition is stored in a file with ‘.jade’ extension (e.g., Quest.jade). Only one project can be loaded at a time. The name of this project appears in JADE’s frame title, followed by the full path of the front-most script open in the editor. 14.2.1 Project Operations To create a new project, select File|New, or press the Create New File button in the toolbar. The following wizard dialog appears. Select JADE Project from the tree and press the Next button. JADE – JavaGram IDE 515 To specify the project folder, press the Project Folder button and browse to the desired directory, or simply type the path in the field next to it. Once you’ve entered a name in the Project Name field, the Finish button is enabled. Press this button and your project will be created, ready for use. To load an existing project, select File|Open Project, browse to the desired project file and open it. To save your changes to a project, select File|Save Project. Alternatively, you can press the Save All button in the main toolbar, which causes the project as well as any modified files open in the editor to be saved. To close a project, click the cross in project tree tab, or choose File|Close Project. If the project has any unsaved changes and/or any of its open files have unsaved changes, you’ll be asked as to whether you want to save the changes. For convenience, JADE keeps a list of the last few opened projects and displays them in the File|Recent Projects submenu. You can quickly open a project by selecting from this submenu. When you exit JADE, if you have a project open, it will be remembered and automatically reopened the next time you run JADE. 14.2.2 Project Tree Every project has a scope that determines the files that comprise the project. JADE automatically builds a project according to its scope. The project tree displays a directory structure starting with the project folder. By definition, the project scope is a subset of the project tree. Folders and files in this tree are color-coded to show if they’re part of the scope: An explicitly included node appears in bold blue font, and its implicitly-included descendents appear in bold black font. A neutral node appears in plain grey font. Neutral nodes are outside the project scope. An explicitly excluded node appears in plain red font. 516 JavaGram Agile Development When you create a new project, the whole project is initially in neutral state. You must then include nodes that are part of the project scope, and exclude any sub-nodes that are outside the project scope. To change the scope of your project, select the nodes that you want in/out of the scope and press one of these buttons (in the toolbar directly above the project tree): Press the Include in Project (+) button (or choose the same from the Project menu, or press Control+I) to include the nodes in the project scope. Press the Inherit Parent’s Inclusion (=) button (or choose the same from the Project menu, or press Control+T) to make the nodes conform to their parent. You only need to do this to nodes that were previously forced to deviate from their parents’ settings and now need to be restored. Press the Exclude from Project (-) button (or choose the same from the Project menu, or press Control+E) to exclude the nodes from the project scope. To reset all your include/exclude commands, select Project|Reset All Include/Exclude. Any of these changes causes JavaGram to do an incremental build to validate the scope. For example, if you exclude a script that’s loaded by other included scripts then the latter will show build errors due to the missing dependency. Other operations you can perform on project tree nodes are as follows. To open a script for editing, double-click the script’s node in the tree. A new tab is added to the editing pane, displaying the script’s content ready for editing. If the script is already open, its tab is simply brought to the front. To create a new folder, select the parent folder and press the Create Folder button or choose the same from the Project menu. The following dialog appears. Type in the folder name and press the OK button to create the folder. The created folder is added to the project tree and selected. To delete tree nodes, select the nodes you want to delete and press the Delete File/Folder button or choose the same from the Project menu. An alert box will appear, asking you to confirm the delete. JADE – JavaGram IDE 517 To refresh the project tree from disk storage, press the Refresh and Re-analyze button or choose the same from the Project menu. Any changes made to the project directory structure outside JADE are reflected in the tree. Scripts that need to be re-analyzed as a result are re-analyzed. Alternatively, you can go a step further and choose Project|Refresh and Reparse All. This effectively performs a clean build and is useful if you’re experiencing spurious errors being reported. To navigate to the currently-edited file, press the Show File in Tree button. The node for the front-most file in the editor pane is selected in the project tree. 14.2.3 Outline Tree The outline tab (next to the project tab) displays the class structure for the front-most script in the editor pane. Use this tab to get a summary view of the structure of a script and to navigate to its individual parts. The outline is organized as a tree, where the root denotes the script, under which we have a class node for each of the classes in the script, and under each class we have a list of the class members. The icon of each node identifies its type: Classes appear as combs. Methods appear as circles. Fields appear as rectangles. GUI members appear as hexagons bearing the letter ‘G’. Text members appear as hexagons bearing the letter ‘T’. The icons are color-coded to represent their visibility: 518 JavaGram Agile Development Blue means domain visibility. Green means public visibility. Yellow means protected visibility. Red means private visibility. You can browse to the definition of a node by simply clicking on it – the first line of its definition is selected in the editor. 14.2.4 Project Properties To view the properties of the current project, press the Project Properties button or choose the same from the Project menu. The following dialog is displayed. The project name (e.g., Quest.jade) is displayed as read-only, because it represents the project file. A project keeps track of two root folders: The Project Tree Root determines the root folder displayed in the project navigation tree. The JavaGram Root sets the root path of a running program (via the –root option). Both these are usually set to the same folder, but they can be different. The Build Target field specifies a target folder where the output of an explicit build is stored (as initiated by Project|Build Project). To change any of these, press the relevant button, navigate to the desired folder and select it. Alternatively, you can enter the path directly into the field. Non-existing paths are rejected. JADE – JavaGram IDE 519 The Run Configurations tab allows you to define any number of run configurations for your project. You can bring up this tab directly (without going through project properties) by pressing the Runtime Configurations button in the main toolbar or choosing the same from the Project menu. Every new project contains a <Default> configuration to get you started. To define a new run configuration, press the New button. If the new configuration is similar to an existing one, it’s easier to select it, press the Copy button to make a copy of it, and then make the necessary changes to it. To edit an existing configuration, select it and press the Edit button. To delete it, press the Delete button. You can re-order the list by selecting an entry and pressing the Move Up or Move Down button to move it up/down the list. Any change made to the list of run configurations is reflected in the combo box in the main toolbar. To run a program, you select the desired configuration from this combo and press the Run or Debug button next to it. For convenience, the last modified configuration is automatically selected in this combo, ready for execution. When you press New, Copy, or Edit, the following dialog appears. 520 JavaGram Agile Development Enter the desired configuration name into the first field, and select its type from the combo box. The latter allows you to specify the run mode by choosing one of these options: Standalone non-GUI App Standalone GUI App Client non-GUI App Client GUI App Client Browser App Server Choosing the correct mode is very important. For example, choosing ‘Client non-GUI App’ for an application that has a GUI may cause it to misbehave. The selected mode affects the rest of the dialog, so in explaining the remaining fields we’ll indicate whether it’s relevant to a specific mode. The Main Script field specifies the initial script for a program. This only applies to standalone and server modes. Press the button, navigate to the desired script, and select it. The Config File field specifies the program configuration file. This only applies to standalone (optional) and server modes. Press the button, navigate to the desired configuration file, and select it. The Log File field optionally specifies a log file. When specified, all output sent to standard output and standard error streams will go to this file instead. To use an existing JADE – JavaGram IDE 521 file, press the button, navigate to the desired log file and select it. To specify a new file, type its path into the field. The Client Root file specifies the root path of a client program, so it only applies to the client mode. Press the button, navigate to the desired folder, and select it. In a standard installation, use C:/JavaGram/client/ as the root for native clients, and C:/JavaGram/flash/ as the root for browser-based clients. The Client Boot Script field specifies the initial script for a client, so it only applies to the client mode. Because this script refers to a server-side file, it must be specified as a relative path (relative to the server’s JavaGram root path). The Client Boot Partition field applies to browser clients only and specifies the initial partition for the application. The Transport field applies to browser clients only and specifies the transport format for client-server communication. Note that if you use http or https transport, you must deploy your server in a servlet container (see Chapter 7). The SSL Keystore field specifies the Java keystore file for a client, so it only applies to the client mode. In a vanilla installation, you would set this to C:/JagClient/ssl/JAG.jks. The Client/Server Host:Port field specifies the host and port number for a client or server, so it only applies to client and server modes. The host can be specified using its domain name or IP address. The host and port must be separated by a colon (e.g., localhost:443). The Proxy Server Host:Port field applies to browser clients only and optionally specifies a HTTP proxy server for the client to tunnel through. The Application Stage optionally specifies the development stage of the application. Possible values are: blank, dev, test, or prod. The Timezone field optionally specifies the desired time zone for the application (e.g., "America/Los_Angeles"). Leaving it blank assumes your default installation timezone. When ticked, the Verbose mode checkbox causes the program to run in verbose mode, producing additional output (e.g., all client-server communication). The Suppress warnings checkbox inhibits all warnings. The Clear cache checkbox causes the program’s cache to be cleared before running (applies to client and server modes). The Keep Std Out checkbox causes output redirected to a log file to also be sent to the standard output stream. The Override Java Pars field can be used to override the Java parameters specified in the Tools|Preferences dialog (described later in this chapter). When not ticked, the field 522 JavaGram Agile Development shows the current setting as read-only. To override it, tick the checkbox and enter the new setting into the field. The new setting will only apply to this run configuration. The Debugger Host:Port field specifies the internal host and port number for the JADE debugger. You should leave this to its default setting (localhost:9900) unless the port is conflicting with something else on your computer. Once you’ve finished your changes, press OK to confirm them, or Cancel to dismiss them. This takes you back to the project properties dialog. 14.2.5 Project Statistics You can quickly generate statistics on your project by choosing Project|Statistics. Within the dialog that appears, you have the option of limiting the statistics to the files in the project scope, or scanning all files under the project root folder. Press the Scan button. The result is displayed in the dialog shortly after, with a tally at the bottom of the dialog. 14.2.6 Building a Project As stated earlier, JavaGram automatically and incrementally builds your project as you work. The purpose of the incremental build is to report any errors immediately, so that you can take action. No binary files are generated in this process – the build result is kept in memory to aid performance. The Project|Build Project command offers you a different style of build, where compiled binary files are generated. Use this command for situations where you want to release an application in binary (rather than source) format. JADE – JavaGram IDE 523 The build commences as soon as you select this command. The Build Output tab appears in the task pane and displays the build log. To cancel a lengthy build, press the Stop Process button. To clear the output log, press the Clear Process Output button. Before initiating a build, make sure that the target folder is correctly specified in the project properties dialog. All compiled (.jax) files are written to this folder, following the same folder hierarchy as the source files. 14.3 Editor Each open file is displayed as a tab in the editor pane. JavaGram source ( .jag) files are color-coded. All other text files are displayed without color coding. The visual appearance of the editor is shown below. We’ve intentionally introduced an error, a warning, and a breakpoint in the front-most tab to illustrate how they’re highlighted. The editor view is divided into these regions: The left-most strip (line gutter) displays the line numbers for the file. If a line contains an error or warning, its line number appears, respectively, in red or amber. The grey strip (breakpoint gutter) to the right of the line gutter is reserved for displaying breakpoints. Each breakpoint is displayed as a small ‘hand’ icon. The editor’s text content is displayed in the middle, including scrollbars. This is where you perform most of your editing operations using the mouse and keyboard. The right-most grey strip (ruler) displays the relative position of errors/warnings in the file as red/amber boxes. The small square at the top of the ruler goes green when the file has no errors/warnings. Otherwise it goes red or amber. 524 JavaGram Agile Development Identifiers and symbols in a file are color-coded according to a set of color-coding rules, which can be customized by the user (see Tools|Preferences|Syntax Coloring). A code fragment that is in error is displayed in bold red and underlined with a jagged line. A code fragment that’s causing a warning is highlighted similarly but in amber. The background of the cursor line (the line where the blinking cursor appears) is displayed in light grey. 14.3.1 File Operations To create a new script, press the New button in the main toolbar or choose File|New or press Control+N. The Create New wizard dialog is displayed (see Section 14.2.1). Select JavaGram Script from the wizard tree and press the Next button. The wizard changes to the following. JADE – JavaGram IDE 525 Enter the script name (the class name defaults to the same, but you can change it) and press Finish. The script is created, added to the project tree, and opened in the editor (if the Open checkbox is also ticked). To open a file, locate it in the project tree and double-click it. Alternatively, press the Open File button in the main toolbar or select the same from the File menu or press Control+O, navigate to the desired file and select it. The file is loaded onto the editor. To save changes to a file, press the Save button in the main toolbar or select the same from the File menu or press Control+S. Alternatively, you can press the Save All button or select the same from the File menu to save all modified open files (as well as any changes to the project). To save a file under a different name, select File|Save As and specify its new name in the displayed dialog. To close a file, click in the cross icon of the file’s tab. Alternatively, make sure that it’s the front-most tab and press the Close File button in the main toolbar or select the same from the File menu or press Control+Q. If the file has any unsaved changes, you’ll be asked as to whether you want to save the changes. To close all open files, choose File|Close All Files. If any of the open files have any unsaved changes, you’ll be asked as to whether you want to save the changes. For convenience, JADE keeps a list of the last few opened files and displays them in the File|Recent Files submenu. You can quickly open a file by selecting it from this submenu. To print a file, make sure that it’s the front-most tab in the editor and select File|Print or press Control+P. The following print dialog (or something similar) is displayed. 526 JavaGram Agile Development Make your selections and press the Print button. You can define your page setup either in this dialog or using the File|Page Setup dialog. 14.3.2 Editing Operations This section describes the editing operations that you can perform on an open file. It assumes that the file is a JavaGram (.jag) script. You can also view a list of the supported editing shortcuts by selecting Help|Shortcuts. To view the details of an error/warning, place the cursor on it – the details will appear in a tooltip. You can do this in the line gutter, in the editor content, or in the ruler. To select a text segment, click at its beginning and hold the mouse button down, drag to its end and release the mouse button. For a selection that spans across many lines, it may be easier to click at the beginning, hold the shift key down, and then click at the end. To select a token, double-click it. To select an entire line, click its line number in the line gutter. To copy text to the clipboard, select it and choose Edit|Copy or press Control+C. To move text to the clipboard, select it and choose Edit|Cut or press Control+X. To insert text, click where you want to insert and start typing or, if you have the text already on the clipboard, paste it by choosing Edit|Paste or pressing Control+V. JADE – JavaGram IDE 527 To replace text, select it and start typing or, if you have the text already on the clipboard, paste it by choosing Edit|Paste or pressing Control+V. To delete text, select it and press the backspace or delete key. Alternatively, click at the beginning of the text to be deleted and repeatedly press the delete key, or click at its end and repeatedly press the backspace key. To toggle a line’s breakpoint, click in the breakpoint gutter next to it. Breakpoints can be set only for lines that are capable of having breakpoints. To toggle the breakpoint of the caret line, you can also choose Run|Toggle Breakpoint or press Control+B. To clear all breakpoints for the project, choose Run|Clear Bkreapoints or press Control+D. To find out the type of an identifier, hold the Control key down and place the mouse pointer over the identifier. If the identifier has a viewable type, it’s shown as a tooltip (e.g., class summary, variable/field type, method signature). To navigate to the definition of an identifier, hold the Control key down and place the mouse pointer over the identifier. The identifier is highlighted (underlined) if it’s navigate-able. Click and JADE will take you to its definition, which may be in the same file or another file. To indent one or more lines, select them (first and last lines may be partially selected) and press the tab key. With each tab press, the lines are moved to the right by one tab position. To left-indent the lines, press Shift+Tab instead. To select the next word (or place the caret after the next close bracket), choose Edit|Select Next or press Control+Tab. To select the previous word (or place the caret after the previous open bracket), choose Edit|Select Previous or press Control+Shift+Tab. To comment or uncomment one or more lines, select them (or if it’s just one line, place the cursor anywhere on the line) and choose Edit|Toggle Comments or press Control+/. To move the caret forward one token at a time, press Control+►. To move back one token, press Control+◄. To view code insight for the current context, choose Tools|Code Insight or press Control+Period. To go to the start of the caret line, press the Home key. To go to the start of the file, press Control+Home. 528 JavaGram Agile Development To go to the end of the caret line, press the End key. To go to the end of the file, press Control+End. To select the entire file, choose Edit|Select All or press Control+A. To go to a specific line of the file, choose Edit|Go to Line or press Control+G. Enter the desired line number into the dialog that’s displayed and press OK. To move the caret to a matching bracket, choose Edit|Go to Matching Bracket or press Control+M. To select from the caret to a matching bracket (assuming that the caret is just after a bracket), choose Edit|Select to Matching Bracket or press Control+Shift+M. To undo the last editing operation, choose Edit|Undo or press Control+Z. To redo an undo, choose Edit|Redo or press Control+Y. Unlimited undo and redo operations are supported. If you undo/redo up to a save point, the file’s ‘edited’ status (a * next to its name in the editor tab) disappears. The editor also supports a number of context sensitive behaviors (these can be switched on/off via Tools|Preferences|Typing): When you place the caret after a bracket (any type of bracket), this bracket and its matching pair are highlighted. When you place the caret on or after a markup tag, this tag and its matching pair are highlighted. When you type an opening bracket (any type of bracket), its closing bracket is automatically inserted. If you type the closing bracket anyway, it will type over the auto-inserted one. An auto-inserted closing brace is placed where you’re likely to want it, having taken block indentation into account. When you type a (single or double) quote character, its closing quote is automatically inserted. If you type the closing quote anyway, it will type over the auto-inserted one. When you press enter to open a new line, the caret on the new line is indented to where you’re likely to want to start typing. 14.3.3 Code Insight Code insight is a popup that’s displayed automatically as you type your code, offering you code completion suggestions. It’s activated by the following triggers (which can be switched on/off via the Tools|Preferences|Typing): Typing the opening < character of a markup tag (e.g., <Panel>). Code insight will display all possible tags that can be inserted at the caret point. JADE – JavaGram IDE 529 Typing the first character of an element’s property (e.g., border property of a <Panel>). Code insight will display all permissible properties for the element. Typing the = character after an element’s property name whose values form an enumeration (e.g., lay property of a component). Code insight will display the enumeration values. Typing the period (.) character after a class name (including pseudo classes). Code insight will display all public static members of the class. Typing the period (.) character after an object’s identifier. Code insight will display all members of the object’s class that are visible in the current context. You can also explicitly activate code insight by choosing Tools:Code Insight or pressing Control+Period. This method is useful when you don’t want to cause any change to the file. Here is an example of a code insight popup. When the popup is visible, you can do any of the following: Narrow your choices by typing the first few characters of the member you’re after. The list is filtered and only members that match the typed prefix are displayed. Press the Up/Down arrow key to move up/down the list one row at a time. Press the Enter key to select the current row, or double-click the desired member. The code for that member is inserted into the file at the caret position, replacing any prefix that you might have typed in the process. Press the Escape key or click outside the popup to dismiss it. 14.3.4 Reformatting a File JADE has a built-in code formatter that enforces a coding style similar to Kernighan and Ritchie. You can instantly reformat a file by choosing Tools|Reformat File or pressing Control+R, provided your file has no syntax errors. This operation is fully undoable. 530 JavaGram Agile Development 14.3.5 Viewing a Script in HTML If you want to copy code from the editor and paste it to another application, you have three choices: Use the Copy button in the toolbar or choose the same from the Edit menu. The selected code will be copied as plain text (none of the syntax coloring or bold/italic styling of the code will be preserved). Choose the View|View as Color HTML command, which will display the front-most code tab in a separate dialog, rendered in HTML that’s formatted to exactly match the coloring and styling you see in the editor tab. You can then copy from this dialog and paste it into your target application. Choose the View|View as Plain HTML command. This has the same effect as the second option, except that the HTML will be in black and white, but will including bold/italic styling. The code snippets in this book were included using this approach. Here is an example of what the second option produces. You can also right-click on this dialog and choose the Save As command to save the HTML view in a file. This is useful when you want to publish a script on a web page. 14.4 Searching and Refactoring 14.4.1 Searching a File To search for a string in a file, press the Find button in the main toolbar or choose the same from the Search menu. The following dialog is displayed. JADE – JavaGram IDE 531 Type in the search string into the Find field and press the Find button (if you have a string selected in the editor before you bring up this dialog, it will automatically appear in the Find field). The editor will scroll to the next instance of the search string (starting from the caret position) and highlight it. The Find/Replace dialog remains visible after a search so that you can do more searches. When ticked, the Case Sensitive checkbox forces case-sensitive matching of the search string. Similarly, the Whole Word checkbox requires the search string to match a whole word. To find the next instance of the search string, press Find again. The search wraps around when it reaches the end of the file. To replace a string with another string, type the latter into the Replace field. Press Find until you get to the instance you want to replace, and press Replace. Alternatively, press Replace and Find to do a replace followed by a find. To replace all instances of a string in a file, enter the search and replace strings and press Replace All. When the dialog is dismissed, the search and replace strings are remembered, so that you can apply them without the dialog: To find the next match, choose Search|Find Next or press the F3 key. To replace the current match and find the next one, choose Search|Replace and Find Next or press the F4 key. 14.4.2 Searching a Project You can do find and replace across multiple files or even an entire project. First, nominate your search scope by selecting a folder in the project tree, or select the project node for a full project search. Then press the Find/Replace in Folder button in the main toolbar or choose the same from the Search menu. A quicker way of doing both steps is to right-click on a node in the project tree and select Find from the popup menu. The following dialog is displayed (assuming that the search scope is the quest folder in the sample application). 532 JavaGram Agile Development When ticked, the Limit search checkbox ignores files that are outside the project scope. The other fields of this dialog are similar to the Find/Replace dialog for single files. However, rather than highlighting the search/replace result in the editor, it’s shown in the Search/Refactor Results tab of JADE’s task pane. To navigate to each search/replace result, simply click on its node in the result tree. Because Replace All is not undoable, the user is prompted with the number of replacements to be performed and asked to confirm the operation. 14.4.3 Refactoring When you right-click on a project tree node, a popup menu is displayed that offers you the following refactoring options: Rename allows you to rename a folder or file name. Move allows you to move a folder or file name to a different location – the affected domain names are updated accordingly. Duplicate allows you to duplicate a file to a nominated target folder, changing its domain name accordingly. Find displays the Find/Replace in Folder dialog explained earlier. Project References applies only to files and displays instances where the file is loaded by other files. 14.5 Running Before running an application, make sure that you have a suitable run configuration defined for it (see Section 14.2.4). To run an application, select its desired configuration from Run Configurations combo in the main toolbar and press the Run button next to it or choose the same from the Run JADE – JavaGram IDE 533 menu or press F9. A new tab is added to JADE’s task pane, bearing the run configuration’s name. The application is run as a separate process, whose output is redirected to this tab. The first line that appears in the process output tab is the actual command line JADE has used to run it. JADE builds this command line based on information defined in Tools|Preferences, project properties, and the run configuration. To stop the application, press the Stop Process button. To re-run the process, press the Restart Process button. To clear the process output, press the Clear Process Output button. When a process is restarted, its output is automatically cleared from the tab. You can run multiple processes at the same time by repeating the above sequence for running a process. Additional tabs will appear to represent them. For example, to test a client-server application, you can run a server and a client in this way. As a convenience, if you stop a running process and run the same configuration again from the toolbar or the Run menu, the existing run tab will be reused if it’s the front-most tab. To remove a run tab, press the cross icon next to its tab name. If the application is still running, it will be terminated. 14.6 Debugging The steps for running an application in debug mode are the same as a normal run, except that instead of pressing the Run button, you press the Debug button or choose the same from the Run menu or press Shift+F9. The appearance of a debug run tab is shown below. For this example, we’ve setup a breakpoint, and the picture shows the debugger stopping at the breakpoint line. 534 JavaGram Agile Development The debug tab has three sub-tabs. The console sub-tab displays the process output (as in a normal run). The debug information sub-tab displays the runtime stacks for the process threads, as well as a Variables and an Expressions tab. Each node in the stack tree represents a method call. If you click on a node, the editor will navigate to the location of that call. At the same time, the Variables and Expressions tabs are updated to show data relevant to the selected stack frame. JADE – JavaGram IDE 535 The Variables ‘watch’ tab displays a table of variables that are visible at the currentlyselected stack frame. If you click on a variable in this table, its value will be displayed in the adjacent panel. When the As Hierarchy checkbox is ticked, this value is displayed as an expandable tree. Otherwise, it’s displayed as a literal in clear text. The Expressions ‘watch’ tab is similar, except that instead of variables, you maintain a list of expressions here. These expressions are evaluated in the context of the currentlyselected stack frame. If an expression can’t be evaluated, instead of a value, an error is displayed. To add a new expression to the list, press the Create Watch Item button, type the expression in the Expr field, and press the Enter key. There are also buttons for applying changes to an expression, deleting it, clearing it, and reordering the list. A useful shortcut for adding an expression to the list is to select the expression in the editor and choose Run|Add Selection to Watch or press Control+W. The toolbar at the extreme left of the debug tab allows you to direct the flow of control. The first three buttons behave as for a normal run. The remaining buttons are: The Pause Process button (disabled in the above picture) is enabled when the process is running and hasn’t stopped at a breakpoint. This button is useful when your process has become unresponsive. For example, if the process is caught in an infinite loop, pressing this button will cause it to pause inside the loop, thus pinpointing the loop that’s misbehaving. The Continue button is enabled when the debugger has stopped at a breakpoint. Pressing it will cause the debugger to continue from that point. The Step Into button causes the debugger to ‘step into’ the breakpoint line. If the line is a method call then the debugger will progress execution to the beginning of the method body and wait there. The Step Over button causes the debugger to execute the breakpoint line and wait on the next line. The Step Out Of button causes the debugger to complete the execution of the current method, return to the calling line, and wait there. The Run to Caret Line causes the debugger to continue execution until it reaches the caret line. 536 JavaGram Agile Development The loaded files sub-tab shows the list of scripts so far loaded by the running process (and their line count). This is for information only. You can navigate to each script by simply clicking on its node. Finally, to get a central view of all the breakpoints defined for your project, choose View|Show Breakpoints tab. A tab similar to the following appears in the task pane of JADE. It lists every breakpoint. To navigate to a breakpoint, simply click on its node. 14.7 Tools 14.7.1 Import Java Classes JADE allows you to import Java classes and wrap them as JavaGram classes. This serves as a quick and convenient way of accessing third-party Java functionality. The Java classes may be provided as either a directory of class files or a JAR file. JADE processes the Java classes and, for each class, displays its public members, allowing you to choose the ones that you want to import. A JavaGram script is generated for each imported class. To import one or more Java classes, first select the target folder in your project navigation tree, and then select Tools|Import Java Classes. The following dialog is displayed. JADE – JavaGram IDE 537 If your Java classes reside in a directory, press the Class Dir button, navigate to the directory and select it. If your Java classes reside in a JAR file, press JAR File, navigate to the JAR file and select it. Once selected, the import source appears in the text field to the left of the Scan button. Press Scan to process the Java classes. After a short delay, the list box to the left of the dialog is populated with the available classes, and the Filter combo above it is populated with the package names. The latter is useful when the import source contains a large number of classes. By choosing from this combo, you can reduce your view to classes in a specific package. When you select a class from this list, its members appear in the list box to the right of the dialog. Tick the classes you want to import and, for each of these, tick the class members you want to import. A set of buttons below the list boxes allow you to quickly tick or untick all listed items. Note that the Target File(s) field is pre-populated with the folder you had selected in the project navigation tree before displaying the import dialog. The combo to the right of this field gives you two options: (i) ‘blank’ (default) causes the generated files to be directly placed in the nominated folder, or (ii) ‘Use Folder Names’ causes sub-directories to be generated within the nominated folder that match the imported class package hierarchy. Press Import to commence importing the files. The imported files are added to the nominated folder in the project navigation tree, ready for viewing. 538 JavaGram Agile Development 14.7.2 Preferences To view or edit JADE’s user preferences, select Tools|Preferences. Once you’ve made your changes, press the OK button for them to take effect. Confirmed changes are remembered indefinitely. The preference categories are displayed as a tree. The Workspace category defines a number of options that affect the JADE workspace. The Search category defines the default options for search dialogs. The Editor category defines certain options that affect the appearance of the editor. JADE – JavaGram IDE 539 The Syntax Coloring category allows you to customize the color-coding rules of the editor. The Typing category controls the behavior of the editor in response to the user typing certain meta characters. 540 JavaGram Agile Development The Java category allows you to define the Java settings for the purpose of running an application. The JavaGram category allows you to define the default settings for a run configuration. Every time you create a new run configuration, it inherits its initial settings from the preferences defined here, which you can then override in the run configuration, if desired. JADE – JavaGram IDE 541 The Flash category allows you to nominate the preferred browser(s) for running browser clients. 542 JavaGram Agile Development 15 JavaGram Syntax This chapter describes the formal syntax of JavaGram. The syntax has been intentionally designed to be as close as possible to Java to make it easier for Java programmers to assimilate. A major addition is the use of a markup notation for defining user interface and text class members. This style was chosen due to its expressiveness for defining hierarchical information. 15.1 Conventions The JavaGram syntax is specified as a set of BNF-style production rules. The following conventions apply. The ::= operator means that the left-hand-side can be substituted for by the righthand-side. Terminals are enclosed in single quotes. These are typically keywords, operators, or meta symbols. The | operator means ‘or’. It specifies a choice of alternatives. {...}* means that the contents may appear zero or more times. {…}+ means that the contents may appear one or more times. {…}? means that the contents are optional (i.e., may appear zero or once). Parentheses are used to eliminate ambiguities related to the use of | in complex productions. Explanatory comments appear in italics. The production rules are divided into two groups. The first group (JAG) covers JavaGram’s syntax, as visible to the programmer. The second group (JTP) covers the production rules for client-server messaging – these are hidden from the programmer and managed by the JAG runtime. The only constructs not covered by the production rules are comments. Comments are eliminated during lexical analysis. There are three types of comments: Single line comments (e.g., // some comment). Comments that can span across multiple lines (e.g., /* some comment */). Internal line control comments (e.g., /#122#/ which causes the parser to treat the current line as being line 122). These are not for programmer use. 15.2 JAG Production Rules Target ::= JavaGram Syntax 543 JavaGram JavaGram ::= Literal | '<jag' TagProperties '>' {LoadMarkup | ClassDeclaration | Literal}* '</jag>' A script may contain just a literal (useful for configuration and data files). Also, literals may appear within <jag> markup (enables the IDE to parse and reformat such files correctly). Valid script tag attributes are: domain = "abc/xyz" mjv = "Min JavaGram Version in the format majorVer.minorVer" stage = ('$dev' | '$test' | '$prod') side = ('$client' | '$server' | '$both') TagProperties ::= {Identifier '=' TagValue}* TagValue ::= Literal | Type | '{' Expression '}' LoadMarkup ::= '<load' TagProperties '>' {RelativeFilePath}* '</load>' Valid load tag properties are: relative = ('true' | 'false') source = ('$local' | '$boot' | SocketStream) RelativeFilePath ::= '"' {DirectoryName '/'}* FileName '"' ClassDeclaration ::= {ClassQualifier}* 'class' Identifier {'extends' QualifiedId {',' QualifiedId}* }? '{' {ClassBodyDeclaration}* '}' QualifiedId ::= Identifier {'\' Identifier}* ClassBodyDeclaration ::= ClassBodyDeclarationNoConstructor | ConstructorDeclaration ClassBodyDeclarationNoConstructor ::= StaticInitializer | FieldDeclaration | MethodDeclaration | GuiMarkupDeclaration 544 JavaGram Agile Development | TextMarkupDeclaration StaticInitializer ::= 'static' Block FieldDeclaration ::= {FieldQualifier}* VariablesDeclarator ';' MethodDeclaration ::= {MethodQualifier}* (Type | 'void') Identifier MethodParameters (';' | Block) ConstructorDeclaration ::= {MethodQualifier}* Identifier MethodParameters ConstructorBody GuiMarkupDeclaration ::= {FieldQualifier}* '<'GuiTag {Identifier}? TagProperties ('/>' | '>' {GuiMarkupDeclaration}* '</'GuiTag'>') GuiTag ::= Identifier TagQualifiers TextMarkupDeclaration ::= {MethodQualifier}* '<'TextTag TextMethod TagProperties '>' Text '</'TextTag'>' TextTag ::= 'text' TagQualifiers TagQualifiers ::= {'.' Identifier {'.' Identifier}? }? TextMethod ::= Type Identifier MethodParameters ClassQualifier ::= 'abstract' | 'final' | 'clocal' | 'slocal' | 'remote' | 'singleton' | 'mutual' | 'generated' FieldQualifier ::= 'public' | 'protected' JavaGram Syntax 545 | 'private' | 'static' | 'final' | ‘delayed’ | 'generated' | 'getable' | 'setable' MethodQualifier ::= 'public' | 'protected' | 'private' | 'static' | 'abstract' | 'final' | 'synchronized' | 'clocal' | 'slocal' | 'remote' | 'generated' VariablesDeclarator ::= Type VariableDeclarator {, VariableDeclarator}* Type ::= 'string' | 'symbol' | 'int' | 'real' | 'char' | 'boolean' | 'date' | 'vague' | 'stream' | 'native' | 'object' | 'list' | 'vector' {'<' Type '>'}? | 'map' {'<' Type ',' Type '>}? | QualifiedId VariableDeclarator ::= Identifier {AssignOpr Expression}? AssignOpr ::= '=' 546 JavaGram Agile Development | '@=' Expression ::= ConditionalExpr | Assignment ConditionalExpr ::= LogicalOrExpr {'?' Expression ':' ConditionalExpr}? LogicalOrExpr ::= LogicalAndExpr {'||' LogicalOrExpr}? LogicalAndExpr ::= BitwiseOrExpr {'&&' LogicalAndExpr}? BitwiseOrExpr ::= BitwiseXorExpr {'|' BitwiseOrExpr}? BitwiseXorExpr ::= BitwiseAndExpr {'^' BitwiseXorExpr} BitwiseAndExpr ::= EqualityExpr {'&' BitwiseAndExpr}? EqualityExpr ::= RelationalExpr {('==' | '?=' | '!=' | '===' | '!==') EqualityExpr}? RelationalExpr ::= ShiftExpr {('<' | '>' | '<=' | '>=') RelationalExpr}? | ShiftExpr 'instanceof' Type ShiftExpr ::= AdditiveExpr {('<<' | '>>') ShiftExpr}? AdditiveExpr ::= MultiplicativeExpr {('+' | '-') AdditiveExpr}? MultiplicativeExpr ::= UnaryExpr {('*' | '/' | '%') MultiplicativeExpr}? UnaryExpr ::= ('++' | '--' | '+' | '-' | '~' | '!') UnaryExpr | PostfixExpr PostfixExpr ::= Primary JavaGram Syntax 547 | PostfixExpr ('++' | '--') Primary ::= Literal | 'this' | 'super' {'@' QualifiedId}? | QualifiedId {'.' ('symbol' | 'singleton')}? | '(' Expression ')' | 'arg' '(' Expression ')' | Primary '@' (Type | '<' GuiTag '>') | 'typeof' '(' Expression ')' | 'valueof' '(' Expression {',' Expression}? ')' | ('sys' | 'gui' | 'sql' | 'bom') '.' Identifier {'@' Type}? {ExprList}? | NewExpr | ObjectCreation | ListCreation | VectorCreation | MapCreation | FieldAccess | PropertyAccess | MethodCall | VecMapOrFieldAccess | DelayedString Literal ::= StringLiteral | SymbolLiteral | IntLiteral | RealLiteral | CharLiteral | DateLiteral | BinaryLiteral | ObjectLiteral | ProxyObjectLiteral | ListLiteral | VectorLiteral | MapLiteral | XmlLiteral | 'false' | 'true' | 'null' DelayedString ::= $ StringLiteral DelayedString and Text may contain expressions of the form {...} which are evaluated when the parent is evaluated. 548 JavaGram Agile Development NewExpr ::= 'new' QualifiedId ExprList { '{' {ClassBodyDeclarationNoConstructor}* '}' }? ExprList ::= '(' {Expression {',' Expression}* }? ')' ObjectCreation ::= 'object' '(' QualifiedId {',' Identifier '=>' Expression }* ')' ListCreation ::= 'list' ExprList VectorCreation ::= 'vector' ExprList MapCreation ::= 'map' '(' {Expression '=>' Expression {',' Expression '=>' Expression }* }? ')' FieldAccess ::= Primary ('.' | '?.') (Identifier | 'class') PropertyAccess ::= Identifier '.' PropertyName MethodCall ::= {TargetStream '::'}? {Primary ('.' | '?.')}? Identifier ExprList TargetStream ::= Primary VecMapOrFieldAccess ::= Primary ('[' Expression ']' | '.' SymbolLiteral) Identifier ::= (Letter | '_') {Letter | Digit | '_'}* PropertyName ::= Identifier StringLiteral ::= '"' {Char}* '"' SymbolLiteral ::= '$' (Identifier | '{' {Char)+ '}') JavaGram Syntax 549 IntLiteral ::= {Digit}+ | '0' {OctDigit}+ | '0' ('x' | 'X') {HexDigit}* RealLiteral ::= {Digit}* '.' {Digit}* {('e' | 'E') {'+' | '-'}? {Digit}+ }? | {Digit}+ ('e' | 'E') {'+' | '-'}? {Digit}+ OctDigit ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' Digit ::= OctDigit | '8' | '9' HexDigit ::= Digit | 'A' | 'a' | 'B' | 'b' | 'C' | 'c' | 'D' | 'd' | 'E' | 'e' | 'F' | 'f' CharLiteral ::= "'" ( Char | '\' Char | '\u' {HexDigit}4 ) "'" DateLiteral ::= '[#' YearMonthDay {HoursMinutesSecondsNanos}? | MillisecondsNanos} ']' BinaryLiteral ::= '[##' {HexDigit}* ']' ObjectLiteral ::= '[@' QualifiedId (LiteralFields | LiteralNamedFields) ']' LiteralFields ::= {Literal {',' Literal}* }? LiteralNamedFields ::= {Identifer '=>' Literal {',' Identifier '=>' Literal}* }? ProxyObjectLiteral ::= '[@' QualifiedId '#' IntLiteral ']' YearMonthDay ::= {Digit}+ ('-' | '/') {Digit}+ ('-' | '/') {Digit}+ HoursMinutesSecondsNanos ::= {Digit}+ ':' {Digit}+ ':' {Digit}+ {'.' {Digit}+ }? MillisecondsNanos ::= {Digit}+ {'.' {Digit}+ }? 550 JavaGram Agile Development Text ::= {Char}+ ListLiteral ::= '$(' Literal {',' Literal}* ')' Inside a literal, a list can begin with ( instead of $( VectorLiteral ::= '[' {Literal {',' Literal}* }? ']' MapLiteral ::= '[' Literal '=>' Literal {',' Literal '=>' Literal }* ']' | '[=>]' XmlLiteral ::= '<#' XmlData '#>' Assignment ::= LeftHandSide (AssignOpr | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '&=' | '^=' | '|=') Expression LeftHandSide ::= Indentifier | FieldAccess | VecMapOrFieldAccess | PropertyAccess | ‘sys’ ‘.’ Identifier MethodParameters ::= '(' ( '...' | {MethodParameter {',' MethodParameter}* }? ) ')' MethodParameter ::= {'final'}? Type VariableDeclarator ConstructorBody ::= '{' ( {'this' ExprList ';'}? | {'super' {'@' QualifiedId}? ExprList ';'}* ) {BlockStmt}* '}' Block ::= '{' {BlockStmt}* '}' BlockStmt ::= {'final'}? VariablesDeclarator ';' | Statement Statement ::= JavaGram Syntax 551 Block | ';' | 'if' '(' Expression ')' Statement {'else' Statement}? | 'while' '(' Expression ')' Statement | 'for' '(' {ForInit}? ';' {Expression}? ';' {ForUpdate}? ')' Statement | 'for' '(' Type Identifier 'in' Expression ')' Statement | 'switch' '(' Expression ')' SwitchBlock | 'do' Statement 'while' '(' Expression ')' ';' | 'break' ';' | 'continue' ';' | 'return' {Expression}? ';' | 'synchronized' '(' Expression ')' Block | 'throw' Expression ';' | 'try' Block {CatchClause}* {'finally' Block}? | 'query' Connections Block | 'transaction' Connections Block | 'assert' Expression ';' | StatementExpr ';' | AsyncMethodCall ';' SwitchBlock ::= '{' {{SwitchLabel}+ {BlockStatement}* }* '}' SwitchLabel ::= ('case' CaseExpr | 'default') ':' CaseExpr ::= IntLiteral | CharLiteral | {QualifiedId '.'} Identifier CatchClause ::= 'catch' '(' QualifiedId Identifier ')' Block ForInit ::= {StatementExpr {',' StatementExpr}* }? | VariablesDeclarator ForUpdate ::= {StatementExpr {',' StatementExpr}* }? StatementExpr ::= Expression Connections ::= '(' Expression {',' Expression}* ')' 552 JavaGram Agile Development AsyncMethodCall ::= {LeftHandSide AssignOpr | Type Identifier AssignOpr}? MethodCall { {('??' | '?>') Expression}? '->' AsyncCallback { '->' AsyncCallback }? }? AsyncCallback::= Primary | Block 15.3 JTP Production Rules All client requests and server responses contain a session identifier (sid) and request identifier (rid). These are used to keep track of the identity of requests/responses and to match them where required. ClientRequest ::= '<req' TagProperties ('/>' | '>' Expression '</'req'>') {BinaryData}? Valid tag properties are: sid = IntLiteral rid = IntLiteral timestamp = IntLiteral size = IntLiteral zip = IntLiteral line = IntLiteral id = IntLiteral idx = IntLiteral kind = "sys" | "app" err = StringLietral cmd = StringLiteral context = StringLietral script = StringLietral path = StringLietral file = StringLietral var = StringLiteral method = StringLiteral key = StringLiteral file_attach = StringLiteral async = BooleanLiteral relative = BooleanLiteral code = BooleanLiteral check = BooleanLiteral base = BooleanLiteral static = BooleanLiteral value = Literal JavaGram Syntax 553 ServerResponse ::= '<res' TagProperties ('/>' | '>' {Literal}? {';' ObjectLiteral}? '</'res'>') {BinaryData}? ObjectLiteral after semicolon is the implicit object for non-static remote calls Valid tag properties are: sid = IntLiteral rid = IntLiteral line = IntLiteral start = IntLiteral length = IntLiteral timestamp = IntLiteral env = IntLiteral frame = IntLiteral kind = "ok" | "err" broadcast = StringLiteral path = StringLietral file = StringLietral ext = StringLiteral file_attach = StringLiteral async = BooleanLiteral value = Literal BinaryData ::= {Byte}+ Used for communicating compressed message data or binary file content. InternalNativeLiteral ::= '[@@' QualifiedId Properties ']' Properties ::= {Identifier '=>' Expression {',' Identifier '=>' Expression}* }? InternalClassNameAtLocation ::= QualifiedId '@@' RelativeFilePath Used as a valid Factor in client-server communication to represent a reference to a class name as a value. It tells the server where the class is. 554 JavaGram Agile Development
© Copyright 2024