2. 2
My Background
• ADFm
– Part of Oracle's ADF Framework
– Provides data binding and database access
– Provides ways to customize data views via Groovy
– Groovy uses have basic DSL customizations
• Wanted to Learn JavaFX (after last year's GR8 Conf)
• Have 5 year old daughter
• Napili – named after the Maui bay
3. 3
Logo
• An early programming language (1960's)
• Popular in the 80's as a teaching tool
– Which is how I first heard about it
• A dialect of Lisp (but much more approachable)
• Currently (per Wikipedia) 197 different dialects
• Most famous for it's use of Turtle Graphics
4. 4
Turtle Graphics
• Vector based graphics using a relative cursor
– The “Turtle”
– Represented in early work as a triangle
• Turtles have three attributes
– A location
– An orientation
– A “pen” (which has on/off state, color, width, etc)
5. 5
Turtle Commands
• home
• left / right (takes int arg, degrees)
• forward / back (takes int arg, #pixels)
• penup / pendown
• pencolor (takes Color arg)
• show / hide
• speed (takes int arg)
6. 6
Language Style
• Commands without parens
– left 90, home, hide, etc
• Chained Commands
– left 90 forward 100 hide (as one line)
• Use with an explicit turtle, or with an implicit turtle
• Use Groovy language constructs
– Define variables
– Define methods
– Closures
7. 7
Language Decisions – DSL vs GPL
• DSL (Domain Specific Language)
– A limited language, designed to be useful for a specific task
– HTML, SQL, stuff you can build with Groovy Builders
• GPL (General Purpose Language)
– A general language, designed to be useful for any task
– Java, Groovy, Assembler, etc
• I went the GPLish route, full Groovy + DSL extensions
– But there's not necessarily a one right answer
9. 9
First, Create a Framework
• Created a Turtle class, with supporting classes
– Methods like #home(), #hide(), #forward(int), etc.
• Allows drawing on a canvas via a turtle
• Used JavaFX, including animations
– This isn't a JavaFX talk...
• Once that's all working, add the control language
10. 10
Executing Programs In Memory
• Object GroovyShell#evaluate(String)
– Can be slow (10ms)
• Cacheing Scripts
– Script GroovyShell#parse(String) ← slow part (10ms)
– Object Script#run() ← fast part (<1ms)
GroovyShell shell = new GroovyShell();
Script script = shell.parse("println 'hello world'");
script.run();
11. 11
Bindings – the Nouns
• A Binding is a way to pass values in/out of a script
– Used as unqualified variables (“println name”)
– Declared varaibles (def) go into a separate space
• Groovy provides a default Binding
– But you can provide one of your own
• Use it to provide
– Constant values
– Default values
– Error checked values
test = 1
println test
def test = 0
println test
binding.test
12. 12
Bindings in Napili
• Little used
– But could be used much more
• Just two variables, turtle and out
• out
– Special variable to redirect println output
• turtle
– Used for direct calls on the turtle
• turtle.penup(), turtle.forward(100) will now work
13. 13
Initialize Bindings in Napili
static class BasicBinding extends Binding {
Turtle turtle
public BasicBinding() {
super();
turtle = new Turtle()
}
14. 14
Getting Binding Values
public Object getVariable(String name) {
if (name == 'out') {
return NapiliOutput.getPrintWriter();
}
if (name == 'turtle') {
return turtle;
}
return super.getVariable(name)
}
15. 15
Setting Binding Values
public void setVariable(String name, Object value) {
if ("turtle".equals(name)) {
NapiliOutput.println('Unable to set "turtle" to value' + value)
return;
}
super.setVariable(name, value);
}
16. 16
Using the Binding
GroovyShell shell = new GroovyShell(config)
Script script = shell.parse(scriptStr)
script.setBinding(new BasicBinding())
script.run()
17. 17
Adding Imports
• Can add imports and static imports to scripts
• Allows easy usage of existing classes
• Added via CompilerConfiguration
– An optional parameter to GroovyShell
• Static imports can let you define variables
– import static java.lang.Math.PI lets “println PI” work
18. 18
Adding Imports in Napili
CompilerConfiguration config = new CompilerConfiguration()
ImportCustomizer ic = new ImportCustomizer();
ic.addImports('javafx.scene.paint.Color')
config.addCompilationCustomizers(ic)
GroovyShell shell = new GroovyShell(config)
• Adds an automatic import for JavaFX's Color so...
• turtle.pencolor(Color.Purple) now works
• If we instead do a static import, we could just say Purple
– ImportCustomizer#addStarImports(String)
19. 19
ScriptBaseClass – the Verbs
• Dynamically assign a new base class
• Allows you to declare top level functions
• Inserted as part of the CompilerConfiguration
• Has access to Binding variables
– Can be used for implicit parameter passing
21. 21
InvokeMethod
• Allows you to dynamically route method calls
– Lets you use a context object
– GroovyInterceptable interface
• Just add a #invokeMethod(String,args) function
def invokeMethod(String name, args) {
println name
}
def test() {println “hey”}
hello()
test()
22. 22
MethodMissing
• Allows you to specify new behavior if a method isn't
found
• Just add a #methodMissing(String,args) function
• Combined with a ScriptBaseClass...
23. 23
ScriptBaseClass in Napili
abstract class TurtleDelegateBaseScript extends Script {
// if we can't find the method, look for it on the turtle binding object
def methodMissing(String name, args) {
binding.turtle."$name"(* args)
}
}
• Abstract class
• Effectively reroutes all calls through Turtle
24. 24
ScriptBaseClass in Napili
CompilerConfiguration config = new CompilerConfiguration()
config.setScriptBaseClass("fullpath.TurtleDelegateBaseScript")
GroovyShell shell = new GroovyShell(config)
• Now, “forward(100)” works
• Because of optional parens, “forward 100” works too
• But “home()” is required, “home” won't work...
25. 25
No arg Methods
• But what about “home” or “penup”?
• They're seen as Binding variables
• So, use the Binding to execute them:
public Object getVariable(String name) {
...
// treat all no-arg methods on Turtle as binding variables with side effects
if (turtle.metaClass.respondsTo(turtle,name)) {
return turtle.metaClass.invokeMethod(turtle, name)
}
return super.getVariable(name)
}
26. 26
No arg Methods as Properties
• Use #propertyMissing(String) on Turtle
• Allows turtle.hide instead of turtle.hide()
def propertyMissing(String name) {
// treat all no-arg methods as properties
if (this.metaClass.respondsTo(this, name)) {
return this.metaClass.invokeMethod(this, name)
}
throw new MissingPropertyException("No property on Turtle named '$name'")
}
27. 27
Timeout protection
• What about while (true) {}?
• groovy.transform.TimedInterrupt
CompilerConfiguration config = new CompilerConfiguration()
config.setScriptBaseClass("org.netdance.napili.language.TurtleDelegateBaseScript")
ImportCustomizer ic = new ImportCustomizer();
ic.addImports('javafx.scene.paint.Color')
def ti = new ASTTransformationCustomizer([value: Napili.TIMEOUT],TimedInterrupt)
config.addCompilationCustomizers(ic, ti)
GroovyShell shell = new GroovyShell(config)
28. 28
Recap
• Create a Turtle object
• Make it available to scripts via the Binding
• Use ScriptBaseClass to route all method calls through it
• ImportCustomizer to allow using JavaFX Color
• TimedInterrupt to stop runaway processes
29. 29
Method Chaining
• You may have noticed....
– forward 100 back 100 hide
• It's harder than it looks
– first second third fourth == first(second).third(fourth)
– first second third fourth fifth == first(second).third(fourth).fifth
– GroovyConsole is great for understanding the parse rules
• No arg methods break that flow
– forward 100 hide home ← doesn't work
30. 30
Removing Language Features
• SecureASTCustomizer
– Static Analysis
– def s = Shell; s.exit(0)
– (See my talk this afternoon for more on security)
• Can be used to remove language features
31. 31
Removing Increment Operator (++)
def config = new CompilerConfiguration();
def customizer = new SecureASTCustomizer();
def tokens = []
tokens.add(Types.PLUS_PLUS);
customizer.setTokensBlacklist(tokens);
config.addCompilationCustomizers(customizer);
def shell = new GroovyShell(config);
32. 32
Removing For Loops
def config = new CompilerConfiguration();
def customizer = new SecureASTCustomizer();
def statements = []
statements.add(ForStatement.class);
customizer.setStatementsBlacklist(statements);
config.addCompilationCustomizers(customizer);
def shell = new GroovyShell(config);
34. 34
Resources
• Napili Code
– https://github.com/netdance/Napili
• My Blog
– https://jamesgdriscoll.wordpress.com/
• Books
– Groovy in Action, 2nd
ed (still in early access, but worth getting)
– Groovy for Domain Specific Languages
– DSLs in Action (multiple language examples)
35. 35
The preceding is intended to outline our general product
direction. It is intended for information purposes only,
and may not be incorporated into any contract. It is
not a commitment to deliver any material, code, or
functionality, and should not be relied upon in making
purchasing decisions.
The development, release, and timing of any features
or functionality described for Oracle’s products remains
at the sole discretion of Oracle.