Last active
January 11, 2016 19:31
-
-
Save DarrenBishop/a84f8d40a1e800d37675 to your computer and use it in GitHub Desktop.
NullSafeCoalescer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.darrenbishop | |
/** | |
* Created by Darren on 02/04/2015. | |
*/ | |
object Fixture { | |
import XorSet._ | |
sealed trait Country { self:Country => | |
sealed trait City | |
} | |
case object UK extends Country { | |
case object London extends City | |
case object Manchester extends City | |
case object Liverpool extends City | |
} | |
case object USA extends Country { | |
case object Washington extends City | |
case object NewYork extends City | |
case object MiamiBeach extends City | |
} | |
case object HK extends Country { | |
case object Central extends City | |
case object Kowloon extends City | |
} | |
case class Address(country: Country, city: Country#City=null) | |
case class Person(firstname: String, lastname: String, address: Address=null) | |
case class Department(name: String) | |
case class Employee(person: Person, department: Department) | |
case class Company(name: String, departments:Set[Department] = Set.empty, employees:Set[Employee] = Set.empty) { | |
def expand(department:String) = { | |
departments.find { _.name == department } match { | |
case None => Company(name, departments + Department(department), employees) | |
case Some(d) => this | |
} | |
} | |
def contract(department:String):Company = { | |
departments.find { _.name == department } match { | |
case None => this | |
case Some(d) => Company(name, departments - d, employees &~ employees.filter(_.department == d)) | |
} | |
} | |
def fire(person: Person) = employees find {_.person == person} match { | |
case None => this | |
case Some(e) => Company(name, departments, employees - e) | |
} | |
def fire(people: Person*) = employees filterNot( e => people contains e.person) match { | |
case `employees` => this | |
case s => Company(name, departments, s) | |
} | |
def employ(person:Person, department:String):Company = { | |
(departments.find { _.name == department }, employees find {_.person == person }) match { | |
case (None, _) => this | |
case (Some(d), Some(e)) => Company(name, departments, employees &! Set(e, Employee(person, d))) | |
case (Some(d), None) => Company(name, departments, employees + Employee(person, d)) | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Created by Darren on 02/04/2015. | |
*/ | |
package com.darrenbishop | |
object NullSafeCoalescer { | |
case class NSCOps[E >: Nothing](oe: Option[E]) { | |
def ?>[R >: Nothing](f: E => R): Option[R] = oe match { | |
//oe map f | |
// At the time of writing, map uses Some(...) instead of Option(...) which can lead to Some(null) being returned | |
case None => None | |
case Some(e) => Option(f(e)) | |
} | |
def ?~(f: E => Boolean): Option[E] = { | |
oe filter f | |
} | |
def ??[P >: Nothing](p: => P): Option[E] = { | |
oe filter { _ == p } | |
} | |
def ?![P >: Nothing](p: => P): Option[E] = { | |
oe filterNot { _ == p } | |
} | |
} | |
@inline implicit def anyToNSCOps[E >: Nothing](oe: Option[E]): NSCOps[E] = NSCOps(oe) | |
@inline implicit def anyToNSCOps[E >: Nothing](e: E): NSCOps[E] = NSCOps(Option(e)) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.darrenbishop | |
import org.scalatest.FlatSpec | |
import util.UnitSpec | |
class NullSafeCoalescerSpec extends FlatSpec with UnitSpec { | |
import NullSafeCoalescer._ | |
import Fixture._ | |
val jd = Person("John", "Doe", address = null) | |
val js = Person("John", "Smith", Address(UK, UK.Manchester)) | |
val co = Company("ACME International", Set(Department("hr"), Department("accounting"))).employ(jd, "hr").employ(js, "accounting") | |
"NullSafeCoalescer" should "extract Mr Doe from ACME Int" in { | |
co ?> { _.employees.head } ?> { _.person } should be(Some(Person("John", "Doe", null))) | |
co ?> { _.employees.head } ?> { _.person } should be(Some(jd)) | |
} | |
it should "not extract anything on a null company" in { | |
def employee(co: Company) = co.employees.head | |
def person(emp: Employee) = emp.person | |
def address(p: Person) = p.address | |
val nullCo: Company = null | |
nullCo ?> employee ?> person ?> address should be(None) | |
nullCo ?> { _.employees.head } ?> { _.person } ?> { _.address } should be(None) | |
} | |
it should "not extract Mr Doe's address" in { | |
co ?> { _.employees.head } ?> { _.person } ?> { _.address } should be(None) | |
} | |
it should "not extract Mr Doe's city" in { | |
co ?> { _.employees.head } ?> { _.person } ?> { _.address } ?> { _.city } should be(None) | |
} | |
it should "not extract and stringify Mr Doe's city" in { | |
co ?> { _.employees.head } ?> { _.person } ?> { _.address } ?> { _.city } ?> { _.toString } should be(None) | |
} | |
it should "still not extract Mr Doe's address, even if he is Mr Doe" in { | |
co ?> { _.employees.head } ?> { _.person } ?~ { _.lastname == "Doe" } ?> { _.address } ?> { _.city } ?> { _.toString } should be(None) | |
} | |
"NullSafeCoalescer" should "extract Mr Smith from ACME Int" in { | |
co ?> { _.employees.tail.head } ?> { _.person } should be(Some(js)) | |
} | |
it should "extract Mr Smith's address" in { | |
co ?> { _.employees.tail.head } ?> { _.person } ?> { _.address } should be(Some(Address(UK, UK.Manchester))) | |
} | |
it should "extract Mr Smith's City" in { | |
co ?> { _.employees.tail.head } ?> { _.person } ?> { _.address } ?> { _.city } should be(Some(UK.Manchester)) | |
} | |
it should "extract and stringfy Mr Smith's City" in { | |
co ?> { _.employees.tail.head } ?> { _.person } ?> { _.address } ?> { _.city } ?> { _.toString } should be(Some("Manchester")) | |
} | |
it should "extract and stringify Mr Smith's City if he's from Manchester, UK" in { | |
co ?> { _.employees.tail.head } ?> { _.person } ?> { _.address } ?? { Address(UK, UK.Manchester) } ?> { _.city } ?> { _.toString } should be(Some("Manchester")) | |
} | |
it should "not extract and stringify Mr Smith's City if he's not from London, UK" in { | |
co ?> { _.employees.tail.head } ?> { _.person } ?> { _.address } ?? { Address(UK, UK.London) } ?> { _.city } ?> { _.toString } should be(None) | |
} | |
it should "not extract and stringify Mr Smith's City if he is from Manchester, UK" in { | |
co ?> { _.employees.tail.head } ?> { _.person } ?> { _.address } ?! { Address(UK, UK.Manchester) } ?> { _.city } ?> { _.toString } should be(None) | |
} | |
it should "not extract and stringify Mr Smith's City if he's not Mr Doe" in { | |
co ?> { _.employees.tail.head } ?> { _.person } ?~ { _.lastname == "Doe" } ?> { _.address } ?> { _.city } ?> { _.toString } should be(None) | |
} | |
it should "comment on the weather if Mr Smith is from Manchester, UK" in { | |
co ?> { _.employees.tail.head } ?> { _.person } ?> { _.address } ?? { Address(UK, UK.Manchester) } match { | |
case None => fail | |
case Some(_) => println("It's normally rainy") | |
} | |
} | |
it should "comment on the night-sky if Mr Smith is not from London, UK" in { | |
co ?> { _.employees.tail.head } ?> { _.person } ?> { _.address } ?? { Address(UK, UK.London) } match { | |
case None => println("You can see so many stars when not in the big city") | |
case Some(_) => fail | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object NullSafeCoalescer { | |
... | |
@inline implicit def anyToOption[E >: Nothing](e: E): Option[E] = Option(e) | |
... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if (root != null && root.level1 != null && root.level1.level2 != null && root.level1.level2.level3 != null && root.level1.level2.level3.finalPropertyMightYetBeNull != null) | |
println("Finally we are safe: " + root.level1.level2.level3.finalPropertyMightYetBeNull) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
root ?> _.level1 ?> _.level2 ?> _.level3 ?> _.finalPropertyMightYetBeNull match { | |
case Some(butItAintNull) => println("Safe Safe Safe: " + butItAintNull) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment