Slide 1

Slide 1 text

© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission. Groovy, in the light of Java 8 Guillaume Laforge — Groovy project lead / Pivotal @glaforge

Slide 2

Slide 2 text

Stay up-to-date Groovy Weekly Newsletter (Every Tuesday) http://beta.groovy-lang.org/groovy-weekly.html 2

Slide 3

Slide 3 text

Stay up-to-date Google+ Groovy Page https://google.com/+groovy 3

Slide 4

Slide 4 text

Stay up-to-date Google+ Groovy Community http://bit.ly/g-community 4

Slide 5

Slide 5 text

© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission. Goal of this talk

Slide 6

Slide 6 text

A recurring question… Do we still need Groovy now that we have Java 8? 6

Slide 7

Slide 7 text

To those who said no… 7

Slide 8

Slide 8 text

But more precisely… • Will Java lambdas replace Groovy closures? • What are the differences between them? ! • Will Groovy support all the new Java 8 language constructs? • lambdas, default methods, method references… ! • How Groovy developers can benefit from Java 8? ! • What does Groovy offer beyond Java 8? 8

Slide 9

Slide 9 text

What about redundancy? ! ! ! ! ! • Closures • Traits • Truth & null handling • Functional with collections • Method closures ! ! ! ! ! ! ! • Lambdas • Default methods • Optional • Stream API • Method references 9

Slide 10

Slide 10 text

© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission. Agenda

Slide 11

Slide 11 text

What we’re going to talk about • What’s new in Java 8 • new syntax constructs • new APIs ! • Similar concepts in Groovy • and how they compare or complement ! • What Groovy offers in addition • beyond Java, Groovy adds its own twist! 11

Slide 12

Slide 12 text

© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission. What’s new in Java 8?

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

This is not a Java 8 crash courses with all the details :-)

Slide 15

Slide 15 text

This is not a Java 8 crash courses with all the details :-) We want to understand the implications with regards to Groovy

Slide 16

Slide 16 text

What’s new in Java 8? ! • New syntax • Streams • Profiles • Security enhancements • JavaFX • Tools • i18n • Date / time API • Nashorn / JavaScript ! ! ! • Pack200 • IO / NIO improvements • New utility classes • JDBC • Networking • Concurrency • JAXP • Hotspot • Java Mission Control 14

Slide 17

Slide 17 text

What’s new in Java 8? ! • New syntax • Streams • Profiles • Security enhancements • JavaFX • Tools • i18n • Date / time API • Nashorn / JavaScript ! ! ! • Pack200 • IO / NIO improvements • New utility classes • JDBC • Networking • Concurrency • JAXP • Hotspot • Java Mission Control 14 Great concise resource with the whole list: http://bit.ly/new-in-java-8

Slide 18

Slide 18 text

© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission. Support the new Java 8 language features New Java 8 syntax

Slide 19

Slide 19 text

New Java 8 syntax constructs • Lambda expressions • Method references • Static & default methods in interfaces • Repeating annotations • Annotations on types • Improved type inference • Method parameter reflection 16

Slide 20

Slide 20 text

New Java 8 syntax constructs • Lambda expressions • Method references • Static & default methods in interfaces • Repeating annotations • Annotations on types • Improved type inference • Method parameter reflection 17

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

Lambda expressions 19 double highestScore = students .filter(Student s -> s.getGradYear() == 2011) .map(Student s -> s.getScore()) .max();

Slide 25

Slide 25 text

Lambda expressions 20 double highestScore = students .filter(Student s -> s.getGradYear() == 2011) .map(Student s -> s.getScore()) .max();

Slide 26

Slide 26 text

Lambda expressions 20 double highestScore = students .filter(Student s -> s.getGradYear() == 2011) .map(Student s -> s.getScore()) .max(); Coercion into a Predicate « functional interface »

Slide 27

Slide 27 text

Lambda expressions 20 double highestScore = students .filter(Student s -> s.getGradYear() == 2011) .map(Student s -> s.getScore()) .max(); There’s no function type!

Slide 28

Slide 28 text

Lambda expressions 20 double highestScore = students .filter(Student s -> s.getGradYear() == 2011) .map(Student s -> s.getScore()) .max(); Pipeline being built Single pass!

Slide 29

Slide 29 text

Lambda expressions 20 double highestScore = students .filter(Student s -> s.getGradYear() == 2011) .map(Student s -> s.getScore()) .max();

Slide 30

Slide 30 text

Lambda expressions 21 double highestScore = students .findAll { it.gradYear == 2011 } .collect { it.score} .max()

Slide 31

Slide 31 text

Lambda expressions 21 double highestScore = students .findAll { it.gradYear == 2011 } .collect { it.score} .max() In Groovy, that would be…

Slide 32

Slide 32 text

Lambda expressions 21 double highestScore = students .findAll { it.gradYear == 2011 } .collect { it.score} .max() Drawback: intermediary data structures Unless you use iterator variants

Slide 33

Slide 33 text

Lambda expressions 21 double highestScore = students .findAll { it.gradYear == 2011 } .collect { it.score} .max()

Slide 34

Slide 34 text

The various lambda syntaxes 22 String name -> name.length() ! (int left, int right) -> left + right ! (String left, String sep, String right) -> { System.out.println(left + sep + right) }

Slide 35

Slide 35 text

The various lambda syntaxes 22 String name -> name.length() ! (int left, int right) -> left + right ! (String left, String sep, String right) -> { System.out.println(left + sep + right) } One parameter: no parens Expression on right: no curly

Slide 36

Slide 36 text

The various lambda syntaxes 22 String name -> name.length() ! (int left, int right) -> left + right ! (String left, String sep, String right) -> { System.out.println(left + sep + right) } Parentheses required for more that one parameter

Slide 37

Slide 37 text

The various lambda syntaxes 22 String name -> name.length() ! (int left, int right) -> left + right ! (String left, String sep, String right) -> { System.out.println(left + sep + right) } Statements require curly braces Return keyword required if non-void returning

Slide 38

Slide 38 text

The various lambda syntaxes 22 String name -> name.length() ! (int left, int right) -> left + right ! (String left, String sep, String right) -> { System.out.println(left + sep + right) }

Slide 39

Slide 39 text

The various lambda syntaxes 23 name -> name.length() ! (left, right) -> left + right ! (left, sep, right) -> { System.out.println(left + sep + right) }

Slide 40

Slide 40 text

The various lambda syntaxes 23 name -> name.length() ! (left, right) -> left + right ! (left, sep, right) -> { System.out.println(left + sep + right) } Clever type inference can help get rid of parameter type declarations

Slide 41

Slide 41 text

Closures vs lambdas 24 IntStream.range(1, 100).forEach(s -> System.out.println(s)); ! Files.lines(Paths.get('README.adoc')) .map(it -> it.toUpperCase()) .forEach(it -> System.out.println(it));

Slide 42

Slide 42 text

Closures vs lambdas 24 IntStream.range(1, 100).forEach(s -> System.out.println(s)); ! Files.lines(Paths.get('README.adoc')) .map(it -> it.toUpperCase()) .forEach(it -> System.out.println(it)); IntStream.range(1, 100).forEach { println it } ! Files.lines(Paths.get('README.adoc')) .map { it.toUpperCase() } .forEach { println it }

Slide 43

Slide 43 text

Closures vs lambdas 24 IntStream.range(1, 100).forEach(s -> System.out.println(s)); ! Files.lines(Paths.get('README.adoc')) .map(it -> it.toUpperCase()) .forEach(it -> System.out.println(it)); IntStream.range(1, 100).forEach { println it } ! Files.lines(Paths.get('README.adoc')) .map { it.toUpperCase() } .forEach { println it } Use Groovy closures wherever you pass lambdas in Java 8

Slide 44

Slide 44 text

Closures vs lambdas 24 IntStream.range(1, 100).forEach(s -> System.out.println(s)); ! Files.lines(Paths.get('README.adoc')) .map(it -> it.toUpperCase()) .forEach(it -> System.out.println(it)); IntStream.range(1, 100).forEach { println it } ! Files.lines(Paths.get('README.adoc')) .map { it.toUpperCase() } .forEach { println it } Groovy coerces to SAM types (Single Abstract Method)

Slide 45

Slide 45 text

Closures vs lambdas 24 IntStream.range(1, 100).forEach(s -> System.out.println(s)); ! Files.lines(Paths.get('README.adoc')) .map(it -> it.toUpperCase()) .forEach(it -> System.out.println(it)); IntStream.range(1, 100).forEach { println it } ! Files.lines(Paths.get('README.adoc')) .map { it.toUpperCase() } .forEach { println it }

Slide 46

Slide 46 text

Beyond: Closure default parameters 25 def mult = { int a, int b = 10 -> a * b } ! assert mult(2, 3) == 6 assert mult(5) == 50

Slide 47

Slide 47 text

Beyond: Closure default parameters 25 def mult = { int a, int b = 10 -> a * b } ! assert mult(2, 3) == 6 assert mult(5) == 50 Default value if the parameter is not specified

Slide 48

Slide 48 text

Beyond: Duck typing polymorphism 26 def adder = { a, b -> a + b } ! assert adder(100, 200) == 300 assert adder('X', 'Y') == 'XY'

Slide 49

Slide 49 text

Beyond: Duck typing polymorphism 26 def adder = { a, b -> a + b } ! assert adder(100, 200) == 300 assert adder('X', 'Y') == 'XY' Works both for numbers and for strings

Slide 50

Slide 50 text

Builders

Slide 51

Slide 51 text

Builders What would Java lambda builders look like?

Slide 52

Slide 52 text

Builders What would Java lambda builders look like? Aren’t Groovy builders more powerful?

Slide 53

Slide 53 text

Lambda-based builders uglier and less powerful 28 MarkupBuilder pom = new XmlMarkupBuilder() pom.el("modelVersion", "4.0.0"); pom.el("groupId", "com.github"); pom.el("artifactId", "lambda-builder"); pom.el("version", "1.0-SNAPSHOT"); pom.el("dependencies", () -> { pom.el("dependency", () -> { pom.el("groupId", "junit"); pom.el("artifactId", "junit"); pom.elx("version", "4.11"); }); pom.el("dependency", () -> { pom.el("groupId", "commons-beanutils"); pom.el("artifactId", "commons-beanutils"); pom.elx("version", "1.7.0"); }); });

Slide 54

Slide 54 text

Lambda-based builders uglier and less powerful 28 MarkupBuilder pom = new XmlMarkupBuilder() pom.el("modelVersion", "4.0.0"); pom.el("groupId", "com.github"); pom.el("artifactId", "lambda-builder"); pom.el("version", "1.0-SNAPSHOT"); pom.el("dependencies", () -> { pom.el("dependency", () -> { pom.el("groupId", "junit"); pom.el("artifactId", "junit"); pom.elx("version", "4.11"); }); pom.el("dependency", () -> { pom.el("groupId", "commons-beanutils"); pom.el("artifactId", "commons-beanutils"); pom.elx("version", "1.7.0"); }); }); Repeated « pom »: No delegate like in Groovy’s closures

Slide 55

Slide 55 text

Lambda-based builders uglier and less powerful 28 MarkupBuilder pom = new XmlMarkupBuilder() pom.el("modelVersion", "4.0.0"); pom.el("groupId", "com.github"); pom.el("artifactId", "lambda-builder"); pom.el("version", "1.0-SNAPSHOT"); pom.el("dependencies", () -> { pom.el("dependency", () -> { pom.el("groupId", "junit"); pom.el("artifactId", "junit"); pom.elx("version", "4.11"); }); pom.el("dependency", () -> { pom.el("groupId", "commons-beanutils"); pom.el("artifactId", "commons-beanutils"); pom.elx("version", "1.7.0"); }); }); Zero-arg lamdas not as lean

Slide 56

Slide 56 text

Lambda-based builders uglier and less powerful 28 MarkupBuilder pom = new XmlMarkupBuilder() pom.el("modelVersion", "4.0.0"); pom.el("groupId", "com.github"); pom.el("artifactId", "lambda-builder"); pom.el("version", "1.0-SNAPSHOT"); pom.el("dependencies", () -> { pom.el("dependency", () -> { pom.el("groupId", "junit"); pom.el("artifactId", "junit"); pom.elx("version", "4.11"); }); pom.el("dependency", () -> { pom.el("groupId", "commons-beanutils"); pom.el("artifactId", "commons-beanutils"); pom.elx("version", "1.7.0"); }); }); Generic method + string: No dynamic method

Slide 57

Slide 57 text

Neater Groovy builder! 29 def pom = new PomBuilder().project { modelVersion "4.0.0" groupId "com.github" artifactId "lambda-builder" version "1.0-SNAPSHOT" dependencies { dependency { groupId "junit" artifactId "junit" version "4.11" } dependency { groupId "commons-beanutils" artifactId "commons-beanutils" version "1.7.0" } } }

Slide 58

Slide 58 text

Neater Groovy builder! 29 def pom = new PomBuilder().project { modelVersion "4.0.0" groupId "com.github" artifactId "lambda-builder" version "1.0-SNAPSHOT" dependencies { dependency { groupId "junit" artifactId "junit" version "4.11" } dependency { groupId "commons-beanutils" artifactId "commons-beanutils" version "1.7.0" } } }

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

Aren’t Groovy builders more readable and lean?

Slide 61

Slide 61 text

To those who said no… 31

Slide 62

Slide 62 text

Memoization

Slide 63

Slide 63 text

Closure and method memoization 33 def fib2 = { long n -> if (n < 2) 1 else call(n - 1) + call(n - 2) }.memoize()

Slide 64

Slide 64 text

Closure and method memoization 33 def fib2 = { long n -> if (n < 2) 1 else call(n - 1) + call(n - 2) }.memoize() Closures: memoize()

Slide 65

Slide 65 text

Closure and method memoization 33 @Memoized long fib(long n) { if (n < 2) 1 else fib(n - 1) + fib(n - 2) } def fib2 = { long n -> if (n < 2) 1 else call(n - 1) + call(n - 2) }.memoize() Closures: memoize()

Slide 66

Slide 66 text

Closure and method memoization 33 @Memoized long fib(long n) { if (n < 2) 1 else fib(n - 1) + fib(n - 2) } def fib2 = { long n -> if (n < 2) 1 else call(n - 1) + call(n - 2) }.memoize() Closures: memoize() Methods: memoization AST transformation

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

Tail recursion

Slide 69

Slide 69 text

Closure and method tail recursion 35 def fact = { n, accu = 1G -> if (n < 2) accu else fact.trampoline(n - 1, n * accu) }.trampoline()

Slide 70

Slide 70 text

Closure and method tail recursion 35 def fact = { n, accu = 1G -> if (n < 2) accu else fact.trampoline(n - 1, n * accu) }.trampoline() Closures: Tail recursion with trampoline()

Slide 71

Slide 71 text

Closure and method tail recursion 35 @groovy.transform.TailRecursive def fact(n, accu = 1G) { if (n < 2) accu else fact(n - 1, n * accu) } def fact = { n, accu = 1G -> if (n < 2) accu else fact.trampoline(n - 1, n * accu) }.trampoline() Closures: Tail recursion with trampoline()

Slide 72

Slide 72 text

Closure and method tail recursion 35 @groovy.transform.TailRecursive def fact(n, accu = 1G) { if (n < 2) accu else fact(n - 1, n * accu) } def fact = { n, accu = 1G -> if (n < 2) accu else fact.trampoline(n - 1, n * accu) }.trampoline() Closures: Tail recursion with trampoline() Methods: Tail recursion with @TailRecursive transformation

Slide 73

Slide 73 text

Method references 36 button.setOnAction(event -> System.out.println(event));

Slide 74

Slide 74 text

Method references 36 button.setOnAction(event -> System.out.println(event)); button.setOnAction(System.out::println);

Slide 75

Slide 75 text

Method references — 3 main cases 37 instance::instanceMethod ! SomeClass::staticMethod ! SomeClass::instanceMethod

Slide 76

Slide 76 text

Method references — 3 main cases 37 instance::instanceMethod ! SomeClass::staticMethod ! SomeClass::instanceMethod Not covered by Groovy method closures yet!

Slide 77

Slide 77 text

Groovy’s method closure 38 instance.&instanceMethod ! SomeClass.&staticMethod ! SomeClass.&instanceMethod

Slide 78

Slide 78 text

Groovy’s method closure 38 instance.&instanceMethod ! SomeClass.&staticMethod ! SomeClass.&instanceMethod Choices: Adopt :: Deprecate .& Enhance .&

Slide 79

Slide 79 text

Static methods in interfaces • You can put static utility methods in interfaces, 
 instead of in companion classes (like « Collections ») 39 public interface Stream { // ... static Stream empty() { return new Stream { ... } } }

Slide 80

Slide 80 text

Default methods in interfaces • Define default behavior • possibly to enrich existing interfaces 40 public interface Stream { // ... default Builder add(T t) { ... } }

Slide 81

Slide 81 text

Groovy Traits • Like interfaces, but with method bodies • similar to Java 8 interface default methods • Elegant way to compose behavior • multiple inheritance without the « diamond » problem • Traits can also be stateful • traits can have properties like normal classes • Compatible with static typing and static compilation • class methods from traits also visible from Java classes • Also possible to implement traits at runtime 41

Slide 82

Slide 82 text

Simple trait 42 trait FlyingAbility { String fly() { "I'm flying!" } } ! class Bird implements FlyingAbility {} def b = new Bird() ! assert b.fly() == "I'm flying!"

Slide 83

Slide 83 text

Trait with state 43 trait Named { String name } ! class Bird implements Named {} def b = new Bird(name: 'Colibri') ! assert b.name == 'Colibri'

Slide 84

Slide 84 text

Multiple inheritance 44 trait KiteSurfer { String surf() { 'kite' } } ! trait WebSurfer { String surf() { 'web' } } ! class Person { String name } ! class Hipster extends Person implements KiteSurfer, WebSurfer {} ! def h = new Hipster() assert h.surf() == 'web'

Slide 85

Slide 85 text

To know all about traits! 45 Rethinking API 
 design with traits by Cédric Champeau Tue 2:30pm / Trinity 3

Slide 86

Slide 86 text

Annotations on types Repeating annotations

Slide 87

Slide 87 text

Repeating annotations 47 @Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... }

Slide 88

Slide 88 text

Repeating annotations 47 @Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... } @Schedule annotation repeated twice

Slide 89

Slide 89 text

Repeating annotations 47 @Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... } @Repeatable(Schedules.class) public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12; }

Slide 90

Slide 90 text

Repeating annotations 47 @Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... } @Repeatable(Schedules.class) public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12; } Container annotation for the repeated annotationd

Slide 91

Slide 91 text

Repeating annotations 47 @Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... } @Repeatable(Schedules.class) public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12; } public @interface Schedules { Schedule[] value(); }

Slide 92

Slide 92 text

Repeating annotations 47 @Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... } @Repeatable(Schedules.class) public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12; } public @interface Schedules { Schedule[] value(); } The container annotation iteself

Slide 93

Slide 93 text

Repeating annotations 47 @Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... } @Repeatable(Schedules.class) public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12; } public @interface Schedules { Schedule[] value(); }

Slide 94

Slide 94 text

Repeating annotations 47 @Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... } @Repeatable(Schedules.class) public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12; } public @interface Schedules { Schedule[] value(); } Not yet supported

Slide 95

Slide 95 text

Repeating annotations 47 @Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... } @Repeatable(Schedules.class) public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12; } public @interface Schedules { Schedule[] value(); }

Slide 96

Slide 96 text

Annotations on types • Everywhere you can put a type, you can put an annotation 48 @NonNull String name; ! email = (@Email String) input; ! List<@NonNull String> names; ! new @Interned MyObject(); ! void monitorTemperature() throws @Critical TemperatureException { ... } ! class UnmodifiableList implements @Readonly List<@Readonly T> {...}

Slide 97

Slide 97 text

Annotations on types • Everywhere you can put a type, you can put an annotation 48 @NonNull String name; ! email = (@Email String) input; ! List<@NonNull String> names; ! new @Interned MyObject(); ! void monitorTemperature() throws @Critical TemperatureException { ... } ! class UnmodifiableList implements @Readonly List<@Readonly T> {...} Not yet supported

Slide 98

Slide 98 text

Annotations on types • Everywhere you can put a type, you can put an annotation 48 @NonNull String name; ! email = (@Email String) input; ! List<@NonNull String> names; ! new @Interned MyObject(); ! void monitorTemperature() throws @Critical TemperatureException { ... } ! class UnmodifiableList implements @Readonly List<@Readonly T> {...} Imagine the potential for targets for local AST transformations?

Slide 99

Slide 99 text

Annotations on types • Everywhere you can put a type, you can put an annotation 48 @NonNull String name; ! email = (@Email String) input; ! List<@NonNull String> names; ! new @Interned MyObject(); ! void monitorTemperature() throws @Critical TemperatureException { ... } ! class UnmodifiableList implements @Readonly List<@Readonly T> {...}

Slide 100

Slide 100 text

Groovy compile-time meta-annotations 49 @Service @Transactional class MyTransactionalService {}

Slide 101

Slide 101 text

Groovy compile-time meta-annotations 49 @Service @Transactional class MyTransactionalService {} import groovy.transform.AnnotationCollector ! @Service @Transactional @AnnotationCollector public @interface TransactionalService {}

Slide 102

Slide 102 text

Groovy compile-time meta-annotations 49 import groovy.transform.AnnotationCollector ! @Service @Transactional @AnnotationCollector public @interface TransactionalService {} ! @TransactionalService class MyTransactionalService {}

Slide 103

Slide 103 text

Groovy compile-time meta-annotations 49 import groovy.transform.AnnotationCollector ! @Service @Transactional @AnnotationCollector public @interface TransactionalService {} ! @TransactionalService class MyTransactionalService {} Can handle parameters (even conflicting), or you can create your own « processor »

Slide 104

Slide 104 text

Groovy compile-time meta-annotations 49 import groovy.transform.AnnotationCollector ! @Service @Transactional @AnnotationCollector public @interface TransactionalService {} ! @TransactionalService class MyTransactionalService {}

Slide 105

Slide 105 text

© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission. New Java 8 APIs

Slide 106

Slide 106 text

Date and Time API

Slide 107

Slide 107 text

Date / Time API 52 LocalDate.now(); today.with(TemporalAdjusters.lastDayOfMonth()).minusDays(2); ! LocalDate.of(2012, Month.MAY, 14); dateOfBirth.plusYears(1); ! LocalDate date = LocalDate.of(2000, Month.NOVEMBER, 20); LocalDate nextWed = date.with(TemporalAdjusters.next(DayOfWeek.WEDNESDAY));

Slide 108

Slide 108 text

Date / Time API 52 LocalDate.now(); today.with(TemporalAdjusters.lastDayOfMonth()).minusDays(2); ! LocalDate.of(2012, Month.MAY, 14); dateOfBirth.plusYears(1); ! LocalDate date = LocalDate.of(2000, Month.NOVEMBER, 20); LocalDate nextWed = date.with(TemporalAdjusters.next(DayOfWeek.WEDNESDAY)); Groovy could add some operator overloading?

Slide 109

Slide 109 text

Date / Time API 52 LocalDate.now(); today.with(TemporalAdjusters.lastDayOfMonth()).minusDays(2); ! LocalDate.of(2012, Month.MAY, 14); dateOfBirth.plusYears(1); ! LocalDate date = LocalDate.of(2000, Month.NOVEMBER, 20); LocalDate nextWed = date.with(TemporalAdjusters.next(DayOfWeek.WEDNESDAY));

Slide 110

Slide 110 text

Groovy’s date / time handling 53 import static java.util.Calendar.* import groovy.time.* import org.codehaus.groovy.runtime.TimeCategory ! def cal = Calendar.instance cal.set(year: 2010, month: JULY, date: 9) ! assert FRIDAY == cal[DAY_OF_WEEK] ! use (TimeCategory) { 2.years + 3.months + 15.days + 23.minutes + 2.seconds ! 1.week - 1.day new Date() + 6.days ! 3.days.ago new Date() - 3 }

Slide 111

Slide 111 text

Groovy’s date / time handling 53 import static java.util.Calendar.* import groovy.time.* import org.codehaus.groovy.runtime.TimeCategory ! def cal = Calendar.instance cal.set(year: 2010, month: JULY, date: 9) ! assert FRIDAY == cal[DAY_OF_WEEK] ! use (TimeCategory) { 2.years + 3.months + 15.days + 23.minutes + 2.seconds ! 1.week - 1.day new Date() + 6.days ! 3.days.ago new Date() - 3 } Groovy could provide the same for Date / Time

Slide 112

Slide 112 text

Groovy’s date / time handling 53 import static java.util.Calendar.* import groovy.time.* import org.codehaus.groovy.runtime.TimeCategory ! def cal = Calendar.instance cal.set(year: 2010, month: JULY, date: 9) ! assert FRIDAY == cal[DAY_OF_WEEK] ! use (TimeCategory) { 2.years + 3.months + 15.days + 23.minutes + 2.seconds ! 1.week - 1.day new Date() + 6.days ! 3.days.ago new Date() - 3 }

Slide 113

Slide 113 text

Stream API

Slide 114

Slide 114 text

map map map map map reduce reduce reduce reduce reduce

Slide 115

Slide 115 text

map map map map map reduce reduce reduce reduce reduce Map / filter / reduce explained to your 6 year old

Slide 116

Slide 116 text

map map map map map reduce reduce reduce reduce reduce

Slide 117

Slide 117 text

Stream 56 persons.stream() .filter( p -> p.getAge() < 18 ) .map( p -> p.getName().toUpperCase() ) .sorted() .collect(Collectors.joining(", "));

Slide 118

Slide 118 text

Groovy’s functional style with the GDK methods 57 persons .findAll { it.getAge() < 18 } .collect { it.name.toUpperCase() } .sort() .joining(", ")

Slide 119

Slide 119 text

Groovy using streams too! 58 persons.stream() .filter { it.age < 18 } .map { it.name.toUpperCase() } .sorted() .collect(Collectors.joining(", "))

Slide 120

Slide 120 text

Groovy using streams too! 58 persons.stream() .filter { it.age < 18 } .map { it.name.toUpperCase() } .sorted() .collect(Collectors.joining(", ")) Leveraging closure to « SAM » type coercion

Slide 121

Slide 121 text

Groovy using streams too! 58 persons.stream() .filter { it.age < 18 } .map { it.name.toUpperCase() } .sorted() .collect(Collectors.joining(", "))

Slide 122

Slide 122 text

Optional 59 Optional maybeName = Optional.of("Guillaume"); ! String result = maybeName.orElse("unknown") ! if (maybeName.ifPresent()) { System.out.println(maybeName.get()); } else { System.out.println("unknown"); }

Slide 123

Slide 123 text

Optional 59 Optional maybeName = Optional.of("Guillaume"); ! String result = maybeName.orElse("unknown") ! if (maybeName.ifPresent()) { System.out.println(maybeName.get()); } else { System.out.println("unknown"); } Wrap something that can be potentially null

Slide 124

Slide 124 text

Optional 59 Optional maybeName = Optional.of("Guillaume"); ! String result = maybeName.orElse("unknown") ! if (maybeName.ifPresent()) { System.out.println(maybeName.get()); } else { System.out.println("unknown"); }

Slide 125

Slide 125 text

Optional 59 Optional maybeName = Optional.of("Guillaume"); ! String result = maybeName.orElse("unknown") ! if (maybeName.ifPresent()) { System.out.println(maybeName.get()); } else { System.out.println("unknown"); } Force handling the null value

Slide 126

Slide 126 text

Optional 59 Optional maybeName = Optional.of("Guillaume"); ! String result = maybeName.orElse("unknown") ! if (maybeName.ifPresent()) { System.out.println(maybeName.get()); } else { System.out.println("unknown"); }

Slide 127

Slide 127 text

No content

Slide 128

Slide 128 text

You know you can customize the truth in Groovy?

Slide 129

Slide 129 text

You know you can customize the truth in Groovy? Just implement a custom asBoolean() method!

Slide 130

Slide 130 text

The law of Groovy Truth 61

Slide 131

Slide 131 text

The law of Groovy Truth 61 Everything that’s null, empty, zero-sized, equal to zero is false

Slide 132

Slide 132 text

The law of Groovy Truth 61 Everything that’s null, empty, zero-sized, equal to zero is false Otherwise, it’s true!

Slide 133

Slide 133 text

?:

Slide 134

Slide 134 text

Groovy truth, null handling, Elvis with Optional 63 def maybeName = Optional.of("Guillaume") ! def result = maybeName ?: "unknown" ! if (maybeName) { println maybeName.get() } else { println "unknown" } ! maybeName?.toUpperCase()

Slide 135

Slide 135 text

Groovy truth, null handling, Elvis with Optional 63 def maybeName = Optional.of("Guillaume") ! def result = maybeName ?: "unknown" ! if (maybeName) { println maybeName.get() } else { println "unknown" } ! maybeName?.toUpperCase() Elvis!

Slide 136

Slide 136 text

Groovy truth, null handling, Elvis with Optional 63 def maybeName = Optional.of("Guillaume") ! def result = maybeName ?: "unknown" ! if (maybeName) { println maybeName.get() } else { println "unknown" } ! maybeName?.toUpperCase() Groovy Truth!

Slide 137

Slide 137 text

Groovy truth, null handling, Elvis with Optional 63 def maybeName = Optional.of("Guillaume") ! def result = maybeName ?: "unknown" ! if (maybeName) { println maybeName.get() } else { println "unknown" } ! maybeName?.toUpperCase() Safe navigation

Slide 138

Slide 138 text

Groovy truth, null handling, Elvis with Optional 63 def maybeName = Optional.of("Guillaume") ! def result = maybeName ?: "unknown" ! if (maybeName) { println maybeName.get() } else { println "unknown" } ! maybeName?.toUpperCase() Interesting discussions on the mailing-list to further enhance usage of Optional from Groovy

Slide 139

Slide 139 text

Groovy truth, null handling, Elvis with Optional 63 def maybeName = Optional.of("Guillaume") ! def result = maybeName ?: "unknown" ! if (maybeName) { println maybeName.get() } else { println "unknown" } ! maybeName?.toUpperCase() Not Yet Implemented

Slide 140

Slide 140 text

Groovy truth, null handling, Elvis with Optional 63 def maybeName = Optional.of("Guillaume") ! def result = maybeName ?: "unknown" ! if (maybeName) { println maybeName.get() } else { println "unknown" } ! maybeName?.toUpperCase()

Slide 141

Slide 141 text

Nashorn

Slide 142

Slide 142 text

Nashorn Speak German?

Slide 143

Slide 143 text

Call JavaScript from Groovy with JSR-223 65 def manager = new ScriptEngineManager() def engine = manager.getEngineByName("nashorn") ! assert engine.eval("{} + []") == 0

Slide 144

Slide 144 text

Call JavaScript from Groovy with JSR-223 65 def manager = new ScriptEngineManager() def engine = manager.getEngineByName("nashorn") ! assert engine.eval("{} + []") == 0 Sane JavaScript logic :-)

Slide 145

Slide 145 text

GrooScript — http://grooscript.org/ 66 JS

Slide 146

Slide 146 text

GrooScript — http://grooscript.org/ 66 JS Gradle & Grails plugins available Examples available with Ratpack, Node.JS…

Slide 147

Slide 147 text

Turtles all the way down!

Slide 148

Slide 148 text

Turtles all the way down! Full Groovy!

Slide 149

Slide 149 text

Turtles all the way down! Full Groovy! Back to front

Slide 150

Slide 150 text

Turtles all the way down! Full Groovy! Back to front Front to back

Slide 151

Slide 151 text

No content

Slide 152

Slide 152 text

Jackson Pollock

Slide 153

Slide 153 text

JavaFx… ! ! …the new Swing

Slide 154

Slide 154 text

GroovyFX 70 import javafx.scene.Scene import static groovyx.javafx.GroovyFX.start ! def chamber = ["5 Stelle": 108, "Italia.\nBene commune": 340, "Con Monti per l'Italia": 45, "Berlusconi": 124, "others": 4] ! start { stage(title: 'Italian chamber of Deputies', width: 1024, height: 700, visible: true) { Scene s = scene { tilePane { barChart(barGap: 10, categoryGap: 20, title: "Italy's election in February 2013") { series(name: 'Chamber (seats)', data: ['5 Stelle', 17.5, 'Monti', 7.8, 'Bene commune', 55, 'Berlusconi', 19.9]) series(name: 'Senate (seats)', data: ['5 Stelle', 17.4, 'Monti', 6.1, 'Bene commune', 39.1, 'Berlusconi', 37.1]) } pieChart(data: chamber, title: "Chamber of Deputies") } } s.stylesheets.add("Chart.css") } }

Slide 155

Slide 155 text

GroovyFX 70 import javafx.scene.Scene import static groovyx.javafx.GroovyFX.start ! def chamber = ["5 Stelle": 108, "Italia.\nBene commune": 340, "Con Monti per l'Italia": 45, "Berlusconi": 124, "others": 4] ! start { stage(title: 'Italian chamber of Deputies', width: 1024, height: 700, visible: true) { Scene s = scene { tilePane { barChart(barGap: 10, categoryGap: 20, title: "Italy's election in February 2013") { series(name: 'Chamber (seats)', data: ['5 Stelle', 17.5, 'Monti', 7.8, 'Bene commune', 55, 'Berlusconi', 19.9]) series(name: 'Senate (seats)', data: ['5 Stelle', 17.4, 'Monti', 6.1, 'Bene commune', 39.1, 'Berlusconi', 37.1]) } pieChart(data: chamber, title: "Chamber of Deputies") } } s.stylesheets.add("Chart.css") } } Groovy builder to the rescue!

Slide 156

Slide 156 text

© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission. Beyond Java

Slide 157

Slide 157 text

© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission. Easy scripting

Slide 158

Slide 158 text

Scripting 73 @Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* ! def src = new File('html').toPath() def dst = new File('asciidoc').toPath() ! def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) ! src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc" .execute().waitFor() tmpHtml.delete() } } AsciiDoctor

Slide 159

Slide 159 text

Ant scripting 74 @Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* ! def src = new File('html').toPath() def dst = new File('asciidoc').toPath() ! def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) ! src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td'

Slide 160

Slide 160 text

Ant scripting 74 @Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* ! def src = new File('html').toPath() def dst = new File('asciidoc').toPath() ! def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) ! src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' Use @Grab to download the htmlcleaner library

Slide 161

Slide 161 text

Ant scripting 74 @Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* ! def src = new File('html').toPath() def dst = new File('asciidoc').toPath() ! def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) ! src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td'

Slide 162

Slide 162 text

Ant scripting 74 @Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* ! def src = new File('html').toPath() def dst = new File('asciidoc').toPath() ! def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) ! src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' Setup HtmlCleaner

Slide 163

Slide 163 text

Ant scripting 74 @Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* ! def src = new File('html').toPath() def dst = new File('asciidoc').toPath() ! def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) ! src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td'

Slide 164

Slide 164 text

Ant scripting 74 @Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* ! def src = new File('html').toPath() def dst = new File('asciidoc').toPath() ! def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) ! src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' Recursively find all the files to transform

Slide 165

Slide 165 text

Ant scripting 74 @Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* ! def src = new File('html').toPath() def dst = new File('asciidoc').toPath() ! def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) ! src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td'

Slide 166

Slide 166 text

Ant scripting 75 def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) ! src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" )

Slide 167

Slide 167 text

Ant scripting 75 def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) ! src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) Clean the HTML

Slide 168

Slide 168 text

Ant scripting 75 def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) ! src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" )

Slide 169

Slide 169 text

Ant scripting 75 def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) ! src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) Some more clean-up

Slide 170

Slide 170 text

Ant scripting 75 def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) ! src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" )

Slide 171

Slide 171 text

Ant scripting 76 def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc" .execute().waitFor() tmpHtml.delete() } }

Slide 172

Slide 172 text

Ant scripting 76 def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc" .execute().waitFor() tmpHtml.delete() } } Write the result

Slide 173

Slide 173 text

Ant scripting 76 def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc" .execute().waitFor() tmpHtml.delete() } }

Slide 174

Slide 174 text

Ant scripting 76 def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc" .execute().waitFor() tmpHtml.delete() } } Call an external process to convert and wait for its execution

Slide 175

Slide 175 text

Ant scripting 76 def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc" .execute().waitFor() tmpHtml.delete() } }

Slide 176

Slide 176 text

© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission. Android

Slide 177

Slide 177 text

Android support • You can use Groovy to code Android apps! • use Groovy 2.4.0-beta-1+ • prefer @CompileStatic ! • Two great posts to get started: • http://melix.github.io/blog/2014/06/grooid.html • http://melix.github.io/blog/2014/06/grooid2.html 78

Slide 178

Slide 178 text

New York Times — Getting Groovy with Android 79

Slide 179

Slide 179 text

New York Times — Getting Groovy with Android 79 http://bit.ly/nyt-groovy

Slide 180

Slide 180 text

What does NYT likes about Groovy on Android? • No Java 8, no lambda on Android… 80 Func0 func = new Func0() { @Override public String call() { return "my content"; } }; Async.start(func);

Slide 181

Slide 181 text

What does NYT likes about Groovy on Android? • No Java 8, no lambda on Android… 81 ! ! ! ! ! ! Async.start { "my content" }

Slide 182

Slide 182 text

What does NYT likes about Groovy on Android? • No Java 8, no lambda on Android… 81 ! ! ! ! ! ! Async.start { "my content" } Good bye annonymous inner classes!

Slide 183

Slide 183 text

What does NYT likes about Groovy on Android? ! • Groovy code more concise and more readable ! • but just as type-safe as needed!
 (with @TypeChecked) ! • but just as fast as needed!
 (with @CompileStatic) 82

Slide 184

Slide 184 text

Android support 83

Slide 185

Slide 185 text

Android support 83

Slide 186

Slide 186 text

Android support 83 Source code available: https://github.com/melix/gr8confagenda

Slide 187

Slide 187 text

To know all about Android support 84 Groovy & Android, 
 a winning pair? by Cédric Champeau Thu 12:45pm / Trinity 3

Slide 188

Slide 188 text

© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission. A rich Groovy ecosystem

Slide 189

Slide 189 text

Grails — web framework 86 @Grab("com.h2database:h2:1.3.173") import grails.persistence.* ! @Entity @Resource(uri='/books') class Book { String title }

Slide 190

Slide 190 text

Grails — web framework 86 @Grab("com.h2database:h2:1.3.173") import grails.persistence.* ! @Entity @Resource(uri='/books') class Book { String title } One class, one command, and you’ve got a full REST CRUD application!

Slide 191

Slide 191 text

Ratpack — web framework 87 @GrabResolver("https://oss.jfrog.org/artifactory/repo") @Grab("org.ratpack-framework:ratpack-groovy:0.9.8") import static org.ratpackframework.groovy.RatpackScript.ratpack import static org.ratpackframework.groovy.Template.groovyTemplate ! ratpack { handlers { get { response.send "Welcome!" } ! get("date") { render groovyTemplate("date.html") } ! assets "public" } }

Slide 192

Slide 192 text

Ratpack — web framework 87 @GrabResolver("https://oss.jfrog.org/artifactory/repo") @Grab("org.ratpack-framework:ratpack-groovy:0.9.8") import static org.ratpackframework.groovy.RatpackScript.ratpack import static org.ratpackframework.groovy.Template.groovyTemplate ! ratpack { handlers { get { response.send "Welcome!" } ! get("date") { render groovyTemplate("date.html") } ! assets "public" } } Lightweight Netty-based web application toolkit

Slide 193

Slide 193 text

@ArtifactProviderFor(GriffonModel) class ConsoleModel { String scriptSource @Observable Object scriptResult @Observable boolean enabled = true } Griffon — rich desktop applications 88

Slide 194

Slide 194 text

@ArtifactProviderFor(GriffonModel) class ConsoleModel { String scriptSource @Observable Object scriptResult @Observable boolean enabled = true } Griffon — rich desktop applications 88 Model

Slide 195

Slide 195 text

@ArtifactProviderFor(GriffonModel) class ConsoleModel { String scriptSource @Observable Object scriptResult @Observable boolean enabled = true } Griffon — rich desktop applications 88 Model @ArtifactProviderFor(GriffonController) class ConsoleController { def model ! @Inject Evaluator evaluator ! void executeScript() { model.enabled = false def result try { result = evaluator.evaluate(model.scriptSource) } finally { model.enabled = true model.scriptResult = result } } } Controller

Slide 196

Slide 196 text

@ArtifactProviderFor(GriffonModel) class ConsoleModel { String scriptSource @Observable Object scriptResult @Observable boolean enabled = true } Griffon — rich desktop applications 88 Model @ArtifactProviderFor(GriffonController) class ConsoleController { def model ! @Inject Evaluator evaluator ! void executeScript() { model.enabled = false def result try { result = evaluator.evaluate(model.scriptSource) } finally { model.enabled = true model.scriptResult = result } } } Controller application(title: application.configuration['application.title'], pack: true, locationByPlatform: true, id: 'mainWindow', iconImage: imageIcon('/griffon-icon-48x48.png').image, iconImages: [imageIcon('/griffon-icon-48x48.png').image, imageIcon('/griffon-icon-32x32.png').image, imageIcon('/griffon-icon-16x16.png').image]) { panel(border: emptyBorder(6)) { borderLayout() scrollPane(constraints: CENTER) { textArea(text: bind(target: model, 'scriptSource'), enabled: bind { model.enabled }, columns: 40, rows: 10) } ! hbox(constraints: SOUTH) { button(executeScriptAction) hstrut(5) label('Result:') hstrut(5) textField(editable: false, text: bind { model.scriptResult }) } } } View

Slide 197

Slide 197 text

Spock — unit testing & specification 89 @Grab('org.spockframework:spock-core:0.7-groovy-2.0') import spock.lang.* ! class MathSpec extends Specification { def "maximum of two numbers"() { expect: Math.max(a, b) == c ! where: a | b || c 1 | 3 || 3 7 | 4 || 7 0 | 0 || 0 } }

Slide 198

Slide 198 text

Spock — unit testing & specification 89 @Grab('org.spockframework:spock-core:0.7-groovy-2.0') import spock.lang.* ! class MathSpec extends Specification { def "maximum of two numbers"() { expect: Math.max(a, b) == c ! where: a | b || c 1 | 3 || 3 7 | 4 || 7 0 | 0 || 0 } } Readable & concise expectations

Slide 199

Slide 199 text

Spock — unit testing & specification 89 @Grab('org.spockframework:spock-core:0.7-groovy-2.0') import spock.lang.* ! class MathSpec extends Specification { def "maximum of two numbers"() { expect: Math.max(a, b) == c ! where: a | b || c 1 | 3 || 3 7 | 4 || 7 0 | 0 || 0 } } Readable & concise expectations Awesome data-driven tests with a wiki-like notation

Slide 200

Slide 200 text

Geb — browser automation 90 import geb.Browser ! Browser.drive { go "http://myapp.com/login" ! assert $("h1").text() == "Please Login" ! $("form.login").with { username = "admin" password = "password" login().click() } ! assert $("h1").text() == 
 "Admin Section" }

Slide 201

Slide 201 text

Geb — browser automation 90 import geb.Browser ! Browser.drive { go "http://myapp.com/login" ! assert $("h1").text() == "Please Login" ! $("form.login").with { username = "admin" password = "password" login().click() } ! assert $("h1").text() == 
 "Admin Section" } Drive your browser

Slide 202

Slide 202 text

Geb — browser automation 90 import geb.Browser ! Browser.drive { go "http://myapp.com/login" ! assert $("h1").text() == "Please Login" ! $("form.login").with { username = "admin" password = "password" login().click() } ! assert $("h1").text() == 
 "Admin Section" } Drive your browser JQuery-like selectors

Slide 203

Slide 203 text

Geb — browser automation 90 import geb.Browser ! Browser.drive { go "http://myapp.com/login" ! assert $("h1").text() == "Please Login" ! $("form.login").with { username = "admin" password = "password" login().click() } ! assert $("h1").text() == 
 "Admin Section" } Drive your browser JQuery-like selectors Fill & submit forms

Slide 204

Slide 204 text

Gradle — build automation 91 apply plugin: 'java' apply plugin: 'eclipse' ! sourceCompatibility = 1.5 version = '1.0' jar { manifest { attributes 'Implementation-Title': 'Gradle Quickstart' } } ! repositories { mavenCentral() } ! dependencies { compile ‘commons-collections:commons-collections:3.2’ testCompile 'junit:junit:4.+' } ! uploadArchives { repositories { flatDir { dirs 'repos' } } }

Slide 205

Slide 205 text

Gradle — build automation 91 apply plugin: 'java' apply plugin: 'eclipse' ! sourceCompatibility = 1.5 version = '1.0' jar { manifest { attributes 'Implementation-Title': 'Gradle Quickstart' } } ! repositories { mavenCentral() } ! dependencies { compile ‘commons-collections:commons-collections:3.2’ testCompile 'junit:junit:4.+' } ! uploadArchives { repositories { flatDir { dirs 'repos' } } } Powerful and readable DSL for automating your builds and deployments

Slide 206

Slide 206 text

GPars — concurrency / parallelism / async / … 92 import static groovyx.gpars.actor.Actors.actor ! def decryptor = actor { loop { react { message -> if (message instanceof String) reply message.reverse() else stop() } } } ! def console = actor { decryptor.send 'lellarap si yvoorG' react { println 'Decrypted message: ${it}' decryptor.send false } } ! [decryptor, console]*.join()

Slide 207

Slide 207 text

GPars — concurrency / parallelism / async / … 92 import static groovyx.gpars.actor.Actors.actor ! def decryptor = actor { loop { react { message -> if (message instanceof String) reply message.reverse() else stop() } } } ! def console = actor { decryptor.send 'lellarap si yvoorG' react { println 'Decrypted message: ${it}' decryptor.send false } } ! [decryptor, console]*.join() Actors…

Slide 208

Slide 208 text

GPars — concurrency / parallelism / async / … 92 import static groovyx.gpars.actor.Actors.actor ! def decryptor = actor { loop { react { message -> if (message instanceof String) reply message.reverse() else stop() } } } ! def console = actor { decryptor.send 'lellarap si yvoorG' react { println 'Decrypted message: ${it}' decryptor.send false } } ! [decryptor, console]*.join() Actors… But also: Dataflow concurrency, CSP, agents, concurrent collection processing, fork / join, composable async functions, STM

Slide 209

Slide 209 text

© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission. Groovy modules

Slide 210

Slide 210 text

Groovy modules • Ant scripting • JMX • JSON • Servlet • Swing • SQL • Templating • Testing • XML 94

Slide 211

Slide 211 text

Groovy modules • Ant scripting • JMX • JSON • Servlet • Swing • SQL • Templating • Testing • XML 95

Slide 212

Slide 212 text

Ant + XML 96 def writer = new StringWriter() def mkp = new MarkupBuilder(writer) ! mkp.html { head { title 'Build notification' } body { p 'Your build was successful' } } ! new AntBuilder().mail(mailhost: 'localhost', messagemimetype: 'text/html', subject: 'Build successful') { ! from address: '[email protected]' to address: '[email protected]' message writer attchments { fileset(dir: 'dist') { include name: '**/logs*.txt' } } }

Slide 213

Slide 213 text

Ant + XML 96 def writer = new StringWriter() def mkp = new MarkupBuilder(writer) ! mkp.html { head { title 'Build notification' } body { p 'Your build was successful' } } ! new AntBuilder().mail(mailhost: 'localhost', messagemimetype: 'text/html', subject: 'Build successful') { ! from address: '[email protected]' to address: '[email protected]' message writer attchments { fileset(dir: 'dist') { include name: '**/logs*.txt' } } } Generate some HTML with the XML support

Slide 214

Slide 214 text

Ant + XML 96 def writer = new StringWriter() def mkp = new MarkupBuilder(writer) ! mkp.html { head { title 'Build notification' } body { p 'Your build was successful' } } ! new AntBuilder().mail(mailhost: 'localhost', messagemimetype: 'text/html', subject: 'Build successful') { ! from address: '[email protected]' to address: '[email protected]' message writer attchments { fileset(dir: 'dist') { include name: '**/logs*.txt' } } } Generate some HTML with the XML support Use the Ant builder and the mail task

Slide 215

Slide 215 text

Ant + XML 96 def writer = new StringWriter() def mkp = new MarkupBuilder(writer) ! mkp.html { head { title 'Build notification' } body { p 'Your build was successful' } } ! new AntBuilder().mail(mailhost: 'localhost', messagemimetype: 'text/html', subject: 'Build successful') { ! from address: '[email protected]' to address: '[email protected]' message writer attchments { fileset(dir: 'dist') { include name: '**/logs*.txt' } } } Generate some HTML with the XML support Use the Ant builder and the mail task Use the Ant’s fileset

Slide 216

Slide 216 text

JSON support — creating JSON 97 import groovy.json.* ! def json = new JsonBuilder() json.person { name 'Guillaume' age 37 daughters 'Marion', 'Erine' address { street '1 Main Street' zip 75001 city 'Paris' } }

Slide 217

Slide 217 text

JSON support — creating JSON 97 import groovy.json.* ! def json = new JsonBuilder() json.person { name 'Guillaume' age 37 daughters 'Marion', 'Erine' address { street '1 Main Street' zip 75001 city 'Paris' } } { "person": { "name": "Guillaume", "age": 37, "daughters": [ "Marion", "Erine" ], "address": { "street": "1 Main Street", "zip": 75001, "city": "Paris" } } }

Slide 218

Slide 218 text

JSON support — parsing JSON 98 import groovy.json.* ! def url = 
 "https://api.github.com/repos/groovy/groovy-core/commits" ! def commits = new JsonSlurper().parseText(url.toURL().text) ! assert commits[0].commit.author.name == 'Cedric Champeau'

Slide 219

Slide 219 text

JSON support — parsing JSON 98 import groovy.json.* ! def url = 
 "https://api.github.com/repos/groovy/groovy-core/commits" ! def commits = new JsonSlurper().parseText(url.toURL().text) ! assert commits[0].commit.author.name == 'Cedric Champeau' The power of a dynamic language!

Slide 220

Slide 220 text

Template module — new markup template engine • Based on the principles of Groovy’s « builders » • and particularly the MarkupBuilder class
 for generating arbitrary XML / HTML payloads ! • Compiled statically for fast template rendering ! • Internationalization aware • provide the desired Locale in the configuration object • usual suffix notation template_fr_FR.tpl ! • Custom base template class • ability to provide reusable methods across your templates 99

Slide 221

Slide 221 text

Template module — new markup template engine • Based on the principles of Groovy’s « builders » • and particularly the MarkupBuilder class
 for generating arbitrary XML / HTML payloads ! • Compiled statically for fast template rendering ! • Internationalization aware • provide the desired Locale in the configuration object • usual suffix notation template_fr_FR.tpl ! • Custom base template class • ability to provide reusable methods across your templates 99 Spring Boot approved

Slide 222

Slide 222 text

Markup template engine — the idea 100 cars { cars.each { car(make: it.make, name: it.name) } }

Slide 223

Slide 223 text

Markup template engine — the idea 100 cars { cars.each { car(make: it.make, name: it.name) } } Your template

Slide 224

Slide 224 text

Markup template engine — the idea 100 cars { cars.each { car(make: it.make, name: it.name) } } model = [cars: [ new Car(make: 'Peugeot', name: '508'), new Car(make: 'Toyota', name: 'Prius’) ]]

Slide 225

Slide 225 text

Markup template engine — the idea 100 cars { cars.each { car(make: it.make, name: it.name) } } model = [cars: [ new Car(make: 'Peugeot', name: '508'), new Car(make: 'Toyota', name: 'Prius’) ]] Feed a model into your template

Slide 226

Slide 226 text

Markup template engine — the idea 100 cars { cars.each { car(make: it.make, name: it.name) } } model = [cars: [ new Car(make: 'Peugeot', name: '508'), new Car(make: 'Toyota', name: 'Prius’) ]]

Slide 227

Slide 227 text

Markup template engine — the idea 100 cars { cars.each { car(make: it.make, name: it.name) } } model = [cars: [ new Car(make: 'Peugeot', name: '508'), new Car(make: 'Toyota', name: 'Prius’) ]] Generate the XML output

Slide 228

Slide 228 text

Markup template engine — the idea 100 cars { cars.each { car(make: it.make, name: it.name) } } model = [cars: [ new Car(make: 'Peugeot', name: '508'), new Car(make: 'Toyota', name: 'Prius’) ]]

Slide 229

Slide 229 text

Markup template engine — in action 101 import  groovy.text.markup.*   ! def  config  =  new  TemplateConfiguration()   def  engine  =  new  MarkupTemplateEngine(config)   def  tmpl  =  engine.createTemplate('''          p("Hello  ${model.name}")   ''')   def  model  =  [name:  'World']   System.out  <<  tmpl.make(model)

Slide 230

Slide 230 text

© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission. Summary

Slide 231

Slide 231 text

Back to our original question… 103 Do we still need Groovy now that we have Java 8?

Slide 232

Slide 232 text

No content

Slide 233

Slide 233 text

Longer answer: Yes, because… • You can benefit from Java 8 in Groovy 105 Synergy
 the whole is 
 greater than the sum of the parts

Slide 234

Slide 234 text

Longer answer: Yes, because… • Groovy goes beyond what Java 8 offers 106 Beyond
 Groovy always tries to add something to the table

Slide 235

Slide 235 text

Questions still open: Syntax support for… • lambdas • not necessarily,
 (confusing / duplication) ! • method references • enhance method closures • or replace them with method references ! • interface default methods • yes: traits methods aren’t default methods • repeated annotations • yes: not urgent • but less boilerplate is good ! • annotations on type • yes: opens up new possibilities for targets of local AST transformation ! • interface static methods • yes: for Java compatibility 107

Slide 236

Slide 236 text

Further possible API enhancements • Make Optional Groovy-friendly • with regards to Groovy Truth • accessing the wrapped value ! • More Groovy methods for… • NIO • Streams • Date / time ! • Operator overloading for Date / time • for arithmetics on instants and durations 108

Slide 237

Slide 237 text

Further possible API enhancements • Make Optional Groovy-friendly • with regards to Groovy Truth • accessing the wrapped value ! • More Groovy methods for… • NIO • Streams • Date / time ! • Operator overloading for Date / time • for arithmetics on instants and durations 108 Community feedback & contributions welcome!

Slide 238

Slide 238 text

More Java 8 coverage 109 Java 8 language capabilities by V. Subramaniam Thu 10:30pm / D.Ball.G

Slide 239

Slide 239 text

More Java 8 coverage 110 How to get Groovy with Java 8 by Peter Ledbrook Thu 10:30pm / Trinity 3

Slide 240

Slide 240 text

Java 8 in Action ! • By Urma, Fusco and Mycrof • Plublished by Manning • http://www.manning.com/urma/ 111

Slide 241

Slide 241 text

© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission. Q & A

Slide 242

Slide 242 text

No content

Slide 243

Slide 243 text

Image credits • Question mark • http://www.mynamesnotmommy.com/wp-content/uploads/2013/05/question-mark.png • Get out! • http://static.comicvine.com/uploads/original/11/117995/3772037-0075368930-27616.jpg • Yes • http://soloprpro.com/wp-content/uploads/2013/08/yes.jpg • Synergy • http://www.wildblueberries.com/wp-content/uploads/blogger/_YhH8fDK5-kU/S_5kYWwgAbI/AAAAAAAAAKU/JQ_-ISfT9KY/ s1600/teamwork.jpg • Buzz Lightyear • http://www.disneypictures.net/data/media/202/Buzz_Lightyear_hd.jpg • Start wars spaceship • http://swc.fs2downloads.com/media/screenshots/Support_Trans/Shuttle/lambda003.jpg • Lambda character • http://lambda.ninjackaton.ninja-squad.com/images/lambda.png • Man clock • http://3.bp.blogspot.com/-7hLQ9tnmA84/TuTIoLRLMTI/AAAAAAAABWM/g7ahyLCRjJQ/s1600/Harold+Lloyd+Safety+Last.jpg • Stream • http://wallpaperswide.com/forest_stream-wallpapers.html • Nashorn (rhino) • http://2010sdafrika.files.wordpress.com/2012/07/hi_257587-nashorn-c-naturepl-com-mark-carwardine-wwf-canon.jpg 114

Slide 244

Slide 244 text

Image credits • Brain • http://www.semel.ucla.edu/sites/all/files/users/user-412/dreamstime_xxl_17754591%20(2).jpg • Many thanks • http://www.trys.ie/wp-content/uploads/2013/06/many-thanks.jpg • Swing • http://makemesomethingspecial.co.uk/wp-content/uploads/2012/10/Solid-Oak-Handmade-Spliced-Swing-With-Personalised- Engraving-10867.jpg • Disclaimer • http://3.bp.blogspot.com/-RGnBpjXTCQA/Tj2h_JsLigI/AAAAAAAABbg/AB5ZZYzuE5w/s1600/disclaimer.jpg • Hammer / justice / truth • http://www.bombayrealty.in/images/disclaimer.jpg • Law • http://www.permanentmakeupbymv.com/wp-content/uploads/2014/07/law-2.jpg • Glasses / readable • http://a.fastcompany.net/multisite_files/coexist/imagecache/1280/poster/2013/02/1681393-poster-1280-responsive-eye-chart.jpg • We need you • http://www.bostonbreakerssoccer.com/imgs/ABOUT/volopps/we%20need%20you.JPG 115

Slide 245

Slide 245 text

Image credits • Jackson Pollock • http://www.ibiblio.org/wm/paint/auth/pollock/pollock.number-8.jpg • Turtles • http://33.media.tumblr.com/77095e3a37acb2272133c405b1f7ba54/tumblr_myfydjNgXJ1s20kxvo1_1280.jpg • Annotations • http://3.bp.blogspot.com/-f94n9BHko_s/T35ELs7nYvI/AAAAAAAAAKg/qe06LRPH9U4/s1600/IMG_1721.JPG • Builders • http://detroittraining.com/wp-content/uploads/Construction-Women2.jpg • Sandwich ingredients • http://www.fromages-de-terroirs.com/IMG/MEULE_DETOUREE_72_DPI_-2.jpg • http://eboutique-hebergement.orange-business.com/WebRoot/Orange/Shops/Madeinalsace/4B02/6320/943E/DB4F/DDBF/ 0A0A/33E8/4BC8/iStock_000008775559XSmall.jpg • http://www.cmonprimeur.fr/images/25834-vegetable-pictures%5B1%5D.jpg • http://1.bp.blogspot.com/-27UoqYeYtY4/T4PAr4zGSnI/AAAAAAAAA3s/lleE-NOh0LE/s1600/IMG_1528.JPG • http://www.growingpatch.com/wp-content/uploads/2014/05/tomato-9.jpg • http://vitaminsandhealthsupplements.com/wp-content/uploads/2013/08/fresh-sliced-tomato.jpg • http://2.bp.blogspot.com/-3pPmRhBHv9s/T3D-EtLlN1I/AAAAAAAAAgg/Y5oTM84nGb8/s1600/cochon+2.jpg • http://www.salamarket.fr/wp-content/uploads/steak-hach%C3%A9.jpg • http://www.quinzanisbakery.com/images/bread-vienna.jpg • http://www.saucissonvaudois.qc.ca/images/produits/Jambon_blanc.jpg • http://www.audion.com/system/public/categories/125/images/bread-sandwich.jpg 116