Skip to content

Commit

Permalink
Merge pull request #2 from lukaszbudnik/scala-support
Browse files Browse the repository at this point in the history
Scala support
  • Loading branch information
lukaszbudnik committed Feb 16, 2015
2 parents f5f887d + 5c61db4 commit 312bc00
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 52 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
.idea/
build/
target/
.gradle/
.DS_Store
jelvis.iml
gugis.iml
gradle/
gradlew
gradlew.bat
Expand Down
47 changes: 39 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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

Expand All @@ -43,11 +73,12 @@ Use the following Maven dependency:
<dependency>
<groupId>com.github.lukaszbudnik.jelvis</groupId>
<artifactId>jelvis</artifactId>
<version>1.1</version>
<version>{version}</version>
</dependency>
```

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

Expand Down
18 changes: 15 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id 'java'
id 'scala'
id 'jacoco'
id 'maven'
id 'signing'
Expand All @@ -11,7 +12,7 @@ targetCompatibility = JavaVersion.VERSION_1_8

group = 'com.github.lukaszbudnik.jelvis'
archivesBaseName = 'jelvis'
version = '1.1'
version = '1.2'

repositories {
mavenCentral()
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -78,7 +90,7 @@ task printTestErrors {
}

artifacts {
archives javadocJar, sourcesJar
archives javadocJar, sourcesJar, testJar
}

signing {
Expand Down
13 changes: 11 additions & 2 deletions src/main/java/com/github/lukaszbudnik/jelvis/Elvis.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T, R> R elvis(T t, Function<T, R> 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 <T, R> Function<T, R> wrappedFunction(FunctionThrowingException<T, R> 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);
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright (C) 2015 Łukasz Budnik <[email protected]>
*
* 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
}

}
42 changes: 4 additions & 38 deletions src/test/java/com/github/lukaszbudnik/jelvis/ElvisTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand Down Expand Up @@ -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;
}
}

}

50 changes: 50 additions & 0 deletions src/test/java/com/github/lukaszbudnik/jelvis/Model.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright (C) 2015 Łukasz Budnik <[email protected]>
*
* 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;
}
}

}
27 changes: 27 additions & 0 deletions src/test/resources/log4j2-test.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2015 Łukasz Budnik <[email protected]>
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.
-->
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %c{1.} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="com.github.lukaszbudnik.jelvis" level="debug" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Loading

0 comments on commit 312bc00

Please sign in to comment.