titlesOf(Document... documents) {
+ return Arrays.stream(documents)
+ .map(d -> d.getTitle())
+ .collect(toList());
+ }
+}
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
index e25c601..931bf67 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
@@ -22,8 +22,17 @@
* #L%
*/
+import org.adoptopenjdk.lambda.tutorial.exercise4.Document;
+import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import java.util.Arrays;
import java.util.function.Consumer;
+import static org.adoptopenjdk.lambda.tutorial.util.CodeUsesMethodReferencesMatcher.usesMethodReferences;
+import static org.hamcrest.MatcherAssert.assertThat;
+
/**
* Exercise 4 - Method References
*
@@ -109,6 +118,7 @@
*
*
* Constructor belonging to a particular class
+ *
* By now, we know how to use method references for static methods and instance methods, that leaves an odd case:
* constructors.
*
@@ -134,5 +144,30 @@
@SuppressWarnings("unchecked")
public class Exercise_4_Test {
+ /**
+ * The Documents class has a method which transforms a list of Document into a list of
+ * their titles. The implementation has already been filled out, but it uses a lambda, as in:
+ * .map(document -> document.getTitle())
+ *
+ * Instead of using a lambda, use a method reference instead.
+ *
+ * @see Documents#titlesOf(Document...)
+ * @see Document#getTitle()
+ *
+ */
+ @Test
+ public void getListOfDocumentTitlesUsingInstanceMethodReference() {
+ Document expenses = new Document("My Expenses",
+ Arrays.asList(new Page("LJC Open Conference ticket: £25"), new Page("Beer stipend: £100")));
+ Document toDoList = new Document("My ToDo List",
+ Arrays.asList(new Page("Build a todo app"), new Page("Pick up dry cleaning")));
+ Document certificates = new Document("My Certificates",
+ Arrays.asList(new Page("Oracle Certified Professional"), new Page("Swimming 10m")));
+
+ assertThat(Documents.titlesOf(expenses, toDoList, certificates),
+ Matchers.contains("My Expenses", "My ToDo List", "My Certificates"));
+ assertThat(Documents.class, usesMethodReferences("getTitle"));
+
+ }
}
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/util/CodeUsesMethodReferencesMatcher.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/CodeUsesMethodReferencesMatcher.java
new file mode 100644
index 0000000..70a9ba2
--- /dev/null
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/CodeUsesMethodReferencesMatcher.java
@@ -0,0 +1,169 @@
+package org.adoptopenjdk.lambda.tutorial.util;
+
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Optional;
+
+import static java.util.stream.Collectors.toList;
+
+public final class CodeUsesMethodReferencesMatcher extends TypeSafeDiagnosingMatcher> {
+
+ private final String methodName;
+
+ private CodeUsesMethodReferencesMatcher(String methodName) {
+ this.methodName = methodName;
+ }
+
+ public static CodeUsesMethodReferencesMatcher usesMethodReferences(String methodName) {
+ return new CodeUsesMethodReferencesMatcher(methodName);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("a source file using a method reference to invoke ").appendValue(methodName);
+ }
+
+
+ @Override
+ protected boolean matchesSafely(Class> clazz, Description mismatchDescription) {
+ try {
+ Optional sourceFileContent = getSourceContent(clazz);
+ return sourceFileContent.map(c -> usesMethodReference(c, mismatchDescription)).orElseGet(() -> {
+ mismatchDescription.appendText("could not read source file to discover if you used method references.");
+ return false;
+ });
+ } catch (IOException e) {
+ mismatchDescription.appendText("could not read source file to discover if you used method references.");
+ mismatchDescription.appendValue(e);
+ return false;
+ }
+ }
+
+ private boolean usesMethodReference(String sourceCode, Description mismatchDescription) {
+ if (sourceCode.contains("::"+methodName)) {
+ return true;
+ } else {
+ mismatchDescription.appendText("source code did not use a method reference to invoke " + methodName + ". ");
+ context(sourceCode, methodName, mismatchDescription);
+ return false;
+ }
+ }
+
+ private void context(String sourceCode, String methodName, Description mismatchDescription) {
+ if (!sourceCode.contains(methodName)) {
+ mismatchDescription.appendText("You did not appear to invoke the method at all.");
+ } else {
+ String[] lines = sourceCode.split("\\n");
+ mismatchDescription.appendText("Actual invocations: ");
+ mismatchDescription.appendValueList("[", ",", "]",
+ Arrays.stream(lines).filter(l -> l.contains(methodName)).map(String::trim).collect(toList()));
+ }
+ }
+
+ private Optional getSourceContent(Class> clazz) throws IOException {
+ String sourceFileName = getSourceFileName(clazz);
+ Optional sourceFile = findPathTo(sourceFileName);
+
+ return sourceFile.map(this::toContent);
+ }
+
+ private Optional findPathTo(String sourceFileName) throws IOException {
+ File cwd = new File(".");
+ File rootOfProject = findRootOfProject(cwd);
+ return findSourceFile(rootOfProject, sourceFileName);
+ }
+
+ private String toContent(File file) {
+ try {
+ byte[] encoded = Files.readAllBytes(Paths.get(file.toURI()));
+ return StandardCharsets.UTF_8.decode(ByteBuffer.wrap(encoded)).toString();
+ } catch (IOException e) {
+ throw new RuntimeException("Could not read Java source file.", e);
+ }
+ }
+
+ private Optional findSourceFile(File rootOfProject, String sourceFileName) throws IOException {
+ Path startingDir = Paths.get(rootOfProject.toURI());
+ return Files.find(startingDir, 15, (path, attrs) -> path.endsWith(sourceFileName))
+ .map(p -> new File(p.toUri()))
+ .findFirst();
+ }
+
+ private File findRootOfProject(File cwd) {
+ File[] pomFiles = cwd.listFiles((file, name) -> { return name.equals("pom.xml"); });
+ if (pomFiles != null && pomFiles.length == 1) {
+ return cwd;
+ } else if (cwd.getParentFile() == null) {
+ throw new RuntimeException("Couldn't find directory containing pom.xml. Last looked in: " + cwd.getAbsolutePath());
+ } else {
+ return findRootOfProject(cwd.getParentFile());
+ }
+ }
+
+ private String getSourceFileName(Class> clazz) throws IOException {
+ String resourceName = clazz.getName().replace(".", "/").concat(".class");
+ ClassReader reader = new ClassReader(clazz.getClassLoader().getResourceAsStream(resourceName));
+ SourceFileNameVisitor sourceFileNameVisitor = new SourceFileNameVisitor();
+ reader.accept(sourceFileNameVisitor, 0);
+
+ return sourceFileNameVisitor.getSourceFile();
+ }
+
+
+ private static final class SourceFileNameVisitor extends ClassVisitor {
+
+ private String sourceFile = null;
+ private boolean visitedYet = false;
+
+ public SourceFileNameVisitor() {
+ super(Opcodes.ASM5);
+ }
+
+ @Override
+ public void visitSource(String source, String debug) {
+ this.visitedYet = true;
+ this.sourceFile = source;
+ super.visitSource(source, debug);
+ }
+
+ public String getSourceFile() {
+ if (!visitedYet) throw new IllegalStateException("Must visit a class before asking for source file");
+ return this.sourceFile;
+ }
+ }
+
+}
From c19462754654fef3efa143753eb134d9e39b14cd Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Fri, 4 Oct 2013 00:56:49 +0100
Subject: [PATCH 23/49] Remove main method already removed in master.
---
.../lambda/tutorial/exercise4/Printers.java | 15 ---------------
1 file changed, 15 deletions(-)
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java
index 4bd6d76..46b655a 100644
--- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java
@@ -20,19 +20,4 @@ public static Stream createPagesFrom(Stream contents) {
return contents.map(Page::new);
}
-
- public static void main(String... args) {
- Page p1 = new Page("this is the first page");
- Page p2 = new Page("this is the second page");
-
- Document myDocument = new Document(Arrays.asList(p1, p2));
-
- Printers.printPages(myDocument, 0, 1);
-
- List pages = Arrays.asList(p1, p2);
- pages.stream().map(Page::getContent).forEach(Printers::print);
-
- Stream pagesFromContent = createPagesFrom(Arrays.asList("a", "b").stream());
- System.out.println(pagesFromContent.collect(toList()));
- }
}
From 7314f84ac1cd98aa50cedb436de2ef2907db47f0 Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Fri, 4 Oct 2013 00:57:05 +0100
Subject: [PATCH 24/49] Make test pass by using a method reference.
---
src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
index af52656..006f77f 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
@@ -36,7 +36,7 @@ public class Documents {
*/
public static List titlesOf(Document... documents) {
return Arrays.stream(documents)
- .map(d -> d.getTitle())
+ .map(Document::getTitle)
.collect(toList());
}
}
From 5b6f780557d6e2ce1514ba99d80b8448067a7633 Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Fri, 4 Oct 2013 00:57:43 +0100
Subject: [PATCH 25/49] Add license headers.
---
.../lambda/tutorial/exercise4/Document.java | 22 +++++++++++++++++++
.../lambda/tutorial/exercise4/Printers.java | 22 +++++++++++++++++++
2 files changed, 44 insertions(+)
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java
index fda540f..a79d97b 100644
--- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java
@@ -1,5 +1,27 @@
package org.adoptopenjdk.lambda.tutorial.exercise4;
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java
index 46b655a..c56dba1 100644
--- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java
@@ -1,5 +1,27 @@
package org.adoptopenjdk.lambda.tutorial.exercise4;
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
import java.util.Arrays;
import java.util.List;
import static java.util.stream.Collectors.toList;
From a40a091d73f901756f587a99608258e8f4f88cf1 Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Fri, 4 Oct 2013 18:24:04 +0100
Subject: [PATCH 26/49] Make the name of test consistent with the category of
method reference as described in introduction.
---
.../java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
index 931bf67..06ad9ee 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
@@ -156,7 +156,7 @@ public class Exercise_4_Test {
*
*/
@Test
- public void getListOfDocumentTitlesUsingInstanceMethodReference() {
+ public void getListOfDocumentTitlesUsingReferenceOfInstanceMethodBelongingToAClass() {
Document expenses = new Document("My Expenses",
Arrays.asList(new Page("LJC Open Conference ticket: £25"), new Page("Beer stipend: £100")));
Document toDoList = new Document("My ToDo List",
From 61a3638e5afb00c06cfbb0d34014171470a4866f Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Fri, 4 Oct 2013 18:48:16 +0100
Subject: [PATCH 27/49] Add an example for a reference to a static method.
---
.../lambda/tutorial/exercise4/Document.java | 4 +-
.../lambda/tutorial/exercise4/Printers.java | 45 -------------------
.../lambda/tutorial/Documents.java | 9 ++++
.../lambda/tutorial/Exercise_4_Test.java | 25 ++++++++++-
4 files changed, 34 insertions(+), 49 deletions(-)
delete mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java
index a79d97b..f6c4b9f 100644
--- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java
@@ -35,8 +35,8 @@ public Document(String title, List pages) {
this.pages = Collections.unmodifiableList(new ArrayList<>(pages));
}
- public String getPageContent(Integer pageNumber) {
- return this.pages.get(pageNumber).getContent();
+ public List getPages() {
+ return this.pages;
}
public String getTitle() {
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java
deleted file mode 100644
index c56dba1..0000000
--- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Printers.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.adoptopenjdk.lambda.tutorial.exercise4;
-
-/*
- * #%L
- * lambda-tutorial
- * %%
- * Copyright (C) 2013 Adopt OpenJDK
- * %%
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 2 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public
- * License along with this program. If not, see
- * .
- * #L%
- */
-
-import java.util.Arrays;
-import java.util.List;
-import static java.util.stream.Collectors.toList;
-import java.util.stream.Stream;
-
-import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page;
-
-public class Printers {
- public static void print(String s) {
- System.out.println(s);
- }
-
- public static void printPages(Document doc, Integer... pageNumbers) {
- Arrays.stream(pageNumbers).map(doc::getPageContent).forEach(Printers::print);
- }
-
- public static Stream createPagesFrom(Stream contents) {
- return contents.map(Page::new);
- }
-
-}
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
index 006f77f..aeb4af9 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
@@ -23,6 +23,7 @@
*/
import org.adoptopenjdk.lambda.tutorial.exercise4.Document;
+import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page;
import java.util.Arrays;
import java.util.List;
@@ -39,4 +40,12 @@ public static List titlesOf(Document... documents) {
.map(Document::getTitle)
.collect(toList());
}
+
+ public static Integer characterCount(Page page) {
+ return page.getContent().length();
+ }
+
+ public static List pageCharacterCounts(Document document) {
+ return document.getPages().stream().map(doc -> Documents.characterCount(doc)).collect(toList());
+ }
}
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
index 06ad9ee..b96eb7e 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
@@ -24,7 +24,7 @@
import org.adoptopenjdk.lambda.tutorial.exercise4.Document;
import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page;
-import org.hamcrest.Matchers;
+import static org.hamcrest.Matchers.*;
import org.junit.Test;
import java.util.Arrays;
@@ -165,9 +165,30 @@ public void getListOfDocumentTitlesUsingReferenceOfInstanceMethodBelongingToACla
Arrays.asList(new Page("Oracle Certified Professional"), new Page("Swimming 10m")));
assertThat(Documents.titlesOf(expenses, toDoList, certificates),
- Matchers.contains("My Expenses", "My ToDo List", "My Certificates"));
+ contains("My Expenses", "My ToDo List", "My Certificates"));
assertThat(Documents.class, usesMethodReferences("getTitle"));
}
+ /**
+ * The Documents class has a method which calculates a list of the character counts of Pages in a
+ * Document. The method characterCount can be applied to each Page to calculate the number of
+ * characters in that page. Currently it is invoked using a lambda.
+ *
+ * Change to use a method reference which uses the static characterCount method.
+ *
+ * @see Documents#pageCharacterCounts(Document)
+ * @see Documents#characterCount(Page)
+ */
+ @Test
+ public void getListOfPageCharacterCountsFromDocumentUsingReferenceOfStaticMethodBelongingToAClass() {
+ Document diary = new Document("My Diary", Arrays.asList(
+ new Page("Today I went shopping"),
+ new Page("Today I did maths"),
+ new Page("Today I wrote in my diary")));
+
+ assertThat(Documents.pageCharacterCounts(diary), contains(21, 17, 25));
+ assertThat(Documents.class, usesMethodReferences("characterCount"));
+ }
+
}
From 775796bf3421d67e07aac79d6261e9f3e2641905 Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Fri, 4 Oct 2013 18:57:10 +0100
Subject: [PATCH 28/49] Implement in post java 8.
---
src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
index aeb4af9..f727cc7 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
@@ -46,6 +46,8 @@ public static Integer characterCount(Page page) {
}
public static List pageCharacterCounts(Document document) {
- return document.getPages().stream().map(doc -> Documents.characterCount(doc)).collect(toList());
+ return document.getPages().stream()
+ .map(Documents::characterCount)
+ .collect(toList());
}
}
From 8651b79ef26867c1992b35cee891e0e01b0da6ed Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Fri, 4 Oct 2013 19:34:20 +0100
Subject: [PATCH 29/49] Add test case for using method references to invoke
methods on a particular instance.
---
.../tutorial/exercise4/PagePrinter.java | 48 ++++++++++++++++
.../lambda/tutorial/Documents.java | 13 +++++
.../lambda/tutorial/Exercise_4_Test.java | 37 +++++++++++++
.../util/StringWithComparisonMatcher.java | 55 +++++++++++++++++++
4 files changed, 153 insertions(+)
create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/PagePrinter.java
create mode 100644 src/test/java/org/adoptopenjdk/lambda/tutorial/util/StringWithComparisonMatcher.java
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/PagePrinter.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/PagePrinter.java
new file mode 100644
index 0000000..503692a
--- /dev/null
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/PagePrinter.java
@@ -0,0 +1,48 @@
+package org.adoptopenjdk.lambda.tutorial.exercise4;
+
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page;
+
+import static java.lang.String.format;
+
+public final class PagePrinter {
+
+ private final String pageBreak;
+
+ public PagePrinter(String pageBreak) {
+ this.pageBreak = pageBreak;
+ }
+
+ public String printTitlePage(Document document) {
+ return format(
+ "%s%n" +
+ "%s%n", document.getTitle(), pageBreak);
+ }
+
+ public String printPage(Page page) {
+ return format(
+ "%s%n" +
+ "%s%n", page.getContent(), pageBreak);
+ }
+}
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
index f727cc7..7f01f73 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
@@ -24,10 +24,12 @@
import org.adoptopenjdk.lambda.tutorial.exercise4.Document;
import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page;
+import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter;
import java.util.Arrays;
import java.util.List;
+import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
public class Documents {
@@ -50,4 +52,15 @@ public static List pageCharacterCounts(Document document) {
.map(Documents::characterCount)
.collect(toList());
}
+
+ public static String print(Document document, PagePrinter pagePrinter) {
+ StringBuilder output = new StringBuilder();
+
+ output.append(pagePrinter.printTitlePage(document));
+ document.getPages().stream()
+ .map(p -> pagePrinter.printPage(p))
+ .forEach(s -> output.append(s));
+
+ return output.toString();
+ }
}
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
index b96eb7e..58de542 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
@@ -24,7 +24,11 @@
import org.adoptopenjdk.lambda.tutorial.exercise4.Document;
import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page;
+
+import static java.lang.String.format;
import static org.hamcrest.Matchers.*;
+
+import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter;
import org.junit.Test;
import java.util.Arrays;
@@ -32,6 +36,7 @@
import static org.adoptopenjdk.lambda.tutorial.util.CodeUsesMethodReferencesMatcher.usesMethodReferences;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.adoptopenjdk.lambda.tutorial.util.StringWithComparisonMatcher.*;
/**
* Exercise 4 - Method References
@@ -191,4 +196,36 @@ public void getListOfPageCharacterCountsFromDocumentUsingReferenceOfStaticMethod
assertThat(Documents.class, usesMethodReferences("characterCount"));
}
+ /**
+ * The Documents class has a method which takes a PagePrinter and renders a
+ * Document to text. The method uses two lambda expressions where method references can be used. In
+ * this case the method references are to methods belonging to a particular instance object.
+ *
+ * Change {@link Documents#print(Document, PagePrinter)} to use method references to invoke instance methods of
+ * particular objects.
+ *
+ * @see Documents#print(Document, PagePrinter)
+ * @see StringBuilder#append
+ * @see PagePrinter#printPage(Page)
+ *
+ */
+ @Test
+ public void printContentsOfDocumentUsingReferenceOfInstanceMethodBeloningToAnObject() {
+ Document diary = new Document("My Diary", Arrays.asList(
+ new Page("Today I went shopping"),
+ new Page("Today I did maths"),
+ new Page("Today I wrote in my diary")));
+
+ assertThat(Documents.print(diary, new PagePrinter("----")),
+ isString(format("My Diary%n" +
+ "----%n" +
+ "Today I went shopping%n" +
+ "----%n" +
+ "Today I did maths%n" +
+ "----%n" +
+ "Today I wrote in my diary%n" +
+ "----%n")));
+ assertThat(Documents.class, allOf(usesMethodReferences("printPage"), usesMethodReferences("append")));
+ }
+
}
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/util/StringWithComparisonMatcher.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/StringWithComparisonMatcher.java
new file mode 100644
index 0000000..dc661d0
--- /dev/null
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/StringWithComparisonMatcher.java
@@ -0,0 +1,55 @@
+package org.adoptopenjdk.lambda.tutorial.util;
+
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.junit.ComparisonFailure;
+
+public class StringWithComparisonMatcher extends TypeSafeDiagnosingMatcher {
+ private final String expected;
+
+ private StringWithComparisonMatcher(String expected) {
+ this.expected = expected;
+ }
+
+ public static StringWithComparisonMatcher isString(String expected) {
+ return new StringWithComparisonMatcher(expected);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(expected);
+ }
+
+ @Override
+ protected boolean matchesSafely(String actual, Description mismatchDescription) {
+ if (!expected.equals(actual)) {
+ String compactedMismatch = new ComparisonFailure("did not match:", expected, actual).getMessage();
+ mismatchDescription.appendText(compactedMismatch);
+ return false;
+ }
+ return true;
+ }
+
+}
\ No newline at end of file
From 9a1ced515f9f7ec9bad284ecff58c93ca49a0aec Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Fri, 4 Oct 2013 19:47:41 +0100
Subject: [PATCH 30/49] Add passing test case for post-java 8.
---
src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
index 7f01f73..bb2339c 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
@@ -58,8 +58,8 @@ public static String print(Document document, PagePrinter pagePrinter) {
output.append(pagePrinter.printTitlePage(document));
document.getPages().stream()
- .map(p -> pagePrinter.printPage(p))
- .forEach(s -> output.append(s));
+ .map(pagePrinter::printPage)
+ .forEach(output::append);
return output.toString();
}
From 23db83070d13a1d4fa2ba22e871dcee8d151970c Mon Sep 17 00:00:00 2001
From: Grundlefleck
Date: Sun, 6 Oct 2013 21:48:23 +0100
Subject: [PATCH 31/49] Add test case to show how to use method reference using
'this'.
---
.../lambda/tutorial/exercise4/Document.java | 19 ++++++++++++
.../lambda/tutorial/Exercise_4_Test.java | 31 +++++++++++++++++++
2 files changed, 50 insertions(+)
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java
index f6c4b9f..56976b5 100644
--- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java
@@ -25,6 +25,10 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static java.util.stream.Collectors.collectingAndThen;
public final class Document {
private final String title;
@@ -43,6 +47,21 @@ public String getTitle() {
return this.title;
}
+ private Page appendFooter(Page original) {
+ String footer = "Document: " + getTitle();
+ return new Page(format("%s%n%s", original.getContent(), footer));
+ }
+
+ private Document copyWithPages(List newPages) {
+ return new Document(title, newPages);
+ }
+
+ public Document copyWithFooter() {
+ return getPages().stream()
+ .map(page -> appendFooter(page))
+ .collect(collectingAndThen(Collectors.toList(), pages -> copyWithPages(pages)));
+ }
+
public static final class Page {
private final String content;
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
index 58de542..2a1bc97 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
@@ -29,6 +29,8 @@
import static org.hamcrest.Matchers.*;
import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter;
+import org.adoptopenjdk.lambda.tutorial.util.FeatureMatchers;
+import org.hamcrest.Matcher;
import org.junit.Test;
import java.util.Arrays;
@@ -228,4 +230,33 @@ public void printContentsOfDocumentUsingReferenceOfInstanceMethodBeloningToAnObj
assertThat(Documents.class, allOf(usesMethodReferences("printPage"), usesMethodReferences("append")));
}
+
+ /**
+ * The Document class has a method which can create a new Document where all the pages have had a
+ * footer appended to it of the format "Document: {title}". The method uses two lambda expressions where method
+ * references can be used. In this case the method references are to methods belonging to this object
+ * instance. That is, the methods to be invoked should be invoked on this.
+ *
+ * Change {@link Document#copyWithFooter()} to use method references to invoke instance methods on this
+ * instance.
+ */
+ @Test
+ public void transformPagesToHaveFooterUsingReferenceOfInstanceMethodBelonginToThisObject() {
+ Page blankPage = new Page("");
+ Document diary = new Document("My Diary", Arrays.asList(
+ new Page("Today I went shopping"),
+ blankPage,
+ new Page("Today I did maths"),
+ blankPage,
+ new Page("Today I wrote in my diary")));
+
+ Document diaryWithFooters = diary.copyWithFooter();
+
+ assertThat(diaryWithFooters.getPages(), everyItem(pageEndingWith("Document: My Diary")));
+ assertThat(Document.class, allOf(usesMethodReferences("appendFooter"), usesMethodReferences("copyWithPages")));
+ }
+
+ private static Matcher pageEndingWith(String ending) {
+ return FeatureMatchers.from(endsWith(ending), "page containing", "contents", Page::getContent);
+ }
}
From 80c2c1f15d77241286b12c5894b57db17f44c86a Mon Sep 17 00:00:00 2001
From: Grundlefleck
Date: Sun, 6 Oct 2013 22:01:10 +0100
Subject: [PATCH 32/49] Add post java 8 solutions using method references
attached to this object.
---
.../org/adoptopenjdk/lambda/tutorial/exercise4/Document.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java
index 56976b5..923184c 100644
--- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Document.java
@@ -58,8 +58,8 @@ private Document copyWithPages(List newPages) {
public Document copyWithFooter() {
return getPages().stream()
- .map(page -> appendFooter(page))
- .collect(collectingAndThen(Collectors.toList(), pages -> copyWithPages(pages)));
+ .map(this::appendFooter)
+ .collect(collectingAndThen(Collectors.toList(), this::copyWithPages));
}
public static final class Page {
From aba3b035bb214749116fe49abf6ec5be5b46dc15 Mon Sep 17 00:00:00 2001
From: Grundlefleck
Date: Sun, 6 Oct 2013 22:04:11 +0100
Subject: [PATCH 33/49] Whoops, forgot to remove blankPage variable from when
figuring out what this test should do.
---
.../java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
index 2a1bc97..3a6906b 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
@@ -242,12 +242,9 @@ public void printContentsOfDocumentUsingReferenceOfInstanceMethodBeloningToAnObj
*/
@Test
public void transformPagesToHaveFooterUsingReferenceOfInstanceMethodBelonginToThisObject() {
- Page blankPage = new Page("");
Document diary = new Document("My Diary", Arrays.asList(
new Page("Today I went shopping"),
- blankPage,
new Page("Today I did maths"),
- blankPage,
new Page("Today I wrote in my diary")));
Document diaryWithFooters = diary.copyWithFooter();
From 89d485ceee15f43d44029dea93cbe2a420ceaa39 Mon Sep 17 00:00:00 2001
From: Grundlefleck
Date: Mon, 7 Oct 2013 21:13:41 +0100
Subject: [PATCH 34/49] Add correctly failing test for using constructor method
reference.
---
.../lambda/tutorial/exercise4/Translator.java | 19 ++++++++++++++++
.../lambda/tutorial/Documents.java | 10 +++++++++
.../lambda/tutorial/Exercise_4_Test.java | 22 +++++++++++++++++++
3 files changed, 51 insertions(+)
create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java
new file mode 100644
index 0000000..61ef0fd
--- /dev/null
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java
@@ -0,0 +1,19 @@
+package org.adoptopenjdk.lambda.tutorial.exercise4;
+
+public interface Translator {
+
+ String translate(String input);
+
+ enum Languages implements Translator {
+ REVERSISH {
+ @Override
+ public String translate(String input) {
+ return new StringBuilder(input).reverse().toString();
+ }
+ }
+
+ // TODO: implement other, real languages.
+ }
+
+}
+
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
index bb2339c..1b52160 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
@@ -25,11 +25,13 @@
import org.adoptopenjdk.lambda.tutorial.exercise4.Document;
import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page;
import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter;
+import org.adoptopenjdk.lambda.tutorial.exercise4.Translator;
import java.util.Arrays;
import java.util.List;
import static java.lang.String.format;
+import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
public class Documents {
@@ -63,4 +65,12 @@ public static String print(Document document, PagePrinter pagePrinter) {
return output.toString();
}
+
+ public static Document translate(Document document, Translator translator) {
+ return document.getPages().stream()
+ .map(page -> page.getContent())
+ .map(content -> translator.translate(content))
+ .map(translated -> new Page(translated))
+ .collect(collectingAndThen(toList(), pages -> new Document(document.getTitle(), pages)));
+ }
}
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
index 3a6906b..c8f649c 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
@@ -29,6 +29,7 @@
import static org.hamcrest.Matchers.*;
import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter;
+import org.adoptopenjdk.lambda.tutorial.exercise4.Translator.Languages;
import org.adoptopenjdk.lambda.tutorial.util.FeatureMatchers;
import org.hamcrest.Matcher;
import org.junit.Test;
@@ -253,7 +254,28 @@ public void transformPagesToHaveFooterUsingReferenceOfInstanceMethodBelonginToTh
assertThat(Document.class, allOf(usesMethodReferences("appendFooter"), usesMethodReferences("copyWithPages")));
}
+
+ @Test
+ public void createNewDocumentWithTranslatedPagesUsingReferenceOfConstructorMethod() {
+ Document diary = new Document("My Diary", Arrays.asList(
+ new Page("Today I went shopping"),
+ new Page("Today I did maths"),
+ new Page("Today I wrote in my diary")));
+
+ Document translated = Documents.translate(diary, Languages.REVERSISH);
+
+ assertThat(translated.getPages(),
+ contains(pageContaining("gnippohs tnew I yadoT"),
+ pageContaining("shtam did I yadoT"),
+ pageContaining("yraid ym ni etorw I yadoT")));
+ assertThat(Documents.class, usesMethodReferences("new"));
+ }
+
private static Matcher pageEndingWith(String ending) {
return FeatureMatchers.from(endsWith(ending), "page containing", "contents", Page::getContent);
}
+
+ private static Matcher pageContaining(String content) {
+ return FeatureMatchers.from(isString(content), "page containing", "contents", Page::getContent);
+ }
}
From 130f7bea559db595df52f61448a78c9195a0d247 Mon Sep 17 00:00:00 2001
From: Grundlefleck
Date: Mon, 7 Oct 2013 22:33:04 +0100
Subject: [PATCH 35/49] Finish javadoc for final test of method references
chapter.
---
.../lambda/tutorial/Documents.java | 3 ++-
.../lambda/tutorial/Exercise_4_Test.java | 23 +++++++++++++++----
2 files changed, 20 insertions(+), 6 deletions(-)
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
index 1b52160..a3bb970 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
@@ -71,6 +71,7 @@ public static Document translate(Document document, Translator translator) {
.map(page -> page.getContent())
.map(content -> translator.translate(content))
.map(translated -> new Page(translated))
- .collect(collectingAndThen(toList(), pages -> new Document(document.getTitle(), pages)));
+ .collect(collectingAndThen(toList(),
+ pages -> new Document(translator.translate(document.getTitle()), pages)));
}
}
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
index c8f649c..85ff9f6 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
@@ -24,11 +24,8 @@
import org.adoptopenjdk.lambda.tutorial.exercise4.Document;
import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page;
-
-import static java.lang.String.format;
-import static org.hamcrest.Matchers.*;
-
import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter;
+import org.adoptopenjdk.lambda.tutorial.exercise4.Translator;
import org.adoptopenjdk.lambda.tutorial.exercise4.Translator.Languages;
import org.adoptopenjdk.lambda.tutorial.util.FeatureMatchers;
import org.hamcrest.Matcher;
@@ -37,9 +34,14 @@
import java.util.Arrays;
import java.util.function.Consumer;
+import static java.lang.String.format;
import static org.adoptopenjdk.lambda.tutorial.util.CodeUsesMethodReferencesMatcher.usesMethodReferences;
+import static org.adoptopenjdk.lambda.tutorial.util.StringWithComparisonMatcher.isString;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.adoptopenjdk.lambda.tutorial.util.StringWithComparisonMatcher.*;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.everyItem;
/**
* Exercise 4 - Method References
@@ -255,6 +257,17 @@ public void transformPagesToHaveFooterUsingReferenceOfInstanceMethodBelonginToTh
}
+ /**
+ * The Documents class has a method which can translate a document into another language. The method
+ * uses a lambda expression to construct each translated Page, where it could use a method reference
+ * to Page's constructor.
+ *
+ * Change {@link Documents#translate} to use a method reference to construct each translated Page.
+ *
+ * @see Documents#translate(Document, Translator)
+ * @see Translator.Languages
+ * @see Page
+ */
@Test
public void createNewDocumentWithTranslatedPagesUsingReferenceOfConstructorMethod() {
Document diary = new Document("My Diary", Arrays.asList(
From bb04c10ab1f70c48cc8620379052954249b5b7f7 Mon Sep 17 00:00:00 2001
From: Grundlefleck
Date: Mon, 7 Oct 2013 22:36:17 +0100
Subject: [PATCH 36/49] Make test for using constructor method reference pass
for post jdk8.
---
.../java/org/adoptopenjdk/lambda/tutorial/Documents.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
index a3bb970..82afa8e 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
@@ -68,9 +68,9 @@ public static String print(Document document, PagePrinter pagePrinter) {
public static Document translate(Document document, Translator translator) {
return document.getPages().stream()
- .map(page -> page.getContent())
- .map(content -> translator.translate(content))
- .map(translated -> new Page(translated))
+ .map(Page::getContent)
+ .map(translator::translate)
+ .map(Page::new)
.collect(collectingAndThen(toList(),
pages -> new Document(translator.translate(document.getTitle()), pages)));
}
From 2eebb4503f42bc24f85fab0dfb46b19aae30d096 Mon Sep 17 00:00:00 2001
From: Grundlefleck
Date: Mon, 7 Oct 2013 22:42:13 +0100
Subject: [PATCH 37/49] Merge README from master.
---
README.md | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/README.md b/README.md
index c8123ae..6742287 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@ To follow the exercises:
1. Internal vs External Iteration (the forEach method)
2. Filtering and Collecting
3. Mapping
+ 4. (In Progress) Method References
[More to come]
@@ -34,3 +35,11 @@ The current tutorial is known to work with the following JDK build:
|ea b109 |Sep 26, 2013|
lambda-tutorial will try to track against the newest version available. If you find that you are working with a newer version of the Lambda JDK and the tutorial does not compile or run, please file an issue.
+
+### IDE Setup
+- [IntelliJ IDEA on Ubuntu](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/IntelliJ-IDEA-on-Ubuntu-%5BLinux%5D)
+- [IntelliJ IDEA on MacOS](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/IntelliJ-IDEA-on-MacOS)
+- [IntelliJ IDEA deutsche Anleitung (u.a. Windows)](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/IntelliJ-IDEA-Einrichtung)
+- [Eclipse Kepler 4.3 on Windows](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/Eclipse-Lambda-EA-Setup)
+
+Note: we are hoping the instructions are not too sensitive to the OSes on which they have been performed.
From 79e5389cf7130d804e4436b1950762a614fb3fb3 Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Sun, 13 Oct 2013 21:03:16 +0100
Subject: [PATCH 38/49] Move Documents class into the appropriate package and
source folder.
---
.../adoptopenjdk/lambda/tutorial/exercise4}/Documents.java | 5 +----
.../org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java | 1 +
2 files changed, 2 insertions(+), 4 deletions(-)
rename src/{test/java/org/adoptopenjdk/lambda/tutorial => main/java/org/adoptopenjdk/lambda/tutorial/exercise4}/Documents.java (91%)
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Documents.java
similarity index 91%
rename from src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
rename to src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Documents.java
index 82afa8e..0baff6b 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Documents.java
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Documents.java
@@ -1,4 +1,4 @@
-package org.adoptopenjdk.lambda.tutorial;
+package org.adoptopenjdk.lambda.tutorial.exercise4;
/*
* #%L
@@ -22,10 +22,7 @@
* #L%
*/
-import org.adoptopenjdk.lambda.tutorial.exercise4.Document;
import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page;
-import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter;
-import org.adoptopenjdk.lambda.tutorial.exercise4.Translator;
import java.util.Arrays;
import java.util.List;
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
index 85ff9f6..1bb2ffc 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_4_Test.java
@@ -24,6 +24,7 @@
import org.adoptopenjdk.lambda.tutorial.exercise4.Document;
import org.adoptopenjdk.lambda.tutorial.exercise4.Document.Page;
+import org.adoptopenjdk.lambda.tutorial.exercise4.Documents;
import org.adoptopenjdk.lambda.tutorial.exercise4.PagePrinter;
import org.adoptopenjdk.lambda.tutorial.exercise4.Translator;
import org.adoptopenjdk.lambda.tutorial.exercise4.Translator.Languages;
From 9ef15a2d1a0237b136a0685708a3ee15f09afa7d Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Sat, 19 Oct 2013 12:44:12 +0100
Subject: [PATCH 39/49] Method References chapter is now considered complete.
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 6742287..48fd16a 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ To follow the exercises:
1. Internal vs External Iteration (the forEach method)
2. Filtering and Collecting
3. Mapping
- 4. (In Progress) Method References
+ 4. Method References
[More to come]
From 27bdd01b15e193184df59924c2569f3970a7a317 Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Sun, 16 Feb 2014 12:27:14 +0000
Subject: [PATCH 40/49] Add travis-ci config.
---
.travis.yml | 6 ++++++
1 file changed, 6 insertions(+)
create mode 100644 .travis.yml
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..3045d61
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,6 @@
+language: java
+jdk:
+ - oraclejdk8
+ - openjdk8
+script: "mvn clean test"
+
From 343af46cc6fa50c8290f082ec0d9e8f20eab5e18 Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Sun, 16 Feb 2014 12:40:55 +0000
Subject: [PATCH 41/49] Try to fix .travis config file.
---
.travis.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 3045d61..be8abd4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,5 @@
language: java
jdk:
- oraclejdk8
- - openjdk8
script: "mvn clean test"
From 9ae456efab39d2545387dd0c8af0074f693fad36 Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Sun, 16 Feb 2014 12:45:38 +0000
Subject: [PATCH 42/49] Add Travis build status to readme
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index 48fd16a..3f5e6a1 100644
--- a/README.md
+++ b/README.md
@@ -43,3 +43,5 @@ lambda-tutorial will try to track against the newest version available. If you f
- [Eclipse Kepler 4.3 on Windows](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/Eclipse-Lambda-EA-Setup)
Note: we are hoping the instructions are not too sensitive to the OSes on which they have been performed.
+
+[](https://travis-ci.org/AdoptOpenJDK/lambda-tutorial)
From 8a00bdf58c62e00ee60a0966aa17f7797212b53d Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Sun, 16 Feb 2014 12:46:51 +0000
Subject: [PATCH 43/49] Move build status.
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 3f5e6a1..d1425a6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
-## Lambda Tutorial
+## Lambda Tutorial
+[](https://travis-ci.org/AdoptOpenJDK/lambda-tutorial)
A set of exercises to teach use of Java 8 lambda syntax, and the new Streams API.
@@ -44,4 +45,3 @@ lambda-tutorial will try to track against the newest version available. If you f
Note: we are hoping the instructions are not too sensitive to the OSes on which they have been performed.
-[](https://travis-ci.org/AdoptOpenJDK/lambda-tutorial)
From 8b9972ed967499a81ad7d732f904f3667516de8b Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Sun, 16 Feb 2014 13:00:30 +0000
Subject: [PATCH 44/49] Retrieve build status icon for the correct branch.
---
README.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/README.md b/README.md
index d1425a6..30b8fde 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
-## Lambda Tutorial
-[](https://travis-ci.org/AdoptOpenJDK/lambda-tutorial)
+## Lambda Tutorial [](https://travis-ci.org/AdoptOpenJDK/lambda-tutorial)
A set of exercises to teach use of Java 8 lambda syntax, and the new Streams API.
From 2ecbbafceef93ca8d9950227b5cd32c9d81bfeb0 Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Sun, 16 Feb 2014 13:14:51 +0000
Subject: [PATCH 45/49] Add generated license header.
---
.../lambda/tutorial/exercise4/Translator.java | 22 +++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java
index 61ef0fd..dabc498 100644
--- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise4/Translator.java
@@ -1,5 +1,27 @@
package org.adoptopenjdk.lambda.tutorial.exercise4;
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 - 2014 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
public interface Translator {
String translate(String input);
From 1b08007302d0c2c3b52b5b3321c6436b99bf6e9d Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Wed, 8 Apr 2020 22:20:46 +0100
Subject: [PATCH 46/49] Remove outdated instructions and documentation.
In 2020, there's no need to go into detail about getting lambda enabled JDK builds, or how to get support in IDEs.
---
README.md | 31 +++----------------
.../ConfigureYourLambdaBuildOfJdk.java | 10 +++---
2 files changed, 8 insertions(+), 33 deletions(-)
diff --git a/README.md b/README.md
index 30b8fde..d3b1e66 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
## Lambda Tutorial [](https://travis-ci.org/AdoptOpenJDK/lambda-tutorial)
-A set of exercises to teach use of Java 8 lambda syntax, and the new Streams API.
+A set of exercises to teach use of Java 8 lambda syntax, and the Streams API.
To follow the exercises:
- fork and clone the repository
- - ensure you have a correctly configured, [lambda-enabled Java build](#getting-lambda-jdk)
- - Maven can help generate configuration for your favourite IDE, though you will likely have to set the JDK manually
+ - ensure you have a correctly configured, JDK8+ build
+ - Maven can help generate configuration for your favourite IDE
- ensure your cloned project, particularly the class `ConfigureYourLambdaBuildOfJdk` compiles and runs correctly
- navigate to the first exercise, `Exercise_1_Test` (tests are in `src/test/java`, in the package `org.adoptopenjdk.lambda.tutorial`)
- read background information in the JavaDoc, and follow instructions, making the test pass
@@ -19,28 +19,5 @@ To follow the exercises:
2. Filtering and Collecting
3. Mapping
4. Method References
-
-[More to come]
-
-
-### Getting Lambda JDK
-Early access builds of JDK 8 are available [here](https://jdk8.java.net/lambda/).
-
-
-#### Lamba JDK Build Compatibility
-The current tutorial is known to work with the following JDK build:
-
-|JDK Build Number|Released On |
-|:---------------|:---------- |
-|ea b109 |Sep 26, 2013|
-
-lambda-tutorial will try to track against the newest version available. If you find that you are working with a newer version of the Lambda JDK and the tutorial does not compile or run, please file an issue.
-
-### IDE Setup
-- [IntelliJ IDEA on Ubuntu](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/IntelliJ-IDEA-on-Ubuntu-%5BLinux%5D)
-- [IntelliJ IDEA on MacOS](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/IntelliJ-IDEA-on-MacOS)
-- [IntelliJ IDEA deutsche Anleitung (u.a. Windows)](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/IntelliJ-IDEA-Einrichtung)
-- [Eclipse Kepler 4.3 on Windows](https://github.com/AdoptOpenJDK/lambda-tutorial/wiki/Eclipse-Lambda-EA-Setup)
-
-Note: we are hoping the instructions are not too sensitive to the OSes on which they have been performed.
+ 5. Default methods on interfaces
diff --git a/src/main/java/org/adoptopenjdk/lambda/setupcheck/ConfigureYourLambdaBuildOfJdk.java b/src/main/java/org/adoptopenjdk/lambda/setupcheck/ConfigureYourLambdaBuildOfJdk.java
index 8c87c4a..0298e1f 100644
--- a/src/main/java/org/adoptopenjdk/lambda/setupcheck/ConfigureYourLambdaBuildOfJdk.java
+++ b/src/main/java/org/adoptopenjdk/lambda/setupcheck/ConfigureYourLambdaBuildOfJdk.java
@@ -39,15 +39,13 @@ public class ConfigureYourLambdaBuildOfJdk {
public static void main(String... args) {
List messages = asList(
"If this source file does not compile, you have not configured your development setup correctly.",
- "It uses both a new JDK 8 syntax (method references with '::') and a new JDK 8 library method (Iterable#forEach)",
+ "It uses both a JDK 8+ syntax (method references with '::') and a JDK 8+ library method (Iterable#forEach)",
"You should also be able to execute this main method, and see this message printed to the console.",
"",
"To configure your development environment, you need:",
- " - a lambda build of JDK 8, available at: http://jdk8.java.net/lambda/",
- " - a lambda-aware IDE.",
- " IntelliJ and NetBeans support lambdas in early access versions, available at: http://openjdk.java.net/projects/lambda/ \n" +
- " Eclipse support is more sketchy, the method described here just about works: http://tuhrig.de/?p=921",
- " Maven will compile your code and run your tests, just, add JDK 8 javac and java executables to your system path and use 'mvn test'",
+ " - an install of JDK 8 or higher",
+ " - an IDE that supports JDK8+. All mainstream IDEs (Eclipse/IntelliJ IDEA/NetBeans) support JDK8+ ",
+ " - and/or Maven, to compile your code and run your tests, using 'mvn test'",
"",
"Until this source file compiles, you will be unable to make progress in the tutorial.");
From ff95fb96fafad5772802381aec066b320da85f14 Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Thu, 9 Apr 2020 13:32:38 +0100
Subject: [PATCH 47/49] Copy failing test to postjava8 branch.g
---
.../tutorial/exercise5/musicplayer/Album.java | 26 ++
.../exercise5/musicplayer/MusicLibrary.java | 60 +++++
.../exercise5/musicplayer/Rating.java | 61 +++++
.../tutorial/exercise5/musicplayer/Song.java | 42 +++
.../exercise5/musicplayer/StarRating.java | 39 +++
.../musicplayer/UserRatedMusicLibrary.java | 34 +++
.../CloudScrobblingMusicLibrary.java | 85 ++++++
.../LocalFilesystemMusicLibrary.java | 53 ++++
.../UserRatedLocalFilesystemMusicLibrary.java | 73 +++++
.../lambda/tutorial/Exercise_5_Test.java | 255 ++++++++++++++++++
.../tutorial/util/HasConcreteMethod.java | 99 +++++++
11 files changed, 827 insertions(+)
create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/Album.java
create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/MusicLibrary.java
create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/Rating.java
create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/Song.java
create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/StarRating.java
create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/UserRatedMusicLibrary.java
create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/CloudScrobblingMusicLibrary.java
create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/LocalFilesystemMusicLibrary.java
create mode 100644 src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/UserRatedLocalFilesystemMusicLibrary.java
create mode 100644 src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_5_Test.java
create mode 100644 src/test/java/org/adoptopenjdk/lambda/tutorial/util/HasConcreteMethod.java
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/Album.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/Album.java
new file mode 100644
index 0000000..33626f2
--- /dev/null
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/Album.java
@@ -0,0 +1,26 @@
+package org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer;
+
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+public class Album {
+}
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/MusicLibrary.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/MusicLibrary.java
new file mode 100644
index 0000000..830ac65
--- /dev/null
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/MusicLibrary.java
@@ -0,0 +1,60 @@
+package org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer;
+
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import java.util.Collection;
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+
+public interface MusicLibrary {
+
+ /**
+ * @return every song in the collection, in no specified order.
+ */
+ Collection allSongs();
+
+ /**
+ * Will sort a given collection of Songs by artist.
+ */
+ static class SongByArtistSorter {
+ public static List sort(Collection songs) {
+ return songs.stream().sorted((song1, song2) -> song1.getArtist().compareTo(song2.getArtist()))
+ .collect(toList());
+ }
+ }
+
+ /**
+ * Provides a rating for this song, between 1-100, inclusive.
+ *
+ * Default implementation takes a rating by normalising the play count for the given song with the play count for
+ * all songs in this MusicLibrary.
+ */
+ default Rating ratingOf(Song song) {
+ int totalPlayCount = allSongs().stream().mapToInt(this::timesPlayed).sum();
+ float score = (timesPlayed(song) / totalPlayCount) * 100.0f;
+ return new Rating(Math.round(score));
+ }
+
+ int timesPlayed(Song song);
+}
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/Rating.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/Rating.java
new file mode 100644
index 0000000..4cf1729
--- /dev/null
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/Rating.java
@@ -0,0 +1,61 @@
+package org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer;
+
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+/**
+* Lambda Tutorial -- Adopt Open JDK
+*
+* @author Graham Allan grundlefleck at gmail dot com
+*/
+public final class Rating {
+ public final int score;
+
+ public Rating(int score) {
+ if (score < 0 || score > 100) {
+ throw new IllegalArgumentException("Rating must be between 0 and 100, inclusive");
+ }
+ this.score = score;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Rating rating = (Rating) o;
+
+ if (score != rating.score) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return score;
+ }
+
+ @Override
+ public String toString() {
+ return "Rating{score=" + score + '}';
+ }
+}
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/Song.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/Song.java
new file mode 100644
index 0000000..2685141
--- /dev/null
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/Song.java
@@ -0,0 +1,42 @@
+package org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer;
+
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+public class Song {
+ private final String title;
+ private final String artist;
+
+ public Song(String title, String artist) {
+
+ this.title = title;
+ this.artist = artist;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getArtist() {
+ return artist;
+ }
+}
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/StarRating.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/StarRating.java
new file mode 100644
index 0000000..08fff5f
--- /dev/null
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/StarRating.java
@@ -0,0 +1,39 @@
+package org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer;
+
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 - 2014 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+public enum StarRating {
+
+ FIVE_STARS(5),
+ FOUR_STARS(4),
+ THREE_STARS(3),
+ TWO_STARS(2),
+ ONE_STARS(1),
+ ZERO_STARS(0);
+
+ public final int numberOfStars;
+
+ private StarRating(int stars) {
+ this.numberOfStars = stars;
+ }
+}
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/UserRatedMusicLibrary.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/UserRatedMusicLibrary.java
new file mode 100644
index 0000000..17f5f28
--- /dev/null
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/UserRatedMusicLibrary.java
@@ -0,0 +1,34 @@
+package org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer;
+
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+public interface UserRatedMusicLibrary extends MusicLibrary {
+
+ StarRating userRatingOf(Song song);
+
+ static class StarRatingConverter {
+ public Rating convert(StarRating starRating) {
+ return new Rating(starRating.numberOfStars * 20);
+ }
+ }
+}
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/CloudScrobblingMusicLibrary.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/CloudScrobblingMusicLibrary.java
new file mode 100644
index 0000000..482158f
--- /dev/null
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/CloudScrobblingMusicLibrary.java
@@ -0,0 +1,85 @@
+package org.adoptopenjdk.lambda.tutorial.exercise5.thirdpartyplugin;
+
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer.MusicLibrary;
+import org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer.Rating;
+import org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer.Song;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Random;
+
+public class CloudScrobblingMusicLibrary implements MusicLibrary {
+ private final CloudScrobblingService cloudScrobblingService;
+
+ public CloudScrobblingMusicLibrary() {
+ this.cloudScrobblingService = new CloudScrobblingService();
+ }
+
+ @Override
+ public Collection allSongs() {
+ return cloudScrobblingService.retrieveAllSongs();
+ }
+
+ @Override
+ public int timesPlayed(Song song) {
+ return cloudScrobblingService.retrieveTimesPlayedFromCloud(song);
+ }
+
+ public static final class CloudScrobblingService {
+
+ public int retrieveTimesPlayedFromCloud(Song song) {
+ // Simulate real cloud service by returning a random number
+ return (int) (1 + Math.round(Math.random() % 500));
+ }
+
+ public int retrieveScrobbledRatingOf(Song song) {
+ // Simulate real cloud service by returning a stable but meaningless number
+ switch (song.getTitle()) {
+ case "Candy":
+ return 78;
+ case "A Change Is Gonna Come":
+ return 65;
+ case "Desolation Row":
+ return 34;
+ case "Bad Moon Rising":
+ return 28;
+ case "Eleanor Rigby":
+ return 42;
+ default:
+ return 0;
+ }
+ }
+
+ public Collection retrieveAllSongs() {
+ return Arrays.asList(
+ new Song("A Change Is Gonna Come", "Sam Cooke"),
+ new Song("Bad Moon Rising", "Creedence Clearwater Revival"),
+ new Song("Candy", "Paulo Nutini"),
+ new Song("Desolation Row", "Bob Dylan"),
+ new Song("Eleanor Rigby", "The Beatles"));
+ }
+ }
+}
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/LocalFilesystemMusicLibrary.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/LocalFilesystemMusicLibrary.java
new file mode 100644
index 0000000..5183b80
--- /dev/null
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/LocalFilesystemMusicLibrary.java
@@ -0,0 +1,53 @@
+package org.adoptopenjdk.lambda.tutorial.exercise5.thirdpartyplugin;
+
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer.MusicLibrary;
+import org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer.Song;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class LocalFilesystemMusicLibrary implements MusicLibrary {
+
+
+ private final Set allSongs;
+
+ public LocalFilesystemMusicLibrary(Song... allSongs) {
+ this.allSongs = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(allSongs)));
+ }
+
+ @Override
+ public Collection allSongs() {
+ return allSongs;
+ }
+
+ @Override
+ public int timesPlayed(Song song) {
+ // Could read a local database file to find the number of times played
+ return 4;
+ }
+}
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/UserRatedLocalFilesystemMusicLibrary.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/UserRatedLocalFilesystemMusicLibrary.java
new file mode 100644
index 0000000..c04c713
--- /dev/null
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/UserRatedLocalFilesystemMusicLibrary.java
@@ -0,0 +1,73 @@
+package org.adoptopenjdk.lambda.tutorial.exercise5.thirdpartyplugin;
+
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 - 2014 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer.Song;
+import org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer.StarRating;
+import org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer.UserRatedMusicLibrary;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class UserRatedLocalFilesystemMusicLibrary implements UserRatedMusicLibrary {
+
+ private final Set allSongs = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+ new Song("A Change Is Gonna Come", "Sam Cooke"),
+ new Song("Bad Moon Rising", "Creedence Clearwater Revival"),
+ new Song("Candy", "Paulo Nutini"),
+ new Song("Desolation Row", "Bob Dylan"),
+ new Song("Eleanor Rigby", "The Beatles"))));
+
+ @Override
+ public StarRating userRatingOf(Song song) {
+ // Simulate real user ratings
+ switch (song.getTitle()) {
+ case "Candy":
+ return StarRating.FIVE_STARS;
+ case "A Change Is Gonna Come":
+ return StarRating.FOUR_STARS;
+ case "Desolation Row":
+ return StarRating.THREE_STARS;
+ case "Bad Moon Rising":
+ return StarRating.TWO_STARS;
+ case "Eleanor Rigby":
+ return StarRating.ONE_STARS;
+ default:
+ return StarRating.ZERO_STARS;
+ }
+ }
+
+ @Override
+ public Collection allSongs() {
+ return allSongs;
+ }
+
+ @Override
+ public int timesPlayed(Song song) {
+ // Could read a local database file to find the number of times played
+ return 5;
+ }
+}
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_5_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_5_Test.java
new file mode 100644
index 0000000..51097eb
--- /dev/null
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_5_Test.java
@@ -0,0 +1,255 @@
+package org.adoptopenjdk.lambda.tutorial;
+
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+
+import org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer.StarRating;
+import org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer.UserRatedMusicLibrary;
+import org.adoptopenjdk.lambda.tutorial.exercise5.thirdpartyplugin.CloudScrobblingMusicLibrary;
+import org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer.MusicLibrary;
+import org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer.Rating;
+import org.adoptopenjdk.lambda.tutorial.exercise5.musicplayer.Song;
+import org.adoptopenjdk.lambda.tutorial.exercise5.thirdpartyplugin.LocalFilesystemMusicLibrary;
+import org.adoptopenjdk.lambda.tutorial.exercise5.thirdpartyplugin.UserRatedLocalFilesystemMusicLibrary;
+import org.adoptopenjdk.lambda.tutorial.util.FeatureMatchers;
+import org.adoptopenjdk.lambda.tutorial.util.HasConcreteMethod;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+
+/**
+ * Exercise 5 - Default Methods
+ *
+ * The introduction of Default Methods is intended to allow for libraries to evolve more easily - with the
+ * JDK Collections library as their first main user. They permit adding concrete methods, with an implementation,
+ * to Java interfaces. Prior to JDK 8, every method on an interface had to be abstract, with implementations provided
+ * by classes. With JDK 8, it's possible to declare the following interface:
+ *
+ *
+ *
+ * // (stripped down version of the real Iterable.java)
+ * public interface Iterable {
+ *
+ * Iterator iterator(); // As in prior versions
+ *
+ * // example of a new JDK 8 default method
+ * default void forEach(Consumer super T> action) {
+ * Objects.requireNonNull(action);
+ * for (T t : this) {
+ * action.accept(t);
+ * }
+ * }
+ * }
+ *
+ *
+ * In this case the method forEach is a default method with a concrete implementation. With this
+ * declaration forEach() can be invoked on any implementation of Iterable. This is very
+ * similar to adding a concrete method to an abstract class -- if no implementation overrides it, the code in the method
+ * on the interface is executed. Crucially, the new method can be added without causing compiler errors in the client
+ * code. Consider if a normal forEach method was added to Iterable: every class that
+ * implemented the Iterable interface would now fail to compile. Using default methods, the interface can
+ * be evolved without breaking client code.
+ *
+ * Allowing evolution of a library is the primary use case of default methods.
+ *
+ *
+ *
Rules of Method Lookup
+ *
+ * Inheritance of method implementations is not new to Java. When a subclass method is invoked, the runtime finds the
+ * nearest implementation available. This could be declared on the subclass itself, or on any of it's superclasses, or
+ * even all the way up in java.lang.Object. This last case is what occurs if your class does not override
+ * toString. However, if anywhere in the inheritance hierarchy from your class to Object, toString is
+ * implemented, that would be executed instead of Object's toString. This behaviour is still in
+ * place in JDK 8, but has been augmented to cope with default methods.
+ *
+ *
+ * The main difference between superclass methods and default methods in JDK 8 is that there is only single
+ * inheritance of classes, while there is multiple inheritance of interfaces. As such, there needs to be
+ * some extra rules around method lookup.
+ *
+ * Method lookup has all the following characteristics:
+ *
+ *
For a default method to be invoked, there must be no declaration anywhere in the class hierarchy.
+ *
+ * This can
+ * be thought of like Miranda rights, as in, "You have
+ * the right to a method implementation. If your class hierarchy cannot afford an implementation, one will be
+ * provided for you". A method implementation anywhere in the class hierarchy will take priority over a default
+ * method. This also applies to default methods which match the signature from java.lang.Object; the default method
+ * can never be invoked. As such, declaring a default method which matches a method from Object is a compiler error.
+ *
+ * The closest default method wins.
+ *
+ * As with superclasses, when searching for a concrete method the nearest
+ * superclass wins. For example, given "class A", "class B extends A", and "class C
+ * extends B", if a method is invoked on C, the Java runtime will first look for the implementation in C,
+ * then B, then A, then finally java.lang.Object, invoking the first method it finds. This is also the case with
+ * super-interfaces. Indeed, given "interface X", "interface Y extends X" and "
+ * interface Z extends Y", and declaring "class C extends B, implements Z", the lookup for a
+ * default method would traverse the hierarchy in the following order: C -> B -> A -> Object -> Z -> Y -> X.
+ *
+ * Ambiguous inheritance of a default method must be resolved in the implementing class, at compile time.
+ *
+ * If a class inherits the same default method from more than one source, a compiler error will be emitted. This happens
+ * when unrelated types declare the same method signature, and a class becomes a subtype of more than one of them.
+ * Consider the following example:
+ *
+ * interface A {
+ * default void speak() { System.out.println("A says hi!"; }
+ * }
+ * interface B {
+ * default void speak() { System.out.println("Regards from B!"; }
+ * }
+ * class C implements A, B { } // compiler error: inherits unrelated default methods from A and B
+ *
+ * This principle applies regardless of how deep the interface inheritance hierarchy is, and also applies to
+ * sub-interfaces as well as classes, i.e. "interface Y extends A, B" results in the same compile error.
+ * This error can be resolved by removing ambiguity in the subtype, by overriding the default method. This can be any
+ * compliant implementation, including directly invoking a specific inherited default method. A new syntax in JDK 8 is
+ * available to allow choosing an inherited default method, like so:
+ *
+ * class C implements A, B {
+ * public void speak() { A.super.speak(); } // prints "A says hi!"
+ * }
+ *
+ * In this case, when instanceOfC.speak(); is executed, the default speak from
+ * interface A is invoked. This syntax is also available within default method bodies, allowing an interface
+ * to choose an implementation from one of its super-interfaces. Note that this syntax is not entirely unfamiliar: it
+ * can be considered just like invoking super.someMethod();, except that with the single inheritance of
+ * classes, the name of the super class is can be nothing other than the single superclass, so it can remain implicit.
+ *
+ * It should be noted that ambiguities are never resolved by the order in which interface implementations are
+ * declared (such as with Scala's traits). They must always be resolved explicitly in the subtype.
+ *
+ * Do default methods mean Java supports multiple inheritance?
+ *
+ * In a way. Java has always supported multiple inheritance of interfaces, previously this did not include any of the
+ * implementation, just the contract. Inheritance can be subdivided again, into inheritance of state, and
+ * inheritance of behaviour. Default methods introduce the latter, multiple inheritance of behaviour.
+ *
What about the diamond problem?
+ * There are two aspects to the diamond problem: a) disambiguating implementations and b) handling state. We have seen
+ * how ambiguities are resolved in JDK 8, requiring that users specify the implementation at compile time. Default
+ * methods do not introduce a problem in that aspect of the diamond problem. The second problem, state, is where trickier
+ * issues of the diamond problem live. It can be too easy to accidentally introduce bugs into an implementing class
+ * because it must adhere to the contract defined in the interface and/or superclass, which can include maintaining
+ * state. Any methods which manipulate that state must also be invoked in the implementing class, and accidentally
+ * "losing" that invocation is an easy way to introduce a subtle bug. Also, there is the problem between the ordering of
+ * conflicting methods, that cannot be resolved by disambiguating. Default methods in Java avoid this issue, due to an
+ * existing virtue of interfaces, that they do not contain state. Because an interface cannot be constructed
+ * with fields, there is no state available to encounter this issue. Java still has single inheritance of state,
+ * through superclasses. If the contract of an interface requires state (like, e.g. Iterator) it will be
+ * provided through the single inheritance chain.
+ *
+ *
+ * Note that there are still ways to "simulate" state in interfaces with default methods, since there is no restriction
+ * on accessing static fields defined in other classes. However, that should be avoided for all the reasons that both
+ * multiple inheritance of state, and global mutable state should be avoided.
+ *
+ *
+ * @see Iterable#forEach(Consumer)
+ */
+@SuppressWarnings("unchecked")
+public class Exercise_5_Test {
+
+ /**
+ * Add a default method to {@link MusicLibrary} that returns every song in the library, sorted by artist. You should
+ * NOT need to add a method to any implementation of MusicLibrary.
+ *
+ * There is a helper class within MusicLibrary that can help perform sorting.
+ *
+ * Uncomment the line below that causes a compiler error until the default method is included.
+ *
+ *
+ * @see MusicLibrary#allSongs()
+ * @see MusicLibrary.SongByArtistSorter#sort(java.util.Collection)
+ *
+ */
+ @Test
+ public void useDefaultMethodToReturnPlaylistOrderedByArtist() {
+ MusicLibrary library = new LocalFilesystemMusicLibrary(
+ new Song("A Change Is Gonna Come", "Sam Cooke"),
+ new Song("Bad Moon Rising", "Creedence Clearwater Revival"),
+ new Song("Candy", "Paulo Nutini"),
+ new Song("Desolation Row", "Bob Dylan"),
+ new Song("Eleanor Rigby", "The Beatles")
+ );
+
+// UNCOMMENT THE LINES BELOW
+// Until the sortedByArtist method is added to MusicLibrary, there will be a compiler error.
+// assertThat(library.sortedByArtist(), containsSongsBy("Bob Dylan", "Creedence Clearwater Revival",
+// "Paulo Nutini", "Sam Cooke", "The Beatles"));
+ assertThat(MusicLibrary.class, HasConcreteMethod.called("sortedByArtist"));
+ assertThat(LocalFilesystemMusicLibrary.class, not(HasConcreteMethod.called("sortedByArtist")));
+ }
+
+ /**
+ * Override the default method {@link MusicLibrary#ratingOf(Song)} in {@link CloudScrobblingMusicLibrary} to return
+ * a {@link Rating} based on the rating given by a cloud scrobbling service.
+ *
+ * There is a helper method within {@link CloudScrobblingMusicLibrary.CloudScrobblingService} that can be called
+ * to retrieve the rating from the cloud.
+ *
+ * @see - Definition of Scrobbling - http://www.last.fm/help/faq?category=99
+ * @see MusicLibrary#ratingOf(Song)
+ * @see CloudScrobblingMusicLibrary.CloudScrobblingService#retrieveScrobbledRatingOf(Song)
+ */
+ @Test
+ public void overridesDefaultMethodInClassToProvideCustomSongRatingAlgorithm() {
+ MusicLibrary library = new CloudScrobblingMusicLibrary();
+
+ assertThat(library.ratingOf(new Song("Candy", "Paulo Nutini")), is(new Rating(78)));
+ assertThat(CloudScrobblingMusicLibrary.class, HasConcreteMethod.called("ratingOf"));
+ }
+
+ /**
+ * Override the default method {@link MusicLibrary#ratingOf(Song)} in {@link UserRatedMusicLibrary} to return
+ * a {@link Rating} based on the {@link StarRating} entered by the user.
+ *
+ * The method {@link UserRatedMusicLibrary#userRatingOf(Song)} provides a user-entered rating that can be converted
+ * to a Rating type with the {@link UserRatedMusicLibrary.StarRatingConverter#convert(StarRating)} method.
+ *
+ */
+ @Test
+ public void overrideDefaultMethodInInterfaceToProvideUserEnteredSongRatings() {
+ MusicLibrary library = new UserRatedLocalFilesystemMusicLibrary();
+
+ assertThat(library.ratingOf(new Song("Desolation Row", "Bob Dylan")), is(new Rating(60)));
+ assertThat(UserRatedMusicLibrary.class, HasConcreteMethod.called("ratingOf"));
+ }
+
+ private Matcher songBy(String artist) {
+ return FeatureMatchers.from(equalTo(artist), "a song by", "artist", Song::getArtist);
+ }
+
+}
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/util/HasConcreteMethod.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/HasConcreteMethod.java
new file mode 100644
index 0000000..e5da61c
--- /dev/null
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/util/HasConcreteMethod.java
@@ -0,0 +1,99 @@
+package org.adoptopenjdk.lambda.tutorial.util;
+
+/*
+ * #%L
+ * lambda-tutorial
+ * %%
+ * Copyright (C) 2013 Adopt OpenJDK
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.io.IOException;
+
+public class HasConcreteMethod extends TypeSafeDiagnosingMatcher> {
+ private final String methodName;
+
+ public HasConcreteMethod(String defaultMethodName) {
+ this.methodName = defaultMethodName;
+ }
+
+ public static Matcher> called(String defaultMethodName) {
+ return new HasConcreteMethod(defaultMethodName);
+ }
+
+ @Override
+ protected boolean matchesSafely(Class> item, Description mismatchDescription) {
+ if (!hasConcreteMethod(methodName, item)) {
+ mismatchDescription.appendText("did not have default method named ").appendText(methodName);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("a type with a default method called " + methodName);
+ }
+
+ private boolean hasConcreteMethod(String defaultMethodName, Class> clazz) {
+ try {
+
+ String resourceName = clazz.getName().replace(".", "/").concat(".class");
+ ClassReader reader = new ClassReader(clazz.getClassLoader().getResourceAsStream(resourceName));
+ HasDefaultMethodVisitor sourceFileNameVisitor = new HasDefaultMethodVisitor(defaultMethodName);
+ reader.accept(sourceFileNameVisitor, 0);
+
+ return sourceFileNameVisitor.defaultMethodExists();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ private static final class HasDefaultMethodVisitor extends ClassVisitor {
+
+ private final String defaultMethodName;
+ private boolean visitedYet = false;
+ private boolean hasDefaultMethod = false;
+
+ public HasDefaultMethodVisitor(String defaultMethodName) {
+ super(Opcodes.ASM5);
+ this.defaultMethodName = defaultMethodName;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ visitedYet = true;
+ hasDefaultMethod |= name.equals(defaultMethodName) && ((access & Opcodes.ACC_ABSTRACT) == 0);
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+
+ public boolean defaultMethodExists() {
+ if (!visitedYet) throw new IllegalStateException("Must visit a class before asking for result");
+ return this.hasDefaultMethod;
+ }
+ }
+}
From 68385624a55bec7be189733743f5a46098160e0a Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Thu, 9 Apr 2020 13:47:37 +0100
Subject: [PATCH 48/49] Fill out the postjava8 solutions for the default method
exercise
---
.../exercise5/musicplayer/MusicLibrary.java | 4 ++++
.../musicplayer/UserRatedMusicLibrary.java | 4 ++++
.../CloudScrobblingMusicLibrary.java | 5 +++++
.../lambda/tutorial/Exercise_5_Test.java | 13 ++++++++++---
4 files changed, 23 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/MusicLibrary.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/MusicLibrary.java
index 830ac65..5be9ff2 100644
--- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/MusicLibrary.java
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/MusicLibrary.java
@@ -56,5 +56,9 @@ default Rating ratingOf(Song song) {
return new Rating(Math.round(score));
}
+ default List sortedByArtist() {
+ return SongByArtistSorter.sort(allSongs());
+ }
+
int timesPlayed(Song song);
}
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/UserRatedMusicLibrary.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/UserRatedMusicLibrary.java
index 17f5f28..c725b17 100644
--- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/UserRatedMusicLibrary.java
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/musicplayer/UserRatedMusicLibrary.java
@@ -31,4 +31,8 @@ public Rating convert(StarRating starRating) {
return new Rating(starRating.numberOfStars * 20);
}
}
+
+ default Rating ratingOf(Song song) {
+ return new StarRatingConverter().convert(userRatingOf(song));
+ }
}
diff --git a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/CloudScrobblingMusicLibrary.java b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/CloudScrobblingMusicLibrary.java
index 482158f..8e2b47c 100644
--- a/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/CloudScrobblingMusicLibrary.java
+++ b/src/main/java/org/adoptopenjdk/lambda/tutorial/exercise5/thirdpartyplugin/CloudScrobblingMusicLibrary.java
@@ -82,4 +82,9 @@ public Collection retrieveAllSongs() {
new Song("Eleanor Rigby", "The Beatles"));
}
}
+
+ @Override
+ public Rating ratingOf(Song song) {
+ return new Rating(cloudScrobblingService.retrieveScrobbledRatingOf(song));
+ }
}
diff --git a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_5_Test.java b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_5_Test.java
index 51097eb..540edf4 100644
--- a/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_5_Test.java
+++ b/src/test/java/org/adoptopenjdk/lambda/tutorial/Exercise_5_Test.java
@@ -36,6 +36,7 @@
import org.hamcrest.Matcher;
import org.junit.Test;
+import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -192,7 +193,7 @@ public class Exercise_5_Test {
*
*
* @see MusicLibrary#allSongs()
- * @see MusicLibrary.SongByArtistSorter#sort(java.util.Collection)
+ * @see MusicLibrary.SongByArtistSorter#sort(Collection)
*
*/
@Test
@@ -207,8 +208,8 @@ public void useDefaultMethodToReturnPlaylistOrderedByArtist() {
// UNCOMMENT THE LINES BELOW
// Until the sortedByArtist method is added to MusicLibrary, there will be a compiler error.
-// assertThat(library.sortedByArtist(), containsSongsBy("Bob Dylan", "Creedence Clearwater Revival",
-// "Paulo Nutini", "Sam Cooke", "The Beatles"));
+ assertThat(library.sortedByArtist(), containsSongsBy("Bob Dylan", "Creedence Clearwater Revival",
+ "Paulo Nutini", "Sam Cooke", "The Beatles"));
assertThat(MusicLibrary.class, HasConcreteMethod.called("sortedByArtist"));
assertThat(LocalFilesystemMusicLibrary.class, not(HasConcreteMethod.called("sortedByArtist")));
}
@@ -252,4 +253,10 @@ private Matcher songBy(String artist) {
return FeatureMatchers.from(equalTo(artist), "a song by", "artist", Song::getArtist);
}
+ private Matcher super List> containsSongsBy(String... artists) {
+ List> songMatchers = Stream.of(artists).map(this::songBy).collect(Collectors.toList());
+ return contains(songMatchers);
+ }
+
+
}
From e550dbbe013040c53adc419b1149fd0da8b382cd Mon Sep 17 00:00:00 2001
From: Graham Allan
Date: Sat, 11 Apr 2020 11:55:24 +0100
Subject: [PATCH 49/49] Travis no longer supports oraclejdk.
---
.travis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index be8abd4..f331bf5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,5 @@
language: java
jdk:
- - oraclejdk8
+ - openjdk8
script: "mvn clean test"