From 5c61db42c44afed9b9171f5b568b06cead1b96e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Budnik?= Date: Mon, 16 Feb 2015 17:02:14 +0100 Subject: [PATCH] added Scala support (major feature) and added Log4j2 logging --- .gitignore | 2 +- README.md | 47 ++++++++++++++--- build.gradle | 18 +++++-- .../com/github/lukaszbudnik/jelvis/Elvis.java | 13 ++++- .../jelvis/ElvisScalaToJavaConverters.scala | 22 ++++++++ .../github/lukaszbudnik/jelvis/ElvisTest.java | 42 ++-------------- .../com/github/lukaszbudnik/jelvis/Model.java | 50 +++++++++++++++++++ src/test/resources/log4j2-test.xml | 27 ++++++++++ .../lukaszbudnik/jelvis/ElvisSpec.scala | 47 +++++++++++++++++ 9 files changed, 216 insertions(+), 52 deletions(-) create mode 100644 src/main/scala/com/github/lukaszbudnik/jelvis/ElvisScalaToJavaConverters.scala create mode 100644 src/test/java/com/github/lukaszbudnik/jelvis/Model.java create mode 100644 src/test/resources/log4j2-test.xml create mode 100644 src/test/scala/com/github/lukaszbudnik/jelvis/ElvisSpec.scala diff --git a/.gitignore b/.gitignore index 94d47e4..5b7d7ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,9 @@ .idea/ build/ +target/ .gradle/ .DS_Store jelvis.iml -gugis.iml gradle/ gradlew gradlew.bat diff --git a/README.md b/README.md index b8da533..c09d8f1 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ jelvis [![Build Status](https://travis-ci.org/lukaszbudnik/jelvis.svg?branch=master)](https://travis-ci.org/lukaszbudnik/jelvis) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.lukaszbudnik.jelvis/jelvis/badge.svg?style=flat)](https://maven-badges.herokuapp.com/maven-central/com.github.lukaszbudnik.jelvis/jelvis) ============================== -Elvis operator in Java 8! Say no to NPE in chained calls! +Elvis operator in Java 8 & Scala! Say no to NPE in chained calls! # Getting started +## Java 8 + 1. Add jelvis to your project -2. Use the static ```elvis``` method like this: +2. Use the static `elvis` method like this: ``` import static com.github.lukaszbudnik.jelvis.Elvis.elvis; @@ -15,11 +17,13 @@ Person person = new Person(); String isoCode = elvis(person, p -> p.getAddress().getCountry().getISOCode()); ``` -The first argument to ```elvis``` method is the root object and the second one is the function to be evaluated. +The first argument to `elvis` method is the root object and the second one is the function to be evaluated. -If either ```person``` is null or ```getAddress()``` or ```getCountry()``` returns null, the whole call returns null. +If either `person` is null or `getAddress()` or `getCountry()` returns null, the whole call returns null. -No ```NullPointerException``` is thrown. All other exceptions are preserved. However, Java 8 lambdas have some problems with functions throwing checked exceptions. If your methods throw checked exception you need to use ```wrappedFunction``` like this: +No `NullPointerException` is thrown. All other exceptions are preserved. +However, Java 8 lambdas have some problems with functions throwing checked exceptions. +If your methods throw checked exception you need to use `wrappedFunction` like this: ``` import static com.github.lukaszbudnik.jelvis.Elvis.elvis; @@ -29,11 +33,37 @@ Person person = new Person(); String line2 = elvis(person, wrappedFunction(p -> p.getAddress().getLine2())); ``` +## Scala + +There is an implicit converter which converts Scala `Function1` into Java 8 `Function`. +All you have to do is import it in your code: + +``` +import com.github.lukaszbudnik.jelvis.Elvis._ +import com.github.lukaszbudnik.jelvis.ElvisScalaToJavaConverters._ +``` + +And then just use the `elvis` method in your code: + +``` +val person = new Person +val isoCode = elvis(person, (p: Person) => p.getAddress.getCountry.getISOCode) +``` + +Exceptions? In Scala all exceptions are unchecked. You don't have to worry about them at all. +For example `p.getAddress.getLine2` doesn't have to be wrapped with `wrappedFunction`. +Thus, when running from Scala, you will never see `ElvisException`. +The call simply looks like this: + + ``` + elvis(person, (p: Person) => p.getAddress.getLine2) + ``` + See examples for more information. # Examples -See `src/test/java` for unit tests and examples. +See `src/test/java` for Java unit tests and `src/test/scala` for Scala ones. # Download @@ -43,11 +73,12 @@ Use the following Maven dependency: com.github.lukaszbudnik.jelvis jelvis - 1.1 + {version} ``` -or open [search.maven.org](http://search.maven.org/#artifactdetails|com.github.lukaszbudnik.jelvis|jelvis|1.1|jar) and copy and paste dependency id for your favourite dependency management tool (Gradle (jelvis uses Gradle), Buildr, Ivy, sbt, Leiningen, etc). +or open [search.maven.org](http://search.maven.org/#search|ga|1|com.github.lukaszbudnik.jelvis) +and copy and paste dependency id for your favourite dependency management tool (Gradle (jelvis uses Gradle), Buildr, Ivy, sbt, Leiningen, etc). # License diff --git a/build.gradle b/build.gradle index 81d60e7..148849e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id 'java' + id 'scala' id 'jacoco' id 'maven' id 'signing' @@ -11,7 +12,7 @@ targetCompatibility = JavaVersion.VERSION_1_8 group = 'com.github.lukaszbudnik.jelvis' archivesBaseName = 'jelvis' -version = '1.1' +version = '1.2' repositories { mavenCentral() @@ -42,7 +43,13 @@ test { } dependencies { - testCompile group: 'junit', name: 'junit', version: '4.12' + compile 'org.apache.logging.log4j:log4j-core:2.1' + compile 'org.scala-lang:scala-library:2.11.5' + + testCompile 'org.specs2:specs2-core_2.11:2.4.16' + testCompile 'org.specs2:specs2-junit_2.11:2.4.16' + testCompile 'org.apache.logging.log4j:log4j-slf4j-impl:2.1' + testCompile 'junit:junit:4.12' } license { @@ -58,6 +65,11 @@ task sourcesJar(type: Jar) { from sourceSets.main.allSource } +task testJar(type: Jar) { + classifier = 'tests' + from sourceSets.test.allSource +} + task javadocJar(type: Jar) { classifier = 'javadoc' from javadoc @@ -78,7 +90,7 @@ task printTestErrors { } artifacts { - archives javadocJar, sourcesJar + archives javadocJar, sourcesJar, testJar } signing { diff --git a/src/main/java/com/github/lukaszbudnik/jelvis/Elvis.java b/src/main/java/com/github/lukaszbudnik/jelvis/Elvis.java index da3276b..622a6f9 100644 --- a/src/main/java/com/github/lukaszbudnik/jelvis/Elvis.java +++ b/src/main/java/com/github/lukaszbudnik/jelvis/Elvis.java @@ -9,30 +9,39 @@ */ package com.github.lukaszbudnik.jelvis; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.util.function.Function; public final class Elvis { + private static final Logger log = LogManager.getLogger(Elvis.class); + private Elvis() { } public static R elvis(T t, Function f) { try { + log.trace("About to apply object " + t + " on function " + f); return f.apply(t); } catch (NullPointerException e) { + log.debug("NullPointerException caught - gracefully returning null instead", e); return null; } } public static Function wrappedFunction(FunctionThrowingException f) { + log.trace("Wrapping function " + f); return nf -> { try { + log.trace("Delegating function " + f + " to function " + nf); return f.apply(nf); } catch (RuntimeException e) { - // unchecked exceptions are re-thrown + log.debug("RuntimeException caught - re-throwing as is", e); throw e; } catch (Exception e) { - // checked exceptions are wrapped into ElvisException for better traceability + log.debug("Checked Exception caught - wrapping into ElvisException and re-throwing", e); throw new ElvisException("Checked exception was thrown", e); } }; diff --git a/src/main/scala/com/github/lukaszbudnik/jelvis/ElvisScalaToJavaConverters.scala b/src/main/scala/com/github/lukaszbudnik/jelvis/ElvisScalaToJavaConverters.scala new file mode 100644 index 0000000..f1c2986 --- /dev/null +++ b/src/main/scala/com/github/lukaszbudnik/jelvis/ElvisScalaToJavaConverters.scala @@ -0,0 +1,22 @@ +/** + * Copyright (C) 2015 Łukasz Budnik + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +package com.github.lukaszbudnik.jelvis + +import java.util.function.{Function => JFunction} + +object ElvisScalaToJavaConverters { + + implicit def toJavaFunction[A, B](f: Function1[A, B]) = new JFunction[A, B] { + override def apply(a: A): B = f(a) + + def fromScala: Boolean = true + } + +} diff --git a/src/test/java/com/github/lukaszbudnik/jelvis/ElvisTest.java b/src/test/java/com/github/lukaszbudnik/jelvis/ElvisTest.java index 1ee96d1..c82d197 100644 --- a/src/test/java/com/github/lukaszbudnik/jelvis/ElvisTest.java +++ b/src/test/java/com/github/lukaszbudnik/jelvis/ElvisTest.java @@ -9,6 +9,8 @@ */ package com.github.lukaszbudnik.jelvis; +import com.github.lukaszbudnik.jelvis.Model.Address; +import com.github.lukaszbudnik.jelvis.Model.Person; import org.junit.Assert; import org.junit.Test; @@ -28,9 +30,9 @@ public void shouldReturnNotNullWhenAllIsGood() { @Test public void shouldReturnNotNullWhenAllIsGoodWrappedFunction() { Person person = new Person(); - int ig = elvis(person, wrappedFunction(p -> p.getIQ())); + int iq = elvis(person, wrappedFunction(p -> p.getIQ())); - Assert.assertEquals(100, ig); + Assert.assertEquals(100, iq); } @Test @@ -91,41 +93,5 @@ public void shouldThrowRuntimeExceptionCorrectlyEvenIfMethodDeclaresCheckedExcep } } - class Country { - String getISOCode() { - return "PL"; - } - } - - class Address { - Country getCountry() { - return null; - } - - String getGeoLocation() throws Exception { - throw new RuntimeException("Geo location declares Exception but is throwing runtime exception"); - } - - // throws unchecked exception - String getLine1() { - throw new RuntimeException("getLine1 threw runtime exception"); - } - - // throws checked exception - String getLine2() throws Exception { - throw new Exception("getLine2 threw checked exception"); - } - } - - class Person { - Address getAddress() { - return new Address(); - } - - int getIQ() throws Exception { - return 100; - } - } - } diff --git a/src/test/java/com/github/lukaszbudnik/jelvis/Model.java b/src/test/java/com/github/lukaszbudnik/jelvis/Model.java new file mode 100644 index 0000000..a0e1b1f --- /dev/null +++ b/src/test/java/com/github/lukaszbudnik/jelvis/Model.java @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2015 Łukasz Budnik + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +package com.github.lukaszbudnik.jelvis; + +final class Model { + + static class Country { + String getISOCode() { + return "PL"; + } + } + + static class Address { + Country getCountry() { + return null; + } + + String getGeoLocation() throws Exception { + throw new RuntimeException("Geo location declares Exception but is throwing runtime exception"); + } + + // throws unchecked exception + String getLine1() { + throw new RuntimeException("getLine1 threw runtime exception"); + } + + // throws checked exception + String getLine2() throws Exception { + throw new Exception("getLine2 threw checked exception"); + } + } + + static class Person { + Address getAddress() { + return new Address(); + } + + int getIQ() throws Exception { + return 100; + } + } + +} diff --git a/src/test/resources/log4j2-test.xml b/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000..2cecf33 --- /dev/null +++ b/src/test/resources/log4j2-test.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/scala/com/github/lukaszbudnik/jelvis/ElvisSpec.scala b/src/test/scala/com/github/lukaszbudnik/jelvis/ElvisSpec.scala new file mode 100644 index 0000000..7d20d48 --- /dev/null +++ b/src/test/scala/com/github/lukaszbudnik/jelvis/ElvisSpec.scala @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2015 Łukasz Budnik + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +package com.github.lukaszbudnik.jelvis + + +import com.github.lukaszbudnik.jelvis.Elvis._ +import com.github.lukaszbudnik.jelvis.ElvisScalaToJavaConverters._ +import com.github.lukaszbudnik.jelvis.Model._ +import org.junit.runner.RunWith +import org.specs2.mutable.Specification +import org.specs2.runner.JUnitRunner + +@RunWith(classOf[JUnitRunner]) +class ElvisSpec extends Specification { + + "Elvis" should { + "return expected values when all is good" in { + val person: Person = new Person + + val iq: Int = elvis(person, (p: Person) => p.getIQ) + + iq must beEqualTo(100) + } + + "return null when chained call throws NPE" in { + val person = new Person + val isoCode = elvis(person, (p: Person) => p.getAddress.getCountry.getISOCode) + + isoCode must beNull + } + + "throw ElvisException when chained call throws checked exception" in { + val person = new Person + + elvis(person, (p: Person) => p.getAddress.getLine2) must throwAn[Exception]("getLine2 threw checked exception") + } + + } + +}