SlideShare a Scribd company logo
1 of 67
Download to read offline
Building DSLs with Groovy:
A Real World Case Study
Sten Anderson
Agenda
What is a DSL?
Groovy’s DSL-friendly language features
Case study: City of Chicago
What is a DSL?
DSL: Domain Specific Language
Small, Specific “Mini-language” geared toward a Domain
Favor conciseness and readability in a narrow domain over Turing
Completeness
External
• Involve Lexer/Parser
• Ant, SQL
Internal (Embedded DSLs/Fluent Interfaces)
• Defined inside a more general purpose language
• Gant/Rake
DSL vs. Framework vs. API?
• What is OpenGL?
Why use Groovy to Write DSLs?
Groovy is a natural extension for Java Developers
• IDE support for “free”
At a minimum Groovy is “less verbose Java”
At a maximum Groovy lets you change the language itself
• dynamic methods and properties and operator overloading
Groovy has built-in DSLs (Builders)
Groovy’s DSL-Friendly Features
Reduced Syntax
Closures
Metaprogramming
• Dynamic methods and properties
• ExpandoMetaclass
Language Feature Example: Text Adventure Interpreter
class Game {
void go(dir) {
println "You go $dir"
}
void look() {
println "You look around and see nothing"
}
void take(it) {
println "You take the $it"
}
...
}
Let’s Start with Java…
…And Make it Groovy…
new GroovyShell().evaluate(“game.${playerInput}”)
go(“north”);
look();
take(“dagger”);
Reduced Syntax
go “north”
look()
take “dagger”
Metaprogramming: Dynamic Properties
class Game {
...
def propertyMissing(String name) {
name
}
go north
look()
take dagger
Metaprogramming: Dynamic Properties
def propertyMissing(String name) {
if (metaClass.respondsTo(this, name)) {
this."$name"()
}
name
}
go north
look
take dagger
Remember This?
new GroovyShell().evaluate(“game.${playerInput}”)
Not very “Groovy”
Closures and “with”
new GroovyShell().evaluate(“game.with{${playerInput}}”)
3.times {
go north
}
look
take dagger
One more thing... (the Synonym Problem)
look
> You see a dagger on the ground
get dagger
> I don’t understand that
grab dagger
> I don’t understand that
Just let me have the %$!#@ dagger!
> I don’t understand that
Metaprogramming: Dynamic Methods
def methodMissing(String name, args) {
if (["grab", “hold", "yoink"].contains(name)) {
this.take(args[0])
}
}
look
> You see a dagger on the ground
grab dagger
> You take the dagger
You’re darn right I do!
> I don’t understand that
def methodMissing(String name, args) {
def funcName = ThesaurusLookup.contains(name)
if (funcName) {
Game.metaClass."$name" = { item ->
this.”$funcName”(item)
}
this.”$name”(args[0])
}
}
Metaprogramming: ExpandoMetaclass
How Far We’ve Come
go north
look
take dagger
grab fish
game.go(“north”);
game.look();
game.take(“dagger”);
game.take(“fish”);
From this… To this…
Case Study: Budget Books for Fun and (non) Profit
The Challenge
Replace Mainframe “Budget Book Generator” application
Did not interact with the live budget data
Over 3 hours to produce a book
Very manual process (print the book, rescan as a PDF of images)
Somewhat dated look
The Solution
Java SE Swing Application deployed locally via Webstart
Time to produce final PDF reduced from three hours to a few minutes
Operated Directly with the Budget Data
PDF as native output: searchable, indexable
BookSection
PageElement
Book Object Model
BookSection
PageElement
TextElement TableHeaderElement TableRowElement …
BookSection
BookSection
TextElement
TableHeader
Separator
TableSection
TableRow
TableTotal
How do we go from Budget Objects to Book Objects?
•Departments
•Organizations
•Appropriations
•Positions
•BookSections
•PageElements
Can we write a DSL to create a book?
Building Drafts with ContentBuilder
interface ContentBuilder
- Draft build(ContentData)
ContentBuilder: First Attempt
Java
BookSection bookSection = new BookSection ("Positions");
TableHeaderElement header = new TableHeaderElement();
header.addHeader(new TableHeader(0.05));
header.addHeader(new TableHeader(0.3, "Position",
Formatter.LeftAligned));
bookSection.addPageElement(header);
for (TableSection section : tableSections) {
TableSectionElement sectionElement = new TableSectionElement();
sectionElement.addColumn(new TableColumn(2, section.getName());
bookSection.addPageElement(sectionElement);
...
BookSection bookSection = new BookSection ("Positions");
TableHeaderElement header = new TableHeaderElement();
Remember to fill out your Objects in Triplicate
1 2 3
BookSection bookSection = new BookSection ("Positions");
TableHeaderElement header = new TableHeaderElement();
header.addHeader(new TableHeader(0.05));
header.addHeader(new TableHeader(0.3, "Position",
Formatter.LeftAligned));
bookSection.addPageElement(header);
Declared here
Added here
BookSection bookSection = new BookSection ("Positions");
TableHeaderElement header = new TableHeaderElement();
header.addHeader(new TableHeader(0.05));
header.addHeader(
new TableHeader(0.3, "Position", Formatter.LeftAligned));
•What do these arguments mean?
•What is “0.05”?
•What is “Position”?
ContentBuilder: Second Attempt
Java with method chaining
Method Chaining: Return the “Current” Object
public void setWidth(double width) { this.width = width;}
public TableHeader setWidth(double width) {
this.width = width;
return this;
}
•Instead of:
•We have:
BookSection bookSection = new BookSection ("Positions");
bookSection.addPageElement(new TableHeader().
addHeader(new TableHeader().setWidth(0.05)).
addHeader(new TableHeader().
setWidth(0.3).
setName("Position").
setFormatter(Formatter.LeftAligned)));
for (TableSection section : tableSections) {
bookSection.addPageElement(new TableSectionElement().
addColumn(new TableColumn().
setColumnSpan(2).
setName(section.getName()));
...
}
Let’s Take a Step Back…
Both Java versions “work”, but are verbose and not overly readable
Need better support for building complex object graphs at the
language level
• The structure of the code should mirror the structure of the objects
(declarative syntax)
• Ceremony vs. Essence
We need a “Builder”
Well, Groovy has Builders
MarkupBuilder
• Tag-based markup (XML, HTML)
SwingBuilder
• GUIs
GraphicsBuilder
• Java2D
NodeBuilder
• General Object graph (Nodes)
MetaBuilder
• Builder of Builders
MarkupBuilder
def htmlBuilder = new MarkupBuilder()
htmlBuilder.html {
body {
h1 "Hi There!"
}
}
<html>
<body>
<h1>Hi There!</h1>
</body>
</html>
SwingBuilder
def builder = new SwingBuilder()
builder.frame (title:"Swing Builder Test",
size:[300,100],
location:[500,200]) {
flowLayout()
button(text:"Press Me")
}.show()
groovy.util.BuilderSupport
void setParent(Object parent, Object child);
Object createNode(Object name);
Object createNode(Object name, Object value);
Object createNode(Object name, Map attributes);
Object createNode(Object name, Map attributes, Object value);
createNode(Object name, Map attributes, Object value)
h1 "Hi There!"
button text:"Press Me“, visible:true
Reduced Syntax
foo [a1:“val1”, b2:true] foo a1:“val1”, b2:true
foo (a1:“val1”, b2:true, {bar()})
foo (a1:“val1”, b2:true) {bar()}
Instead of this:
frame ([title:"Swing Builder Test"], {
flowLayout()
button(text:"Press Me")
})
frame (title:"Swing Builder Test") {
flowLayout()
button(text:"Press Me")
}
You have this:
Better Still: FactoryBuilderSupport
Used by SwingBuilder
Register “Factories” that know how to create your objects
Convenience AbstractFactory requires only one method:
• Object newInstance ( FactoryBuilderSupport builder,
Object name,
Object value,
Map attributes )
A Look Inside SwingBuilder
public SwingBuilder() {
...
registerFactory("button", new RichActionWidgetFactory(JButton));
...
}
ContentBuilder: Final Attempt
BookSection
BookSection
TextElement
TableHeader
Separator
TableSection
TableRow
TableTotal
BookSectionBuilder (extends FactoryBuilderSupport)
public BookSectionBuilder() {
registerFactory("bookSection", new BookSectionFactory());
registerFactory("textElement",
new PageElementFactory(TextElement.class));
...
}
BookSectionFactory (extends AbstractFactory)
public Object newInstance(FactoryBuilderSupport builder, Object name,
Object value, Map attributes) {
return new BookSection(attributes.get(“name”).toString());
}
public void setParent( FactoryBuilderSupport builder, Object parent,
Object child ) {
((BookSection) parent).newBookSection((BookSection) child);
}
PageElementFactory (extends AbstractFactory)
private Class<? extends PageElement> clazz;
public Object newInstance(FactoryBuilderSupport builder, Object name,
Object value, Map attributes) {
return clazz.newInstance();
}
public void setParent( FactoryBuilderSupport builder, Object parent,
Object child ) {
((BookSection) parent).newPageElement((PageElement) child);
}
Groovy Builder (Final Version)
bookSection (name:"Positions") {
tableHeaderElement(styleName:”header.large”, preTablePadding:10) {
tableHeader width:0.05
tableHeader name:”Position”, width:0.3,formatter:LeftAligned
}
tableSections.each { section ->
tableSectionElement (styleName:"table.subheader.1") {
tableColumn columnSpan:2, section.name
}
...
...
positions.each { position ->
tableRowElement (styleName:"table.row") {
tableColumn position.accountId
tableColumn position.title
}
}
tableTotalElement(styleName:"table.total") {
tableColumn columnSpan:2, "Position Total"
}
} // table section
tableFooterElement()
} // book section
for (Position position : positions) {
TableRowElement row = new TableRowElement();
row.setStyleName(“table.row”);
row.addColumn(new TableColumn(position.getAccountId()));
row.addColumn(new TableColumn(position.getTitle()));
bookSection.newPageElement(row);
}
positions.each { position ->
tableRowElement (styleName:"table.row") {
tableColumn position.accountId
tableColumn position.title
}
}
Before:
After:
Free Things “Built-In” to the Builder
Wiring of map attributes to object properties
Preservation of Object hierarchy
Automatic management of closure delegates so that the “Builder” is
always in scope
Closures: What’s a Delegate?
def htmlBuilder = new MarkupBuilder()
htmlBuilder.html {
body {
h1 "Hi There!"
}
}
“html”, “body”, and “h1” don’t exist in scope, but rather are handled
by the Builder
htmlBuilder needs to be set as the closure’s Delegate
…But the Methods Don’t Exist in the Builder Either
def xmlBuilder = new MarkupBuilder()
xmoBuilder.invoices {
invoice {
lineitem ...
}
}
The Builder “pretends” to have these methods
invokeMethod is overridden to write out the markup or (in the case of
SwingBuilder/BookSectionBuilder) dispatch the method to the correct
Factory
(Metaprogramming: Dynamic Methods)
Let’s Add Charting Support
First Write a Factory
private static class ChartFactory extends AbstractFactory {
public Object newInstance(...) {
return new Chart(value.toString());
}
public void setParent(...) {
TableCell cell = (TableCell) parent;
cell.addContent(child.toString());
}
}
Then Register the Factory…
public BookSectionBuilder() {
registerFactory("bookSection", new BookSectionFactory());
registerFactory("textElement",
new PageElementFactory(TextElement.class));
...
registerFactory("chart", new ChartFactory());
}
…And It’s Ready to use in the Builder
tableRowElement {
tableColumn (columnSpan:2) {
chart dept.fundPercent
}
tableColumn {
chart allFundTotal
}
}
Is a DSL Right for Me?
Are the people who maintain your project different than those who
initially developed it?
DSLs give the “gift of documentation”
Is your project part of a domain?
Using Dynamic Languages like Groovy make DSLs more Painless
Resources
Writing an Adventure game DSL in LISP: http://www.lisperati.com/casting.html
http://groovy.codehaus.org/FactoryBuilderSupport
http://docs.codehaus.org/display/GROOVY/MetaBuilder
http://docs.codehaus.org/display/GROOVY/Writing+Domain-Specific+Languages
http://www.agiledeveloper.com/blog/PermaLink.aspx?guid=ce021e6d-b0c2-4d83-8b69-
c163c290983d
http://blogs.citytechinc.com/sanderson/
King’s Quest and Zork images taken from Wikipedia.
“Groovy is like Gravy, except that it has the „Groove‟ in it.”
-Meredith Anderson (5 Year Old)

More Related Content

What's hot

groovy databases
groovy databasesgroovy databases
groovy databasesPaul King
 
Rapid and Scalable Development with MongoDB, PyMongo, and Ming
Rapid and Scalable Development with MongoDB, PyMongo, and MingRapid and Scalable Development with MongoDB, PyMongo, and Ming
Rapid and Scalable Development with MongoDB, PyMongo, and MingRick Copeland
 
Java Development with MongoDB
Java Development with MongoDBJava Development with MongoDB
Java Development with MongoDBScott Hernandez
 
Morphia: Simplifying Persistence for Java and MongoDB
Morphia:  Simplifying Persistence for Java and MongoDBMorphia:  Simplifying Persistence for Java and MongoDB
Morphia: Simplifying Persistence for Java and MongoDBJeff Yemin
 
Solr's Search Relevancy (Understand Solr's query debug)
Solr's Search Relevancy (Understand Solr's query debug)Solr's Search Relevancy (Understand Solr's query debug)
Solr's Search Relevancy (Understand Solr's query debug)Wongnai
 
MongoDB: Easy Java Persistence with Morphia
MongoDB: Easy Java Persistence with MorphiaMongoDB: Easy Java Persistence with Morphia
MongoDB: Easy Java Persistence with MorphiaScott Hernandez
 
11. session 11 functions and objects
11. session 11   functions and objects11. session 11   functions and objects
11. session 11 functions and objectsPhúc Đỗ
 
jQuery Rescue Adventure
jQuery Rescue AdventurejQuery Rescue Adventure
jQuery Rescue AdventureAllegient
 
Ruby Development and MongoMapper (John Nunemaker)
Ruby Development and MongoMapper (John Nunemaker)Ruby Development and MongoMapper (John Nunemaker)
Ruby Development and MongoMapper (John Nunemaker)MongoSF
 
ElasticSearch for .NET Developers
ElasticSearch for .NET DevelopersElasticSearch for .NET Developers
ElasticSearch for .NET DevelopersBen van Mol
 
descriptive programming
descriptive programmingdescriptive programming
descriptive programmingAnand Dhana
 
Simplifying Persistence for Java and MongoDB with Morphia
Simplifying Persistence for Java and MongoDB with MorphiaSimplifying Persistence for Java and MongoDB with Morphia
Simplifying Persistence for Java and MongoDB with MorphiaMongoDB
 

What's hot (19)

groovy databases
groovy databasesgroovy databases
groovy databases
 
Rapid and Scalable Development with MongoDB, PyMongo, and Ming
Rapid and Scalable Development with MongoDB, PyMongo, and MingRapid and Scalable Development with MongoDB, PyMongo, and Ming
Rapid and Scalable Development with MongoDB, PyMongo, and Ming
 
Java Development with MongoDB
Java Development with MongoDBJava Development with MongoDB
Java Development with MongoDB
 
Morphia: Simplifying Persistence for Java and MongoDB
Morphia:  Simplifying Persistence for Java and MongoDBMorphia:  Simplifying Persistence for Java and MongoDB
Morphia: Simplifying Persistence for Java and MongoDB
 
greenDAO
greenDAOgreenDAO
greenDAO
 
Solr's Search Relevancy (Understand Solr's query debug)
Solr's Search Relevancy (Understand Solr's query debug)Solr's Search Relevancy (Understand Solr's query debug)
Solr's Search Relevancy (Understand Solr's query debug)
 
MongoDB: Easy Java Persistence with Morphia
MongoDB: Easy Java Persistence with MorphiaMongoDB: Easy Java Persistence with Morphia
MongoDB: Easy Java Persistence with Morphia
 
11. session 11 functions and objects
11. session 11   functions and objects11. session 11   functions and objects
11. session 11 functions and objects
 
Spock and Geb
Spock and GebSpock and Geb
Spock and Geb
 
MongoDB
MongoDB MongoDB
MongoDB
 
jQuery Rescue Adventure
jQuery Rescue AdventurejQuery Rescue Adventure
jQuery Rescue Adventure
 
Ruby Development and MongoMapper (John Nunemaker)
Ruby Development and MongoMapper (John Nunemaker)Ruby Development and MongoMapper (John Nunemaker)
Ruby Development and MongoMapper (John Nunemaker)
 
MongoDB With Style
MongoDB With StyleMongoDB With Style
MongoDB With Style
 
Html5 Overview
Html5 OverviewHtml5 Overview
Html5 Overview
 
Green dao
Green daoGreen dao
Green dao
 
ElasticSearch for .NET Developers
ElasticSearch for .NET DevelopersElasticSearch for .NET Developers
ElasticSearch for .NET Developers
 
descriptive programming
descriptive programmingdescriptive programming
descriptive programming
 
Knockout
KnockoutKnockout
Knockout
 
Simplifying Persistence for Java and MongoDB with Morphia
Simplifying Persistence for Java and MongoDB with MorphiaSimplifying Persistence for Java and MongoDB with Morphia
Simplifying Persistence for Java and MongoDB with Morphia
 

Viewers also liked

Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - Spri...
Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - Spri...Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - Spri...
Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - Spri...Guillaume Laforge
 
Bab 1 TIK kelas IX smp 18 semarang
Bab 1 TIK kelas IX smp 18 semarang Bab 1 TIK kelas IX smp 18 semarang
Bab 1 TIK kelas IX smp 18 semarang TamaMEN27
 
Kevin kuhn: Neurophysical Fitness & Nutrition
Kevin kuhn: Neurophysical Fitness & NutritionKevin kuhn: Neurophysical Fitness & Nutrition
Kevin kuhn: Neurophysical Fitness & NutritionKevin Kuhn
 
USING THE TENANT PORTAL
USING THE TENANT PORTALUSING THE TENANT PORTAL
USING THE TENANT PORTALJanice Knutson
 
Developing a Plan and Discipline
Developing a Plan and DisciplineDeveloping a Plan and Discipline
Developing a Plan and DisciplineEric Patrick
 
JANS Catherine resume July 2015
JANS Catherine resume July  2015JANS Catherine resume July  2015
JANS Catherine resume July 2015Catherine Jans
 
Эдийн засгийн ил тод байдлыг дэмжих хуулийн засгийн газрын журам
Эдийн засгийн ил тод байдлыг дэмжих хуулийн засгийн газрын журамЭдийн засгийн ил тод байдлыг дэмжих хуулийн засгийн газрын журам
Эдийн засгийн ил тод байдлыг дэмжих хуулийн засгийн газрын журамUmguullin Mongol Umguulugch
 
Evaluacion sitios-web
Evaluacion sitios-webEvaluacion sitios-web
Evaluacion sitios-webDaniela Gomez
 

Viewers also liked (18)

Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - Spri...
Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - Spri...Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - Spri...
Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - Spri...
 
5011 Q sir_7&8 chapter,OK
5011 Q sir_7&8 chapter,OK5011 Q sir_7&8 chapter,OK
5011 Q sir_7&8 chapter,OK
 
Chooing a Broker
Chooing a BrokerChooing a Broker
Chooing a Broker
 
Bab 1 TIK kelas IX smp 18 semarang
Bab 1 TIK kelas IX smp 18 semarang Bab 1 TIK kelas IX smp 18 semarang
Bab 1 TIK kelas IX smp 18 semarang
 
Kevin kuhn: Neurophysical Fitness & Nutrition
Kevin kuhn: Neurophysical Fitness & NutritionKevin kuhn: Neurophysical Fitness & Nutrition
Kevin kuhn: Neurophysical Fitness & Nutrition
 
Soybean sub sector paln_RED
Soybean sub sector paln_REDSoybean sub sector paln_RED
Soybean sub sector paln_RED
 
ГК "АНА"
ГК "АНА"ГК "АНА"
ГК "АНА"
 
WatSan-Technical-Guidelines_CLP
WatSan-Technical-Guidelines_CLPWatSan-Technical-Guidelines_CLP
WatSan-Technical-Guidelines_CLP
 
Urshuul uzuuleh tuhai huuliin nemelt, uurchlult
Urshuul uzuuleh tuhai huuliin nemelt, uurchlultUrshuul uzuuleh tuhai huuliin nemelt, uurchlult
Urshuul uzuuleh tuhai huuliin nemelt, uurchlult
 
USING THE TENANT PORTAL
USING THE TENANT PORTALUSING THE TENANT PORTAL
USING THE TENANT PORTAL
 
Draft Enterprises training_ Bangla_22.8.11
Draft Enterprises training_ Bangla_22.8.11Draft Enterprises training_ Bangla_22.8.11
Draft Enterprises training_ Bangla_22.8.11
 
Developing a Plan and Discipline
Developing a Plan and DisciplineDeveloping a Plan and Discipline
Developing a Plan and Discipline
 
Ana group
Ana groupAna group
Ana group
 
Milk-sector-outcome-report_December-2014_CLP
Milk-sector-outcome-report_December-2014_CLPMilk-sector-outcome-report_December-2014_CLP
Milk-sector-outcome-report_December-2014_CLP
 
Lokman
LokmanLokman
Lokman
 
JANS Catherine resume July 2015
JANS Catherine resume July  2015JANS Catherine resume July  2015
JANS Catherine resume July 2015
 
Эдийн засгийн ил тод байдлыг дэмжих хуулийн засгийн газрын журам
Эдийн засгийн ил тод байдлыг дэмжих хуулийн засгийн газрын журамЭдийн засгийн ил тод байдлыг дэмжих хуулийн засгийн газрын журам
Эдийн засгийн ил тод байдлыг дэмжих хуулийн засгийн газрын журам
 
Evaluacion sitios-web
Evaluacion sitios-webEvaluacion sitios-web
Evaluacion sitios-web
 

Similar to Building DSLs with Groovy

Rich Internet Applications con JavaFX e NetBeans
Rich Internet Applications  con JavaFX e NetBeans Rich Internet Applications  con JavaFX e NetBeans
Rich Internet Applications con JavaFX e NetBeans Fabrizio Giudici
 
JavaScript for Flex Devs
JavaScript for Flex DevsJavaScript for Flex Devs
JavaScript for Flex DevsAaronius
 
Introduction to Client-Side Javascript
Introduction to Client-Side JavascriptIntroduction to Client-Side Javascript
Introduction to Client-Side JavascriptJulie Iskander
 
JavaScript Workshop
JavaScript WorkshopJavaScript Workshop
JavaScript WorkshopPamela Fox
 
jQuery - Tips And Tricks
jQuery - Tips And TricksjQuery - Tips And Tricks
jQuery - Tips And TricksLester Lievens
 
Sprout core and performance
Sprout core and performanceSprout core and performance
Sprout core and performanceYehuda Katz
 
Building Apps with MongoDB
Building Apps with MongoDBBuilding Apps with MongoDB
Building Apps with MongoDBNate Abele
 
CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on AndroidSven Haiges
 
Building Go Web Apps
Building Go Web AppsBuilding Go Web Apps
Building Go Web AppsMark
 
Extreme Swift
Extreme SwiftExtreme Swift
Extreme SwiftMovel
 
Data access 2.0? Please welcome: Spring Data!
Data access 2.0? Please welcome: Spring Data!Data access 2.0? Please welcome: Spring Data!
Data access 2.0? Please welcome: Spring Data!Oliver Gierke
 
Getting the most out of Java [Nordic Coding-2010]
Getting the most out of Java [Nordic Coding-2010]Getting the most out of Java [Nordic Coding-2010]
Getting the most out of Java [Nordic Coding-2010]Sven Efftinge
 
Ejb3 Struts Tutorial En
Ejb3 Struts Tutorial EnEjb3 Struts Tutorial En
Ejb3 Struts Tutorial EnAnkur Dongre
 
Ejb3 Struts Tutorial En
Ejb3 Struts Tutorial EnEjb3 Struts Tutorial En
Ejb3 Struts Tutorial EnAnkur Dongre
 

Similar to Building DSLs with Groovy (20)

Rich Internet Applications con JavaFX e NetBeans
Rich Internet Applications  con JavaFX e NetBeans Rich Internet Applications  con JavaFX e NetBeans
Rich Internet Applications con JavaFX e NetBeans
 
JavaScript for Flex Devs
JavaScript for Flex DevsJavaScript for Flex Devs
JavaScript for Flex Devs
 
Introduction to Client-Side Javascript
Introduction to Client-Side JavascriptIntroduction to Client-Side Javascript
Introduction to Client-Side Javascript
 
"Javascript" por Tiago Rodrigues
"Javascript" por Tiago Rodrigues"Javascript" por Tiago Rodrigues
"Javascript" por Tiago Rodrigues
 
JavaScript Workshop
JavaScript WorkshopJavaScript Workshop
JavaScript Workshop
 
jQuery - Tips And Tricks
jQuery - Tips And TricksjQuery - Tips And Tricks
jQuery - Tips And Tricks
 
Jquery
JqueryJquery
Jquery
 
Green dao
Green daoGreen dao
Green dao
 
jQuery
jQueryjQuery
jQuery
 
Sprout core and performance
Sprout core and performanceSprout core and performance
Sprout core and performance
 
jQuery
jQueryjQuery
jQuery
 
Building Apps with MongoDB
Building Apps with MongoDBBuilding Apps with MongoDB
Building Apps with MongoDB
 
CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on Android
 
Building Go Web Apps
Building Go Web AppsBuilding Go Web Apps
Building Go Web Apps
 
Extreme Swift
Extreme SwiftExtreme Swift
Extreme Swift
 
Latinoware
LatinowareLatinoware
Latinoware
 
Data access 2.0? Please welcome: Spring Data!
Data access 2.0? Please welcome: Spring Data!Data access 2.0? Please welcome: Spring Data!
Data access 2.0? Please welcome: Spring Data!
 
Getting the most out of Java [Nordic Coding-2010]
Getting the most out of Java [Nordic Coding-2010]Getting the most out of Java [Nordic Coding-2010]
Getting the most out of Java [Nordic Coding-2010]
 
Ejb3 Struts Tutorial En
Ejb3 Struts Tutorial EnEjb3 Struts Tutorial En
Ejb3 Struts Tutorial En
 
Ejb3 Struts Tutorial En
Ejb3 Struts Tutorial EnEjb3 Struts Tutorial En
Ejb3 Struts Tutorial En
 

Recently uploaded

How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
Key Features Of Token Development (1).pptx
Key  Features Of Token  Development (1).pptxKey  Features Of Token  Development (1).pptx
Key Features Of Token Development (1).pptxLBM Solutions
 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...HostedbyConfluent
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
 
Snow Chain-Integrated Tire for a Safe Drive on Winter Roads
Snow Chain-Integrated Tire for a Safe Drive on Winter RoadsSnow Chain-Integrated Tire for a Safe Drive on Winter Roads
Snow Chain-Integrated Tire for a Safe Drive on Winter RoadsHyundai Motor Group
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersThousandEyes
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024Scott Keck-Warren
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Patryk Bandurski
 
Azure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & ApplicationAzure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & ApplicationAndikSusilo4
 
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptxMaking_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptxnull - The Open Security Community
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticscarlostorres15106
 
How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?XfilesPro
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphNeo4j
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitecturePixlogix Infotech
 
Hyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your Budget
Hyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your BudgetHyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your Budget
Hyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your BudgetEnjoy Anytime
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsMemoori
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreternaman860154
 

Recently uploaded (20)

How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
Key Features Of Token Development (1).pptx
Key  Features Of Token  Development (1).pptxKey  Features Of Token  Development (1).pptx
Key Features Of Token Development (1).pptx
 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
 
Snow Chain-Integrated Tire for a Safe Drive on Winter Roads
Snow Chain-Integrated Tire for a Safe Drive on Winter RoadsSnow Chain-Integrated Tire for a Safe Drive on Winter Roads
Snow Chain-Integrated Tire for a Safe Drive on Winter Roads
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
 
Azure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & ApplicationAzure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & Application
 
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptxMaking_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
 
How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC Architecture
 
Hyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your Budget
Hyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your BudgetHyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your Budget
Hyderabad Call Girls Khairatabad ✨ 7001305949 ✨ Cheap Price Your Budget
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial Buildings
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 

Building DSLs with Groovy

  • 1. Building DSLs with Groovy: A Real World Case Study Sten Anderson
  • 2. Agenda What is a DSL? Groovy’s DSL-friendly language features Case study: City of Chicago
  • 3. What is a DSL?
  • 4. DSL: Domain Specific Language Small, Specific “Mini-language” geared toward a Domain Favor conciseness and readability in a narrow domain over Turing Completeness External • Involve Lexer/Parser • Ant, SQL Internal (Embedded DSLs/Fluent Interfaces) • Defined inside a more general purpose language • Gant/Rake DSL vs. Framework vs. API? • What is OpenGL?
  • 5. Why use Groovy to Write DSLs? Groovy is a natural extension for Java Developers • IDE support for “free” At a minimum Groovy is “less verbose Java” At a maximum Groovy lets you change the language itself • dynamic methods and properties and operator overloading Groovy has built-in DSLs (Builders)
  • 6. Groovy’s DSL-Friendly Features Reduced Syntax Closures Metaprogramming • Dynamic methods and properties • ExpandoMetaclass
  • 7. Language Feature Example: Text Adventure Interpreter
  • 8. class Game { void go(dir) { println "You go $dir" } void look() { println "You look around and see nothing" } void take(it) { println "You take the $it" } ... }
  • 10. …And Make it Groovy… new GroovyShell().evaluate(“game.${playerInput}”) go(“north”); look(); take(“dagger”);
  • 12. Metaprogramming: Dynamic Properties class Game { ... def propertyMissing(String name) { name } go north look() take dagger
  • 13. Metaprogramming: Dynamic Properties def propertyMissing(String name) { if (metaClass.respondsTo(this, name)) { this."$name"() } name } go north look take dagger
  • 15. Closures and “with” new GroovyShell().evaluate(“game.with{${playerInput}}”) 3.times { go north } look take dagger
  • 16. One more thing... (the Synonym Problem) look > You see a dagger on the ground get dagger > I don’t understand that grab dagger > I don’t understand that Just let me have the %$!#@ dagger! > I don’t understand that
  • 17. Metaprogramming: Dynamic Methods def methodMissing(String name, args) { if (["grab", “hold", "yoink"].contains(name)) { this.take(args[0]) } } look > You see a dagger on the ground grab dagger > You take the dagger You’re darn right I do! > I don’t understand that
  • 18. def methodMissing(String name, args) { def funcName = ThesaurusLookup.contains(name) if (funcName) { Game.metaClass."$name" = { item -> this.”$funcName”(item) } this.”$name”(args[0]) } } Metaprogramming: ExpandoMetaclass
  • 19. How Far We’ve Come go north look take dagger grab fish game.go(“north”); game.look(); game.take(“dagger”); game.take(“fish”); From this… To this…
  • 20. Case Study: Budget Books for Fun and (non) Profit
  • 21. The Challenge Replace Mainframe “Budget Book Generator” application Did not interact with the live budget data Over 3 hours to produce a book Very manual process (print the book, rescan as a PDF of images) Somewhat dated look
  • 22. The Solution Java SE Swing Application deployed locally via Webstart Time to produce final PDF reduced from three hours to a few minutes Operated Directly with the Budget Data PDF as native output: searchable, indexable
  • 23.
  • 24.
  • 29. How do we go from Budget Objects to Book Objects? •Departments •Organizations •Appropriations •Positions •BookSections •PageElements Can we write a DSL to create a book?
  • 30. Building Drafts with ContentBuilder interface ContentBuilder - Draft build(ContentData)
  • 32. BookSection bookSection = new BookSection ("Positions"); TableHeaderElement header = new TableHeaderElement(); header.addHeader(new TableHeader(0.05)); header.addHeader(new TableHeader(0.3, "Position", Formatter.LeftAligned)); bookSection.addPageElement(header); for (TableSection section : tableSections) { TableSectionElement sectionElement = new TableSectionElement(); sectionElement.addColumn(new TableColumn(2, section.getName()); bookSection.addPageElement(sectionElement); ...
  • 33. BookSection bookSection = new BookSection ("Positions"); TableHeaderElement header = new TableHeaderElement(); Remember to fill out your Objects in Triplicate 1 2 3
  • 34. BookSection bookSection = new BookSection ("Positions"); TableHeaderElement header = new TableHeaderElement(); header.addHeader(new TableHeader(0.05)); header.addHeader(new TableHeader(0.3, "Position", Formatter.LeftAligned)); bookSection.addPageElement(header); Declared here Added here
  • 35. BookSection bookSection = new BookSection ("Positions"); TableHeaderElement header = new TableHeaderElement(); header.addHeader(new TableHeader(0.05)); header.addHeader( new TableHeader(0.3, "Position", Formatter.LeftAligned)); •What do these arguments mean? •What is “0.05”? •What is “Position”?
  • 36. ContentBuilder: Second Attempt Java with method chaining
  • 37. Method Chaining: Return the “Current” Object public void setWidth(double width) { this.width = width;} public TableHeader setWidth(double width) { this.width = width; return this; } •Instead of: •We have:
  • 38. BookSection bookSection = new BookSection ("Positions"); bookSection.addPageElement(new TableHeader(). addHeader(new TableHeader().setWidth(0.05)). addHeader(new TableHeader(). setWidth(0.3). setName("Position"). setFormatter(Formatter.LeftAligned))); for (TableSection section : tableSections) { bookSection.addPageElement(new TableSectionElement(). addColumn(new TableColumn(). setColumnSpan(2). setName(section.getName())); ... }
  • 39. Let’s Take a Step Back… Both Java versions “work”, but are verbose and not overly readable Need better support for building complex object graphs at the language level • The structure of the code should mirror the structure of the objects (declarative syntax) • Ceremony vs. Essence We need a “Builder”
  • 40. Well, Groovy has Builders MarkupBuilder • Tag-based markup (XML, HTML) SwingBuilder • GUIs GraphicsBuilder • Java2D NodeBuilder • General Object graph (Nodes) MetaBuilder • Builder of Builders
  • 41. MarkupBuilder def htmlBuilder = new MarkupBuilder() htmlBuilder.html { body { h1 "Hi There!" } } <html> <body> <h1>Hi There!</h1> </body> </html>
  • 42. SwingBuilder def builder = new SwingBuilder() builder.frame (title:"Swing Builder Test", size:[300,100], location:[500,200]) { flowLayout() button(text:"Press Me") }.show()
  • 43. groovy.util.BuilderSupport void setParent(Object parent, Object child); Object createNode(Object name); Object createNode(Object name, Object value); Object createNode(Object name, Map attributes); Object createNode(Object name, Map attributes, Object value);
  • 44. createNode(Object name, Map attributes, Object value) h1 "Hi There!" button text:"Press Me“, visible:true
  • 45. Reduced Syntax foo [a1:“val1”, b2:true] foo a1:“val1”, b2:true foo (a1:“val1”, b2:true, {bar()}) foo (a1:“val1”, b2:true) {bar()}
  • 46. Instead of this: frame ([title:"Swing Builder Test"], { flowLayout() button(text:"Press Me") }) frame (title:"Swing Builder Test") { flowLayout() button(text:"Press Me") } You have this:
  • 47. Better Still: FactoryBuilderSupport Used by SwingBuilder Register “Factories” that know how to create your objects Convenience AbstractFactory requires only one method: • Object newInstance ( FactoryBuilderSupport builder, Object name, Object value, Map attributes )
  • 48. A Look Inside SwingBuilder public SwingBuilder() { ... registerFactory("button", new RichActionWidgetFactory(JButton)); ... }
  • 52. BookSectionBuilder (extends FactoryBuilderSupport) public BookSectionBuilder() { registerFactory("bookSection", new BookSectionFactory()); registerFactory("textElement", new PageElementFactory(TextElement.class)); ... }
  • 53. BookSectionFactory (extends AbstractFactory) public Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes) { return new BookSection(attributes.get(“name”).toString()); } public void setParent( FactoryBuilderSupport builder, Object parent, Object child ) { ((BookSection) parent).newBookSection((BookSection) child); }
  • 54. PageElementFactory (extends AbstractFactory) private Class<? extends PageElement> clazz; public Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes) { return clazz.newInstance(); } public void setParent( FactoryBuilderSupport builder, Object parent, Object child ) { ((BookSection) parent).newPageElement((PageElement) child); }
  • 55. Groovy Builder (Final Version) bookSection (name:"Positions") { tableHeaderElement(styleName:”header.large”, preTablePadding:10) { tableHeader width:0.05 tableHeader name:”Position”, width:0.3,formatter:LeftAligned } tableSections.each { section -> tableSectionElement (styleName:"table.subheader.1") { tableColumn columnSpan:2, section.name } ...
  • 56. ... positions.each { position -> tableRowElement (styleName:"table.row") { tableColumn position.accountId tableColumn position.title } } tableTotalElement(styleName:"table.total") { tableColumn columnSpan:2, "Position Total" } } // table section tableFooterElement() } // book section
  • 57. for (Position position : positions) { TableRowElement row = new TableRowElement(); row.setStyleName(“table.row”); row.addColumn(new TableColumn(position.getAccountId())); row.addColumn(new TableColumn(position.getTitle())); bookSection.newPageElement(row); } positions.each { position -> tableRowElement (styleName:"table.row") { tableColumn position.accountId tableColumn position.title } } Before: After:
  • 58. Free Things “Built-In” to the Builder Wiring of map attributes to object properties Preservation of Object hierarchy Automatic management of closure delegates so that the “Builder” is always in scope
  • 59. Closures: What’s a Delegate? def htmlBuilder = new MarkupBuilder() htmlBuilder.html { body { h1 "Hi There!" } } “html”, “body”, and “h1” don’t exist in scope, but rather are handled by the Builder htmlBuilder needs to be set as the closure’s Delegate
  • 60. …But the Methods Don’t Exist in the Builder Either def xmlBuilder = new MarkupBuilder() xmoBuilder.invoices { invoice { lineitem ... } } The Builder “pretends” to have these methods invokeMethod is overridden to write out the markup or (in the case of SwingBuilder/BookSectionBuilder) dispatch the method to the correct Factory (Metaprogramming: Dynamic Methods)
  • 62. First Write a Factory private static class ChartFactory extends AbstractFactory { public Object newInstance(...) { return new Chart(value.toString()); } public void setParent(...) { TableCell cell = (TableCell) parent; cell.addContent(child.toString()); } }
  • 63. Then Register the Factory… public BookSectionBuilder() { registerFactory("bookSection", new BookSectionFactory()); registerFactory("textElement", new PageElementFactory(TextElement.class)); ... registerFactory("chart", new ChartFactory()); }
  • 64. …And It’s Ready to use in the Builder tableRowElement { tableColumn (columnSpan:2) { chart dept.fundPercent } tableColumn { chart allFundTotal } }
  • 65. Is a DSL Right for Me? Are the people who maintain your project different than those who initially developed it? DSLs give the “gift of documentation” Is your project part of a domain? Using Dynamic Languages like Groovy make DSLs more Painless
  • 66. Resources Writing an Adventure game DSL in LISP: http://www.lisperati.com/casting.html http://groovy.codehaus.org/FactoryBuilderSupport http://docs.codehaus.org/display/GROOVY/MetaBuilder http://docs.codehaus.org/display/GROOVY/Writing+Domain-Specific+Languages http://www.agiledeveloper.com/blog/PermaLink.aspx?guid=ce021e6d-b0c2-4d83-8b69- c163c290983d http://blogs.citytechinc.com/sanderson/ King’s Quest and Zork images taken from Wikipedia.
  • 67. “Groovy is like Gravy, except that it has the „Groove‟ in it.” -Meredith Anderson (5 Year Old)