There is a library for JodaTime that provides Hibernate persistence. Recently I started looking at Joda-Money and started to see how that can be persisted using hibernate and I do not see any library.
Any suggestions?
Since the link to the example in Sudarshan's answer is broken, here is an implementation of a simple custom user type for org.joda.money.BigMoney, that persists money objects in two columns amount and currency) and an example of how to use it. It works the same for org.joda.money.Money.
package test;
import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Currency;
import org.apache.commons.lang.ObjectUtils;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.Type;
import org.hibernate.usertype.CompositeUserType;
import org.joda.money.BigMoney;
import org.joda.money.CurrencyUnit;
public class MoneyUserType implements CompositeUserType
{
private static final String[] PROPERTY_NAMES = {"amount", "currencyUnit"};
private static final Type[] PROPERTY_TYPES = {StandardBasicTypes.BIG_DECIMAL, StandardBasicTypes.CURRENCY};
public MoneyUserType()
{
super();
}
public Object assemble(final Serializable cached, final SessionImplementor session, final Object owner)
throws HibernateException
{
return cached;
}
public Serializable disassemble(final Object value, final SessionImplementor session) throws HibernateException
{
return (Serializable) value;
}
public String[] getPropertyNames()
{
return PROPERTY_NAMES.clone();
}
public Type[] getPropertyTypes()
{
return PROPERTY_TYPES.clone();
}
public Object getPropertyValue(final Object component, final int property) throws HibernateException
{
BigMoney money = (BigMoney) component;
return (property == 0) ? money.getAmount() : money.getCurrencyUnit().toCurrency();
}
public Object nullSafeGet(final ResultSet rs, final String[] names, final SessionImplementor session,
final Object owner) throws HibernateException, SQLException
{
BigDecimal amount = StandardBasicTypes.BIG_DECIMAL.nullSafeGet(rs, names[0], session);
Currency currency = StandardBasicTypes.CURRENCY.nullSafeGet(rs, names[1], session);
return BigMoney.of(CurrencyUnit.of(currency), amount);
}
public void nullSafeSet(final PreparedStatement st, final Object value, final int index,
final SessionImplementor session) throws HibernateException, SQLException
{
BigMoney money = (BigMoney) value;
BigDecimal amount = (money == null) ? null : money.getAmount();
Currency currency = (money == null) ? null : money.getCurrencyUnit().toCurrency();
StandardBasicTypes.BIG_DECIMAL.nullSafeSet(st, amount, index, session);
StandardBasicTypes.CURRENCY.nullSafeSet(st, currency, index + 1, session);
}
public Object replace(final Object original, final Object target, final SessionImplementor session,
final Object owner) throws HibernateException
{
return deepCopy(original);
}
public void setPropertyValue(final Object component, final int property, final Object value)
throws HibernateException
{
throw new HibernateException("Money is immutable.");
}
public Object deepCopy(final Object value) throws HibernateException
{
return (value != null) ? BigMoney.of(((BigMoney) value).getCurrencyUnit(),
((BigMoney) value).getAmount()) : null;
}
public boolean equals(final Object x, final Object y) throws HibernateException
{
return ObjectUtils.equals(x, y);
}
public int hashCode(final Object x) throws HibernateException
{
return ObjectUtils.hashCode(x);
}
public boolean isMutable()
{
return false;
}
public Class<?> returnedClass()
{
return BigMoney.class;
}
}
Usage:
#Type(type = "test.MoneyUserType")
#Columns(columns = {#Column(name = "AMOUNT"), #Column(name = "CURRENCY")})
private BigMoney money;
The User Type project includes Joda Money support.
The User Type project provides support for joda-money 0.6 since version 3.0.0. Please note however that this requires Hibernate 4. Also the current joda-money version is 0.8
If you want to use it with Hibernate 3 use the example in Sudarshan anwser (it's bugged at the time of writing).
Okay I took your advice and cooked up a custom type for Money as defined in the Joda Library, as a reference people can look it up here,usage here and test for the custom type here
Based on http://jadira.sourceforge.net
Money Types typically consist of a currency and amount. Jadira makes it possible to store only the amount to the database with the currency configured using a parameter. For example:
#Column
#Type(type = "org.jadira.usertype.moneyandcurrency.joda.PersistentMoneyAmount",
parameters = {#org.hibernate.annotations.Parameter(name = "currencyCode", value = "USD")})
private Money money;
Alternatively, with other types two columns to hold the amount an currency:
#Columns(columns = { #Column(name = "MY_CURRENCY"), #Column(name = "MY_AMOUNT") })
#Type(type = "org.jadira.usertype.moneyandcurrency.joda.PersistentMoneyAmountAndCurrency")
private Money money;
Joda-Money's very new, so it's no surprise that noone's provided a Hibernate mapping for it yet.
However, writing custom Hibernate type adapters is pretty straightforward. If you look at the source for the JodaTime adapters, you'll see they're really simple. See the docs for how to write your own.
Related
I am getting this annoying JPA validation error in Eclipse but cannot find where it defined anywhere, when the class is opened no error shows. But they do appear in the package explorer/problems tab.
Heres an example of where CurrencyType.class is defined...
/**
* UserSubscription
*
*/
#Entity
#Table(name = "user_subscriptions", schema = "public")
#SqlResultSetMapping(name = "userSubsWithCount",
entities = {#EntityResult(entityClass = UserSubscription.class)},
columns = {#ColumnResult(name = "TOTALCOUNT")}
)
#TypeDefs({
#TypeDef(name="currency_enum",typeClass=CurrencyType.class),
#TypeDef(name="sub_option_enum",typeClass=SubscriptionOptionType.class),
#TypeDef(name="sub_state_enum",typeClass=SubscriptionStateType.class)
})
public class UserSubscription implements java.io.Serializable {
And the class itself (in a different package but same .jar)
public class CurrencyType extends org.hibernate.type.EnumType<Currency> {
/**
*
*/
private static final long serialVersionUID = 1L;
#Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws SQLException{
try{
Currency type = Currency.valueOf(rs.getString(names[0]).toUpperCase());
if(type == null){
System.err.println("CurrencyConvertor: Null returned from CurrencyConvertor.valueOf, pgObject.getValue() = " + rs.toString());
}else{
return type;
}
}catch(Exception e){
e.printStackTrace();
}
System.err.println("CurrencyConvertor: Returning null!: "+rs.getString(names[0]));
return null;
}
#Override
public void nullSafeSet(
PreparedStatement st,
Object value,
int index,
SharedSessionContractImplementor session)
throws HibernateException, SQLException {
st.setObject(
index,
value != null ?
((Currency) value).name().toLowerCase() :
null,
Types.OTHER
);
}
}
I have a nested POJO structure defined something like this,
public class Employee {
private String id;
private Personal personal;
private Official official;
}
public class Personal {
private String fName;
private String lName;
private String address;
}
public class Official {
private boolean active;
private Salary salary;
}
public class Salary {
private double hourly;
private double monthly;
private double yearly;
}
I get updates from a service with dot annotaion on what value changed, for ex,
id change --> id=100
address change --> personal.address=123 Main Street
hourly salary change --> official.salary.hourly=100
This POJO structure could be 3-4 level deeps. I need to look for this incoming change value and update the corresponding value in POJO. What's the best way of doing it?
If you would like to create Java objects that allows you to edit fields. You can specify your object fields with the public/default/protected access modifiers. This will enable you to get and set fields such as personal.address or official.salary.hours
This approach is typically frowned upon as the object is no longer encapsulated and any calling methods are welcome to manipulate the object. If these fields are not encapsulated with getters and setters, your object is no longer a POJO.
public provides access from any anywhere.
default provides access from any package
protected provides access from package or subclass.
public class Employee {
public String id;
public Personal personal;
public Official official;
}
public class Personal {
public String fName;
public String lName;
public String address;
}
Here's a quick approach using reflection to set fields dynamically. It surely isn't and can't be clean. If I were you, I would use a scripting engine for that (assuming it's safe to do so).
private static void setValueAt(Object target, String path, String value)
throws Exception {
String[] fields = path.split("\\.");
if (fields.length > 1) {
setValueAt(readField(target, fields[0]),
path.substring(path.indexOf('.') + 1), value);
return;
}
Field f = target.getClass()
.getDeclaredField(path);
f.setAccessible(true);
f.set(target, parse(value, f.getType())); // cast or convert value first
}
//Example code for converting strings to primitives
private static Object parse(String value, Class<?> type) {
if (String.class.equals(type)) {
return value;
} else if (double.class.equals(type) || Double.class.equals(type)) {
return Long.parseLong(value);
} else if (boolean.class.equals(type) || Boolean.class.equals(type)) {
return Boolean.valueOf(value);
}
return value;// ?
}
private static Object readField(Object from, String field) throws Exception {
Field f = from.getClass()
.getDeclaredField(field);
f.setAccessible(true);
return f.get(from);
}
Just be aware that there's a lot to improve in this code (exception handling, null checks, etc.), although it seems to achieve what you're looking for (split your input on = to call setValueAt()):
Employee e = new Employee();
e.setOfficial(new Official());
e.setPersonal(new Personal());
e.getOfficial().setSalary(new Salary());
ObjectMapper mapper = new ObjectMapper();
setValueAt(e, "id", "123");
// {"id":"123","personal":{},"official":{"active":false,"salary":{"hourly":0.0,"monthly":0.0,"yearly":0.0}}}
setValueAt(e, "personal.address", "123 Main Street");
// {"id":"123","personal":{"address":"123 Main Street"},"official":{"active":false,"salary":{"hourly":0.0,"monthly":0.0,"yearly":0.0}}}
setValueAt(e, "official.salary.hourly", "100");
// {"id":"123","personal":{"address":"123 Main Street"},"official":{"active":false,"salary":{"hourly":100.0,"monthly":0.0,"yearly":0.0}}}
I feel like this is a simple problem, but none of the things i tried work for me. I have an enum, the reason i have string constructor is because Java doesn't allow enum to be numerical..I tried AA, AB, 2C directly without string constructor but that gives an error. Note that for the existing enum i am adding C("2C").
public enum TestEnum{
AA("AA"), AB("AB"), C("2C");
private String display;
private TestEnum( String display ) {
this.display = display;
}
public String toString() {
return display;
}
public String getDisplay() {
return display;
}
public void setDisplay( String display ) {
this.display = display;
}
public String getName() {
return display;
}
Now i have a mybatis mapper which does a merge this is existing and one of the param to the mapper is TestEnum. Until now this worked fine since enum value and string value are same, but i added C("2C"). Now i want to insert 2C to the table using mybaits, but it always inserts C.
merge into text t
using (select #{id} as id from dual) d on (d.id = t.id)
when matched then
update set
appId = #{applId},
src = #{testEnum}
testEnum inserts C, so i changed that to #{testEnum.toString()} which gave me a there is no getter for property name toString() error. I tried #{testEnum.display} and #{testEnum.name} both of them still inserts C whereas i want it to insert 2C. Do you guys know an easier way of handling this?
I don't want to change the model object to pass String rather than TestEnum because this object is being used in many places.Is there a way this can be done in the mybatis mapper without changing model object?
Thanks for your help :)
What you need is a TypeHandler
First, add a static method to your TestEnum to return a TestEnum given a display string:
public static TestEnum fromDisplay(String display){
for (TestEnum v : TestEnum.values()){
if (v.getDisplay().equals(display)){
return v;
}
}
return null;
}
Then use it to create your TypeHandler:
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
public class TestEnumTypeHandler extends BaseTypeHandler<TestEnum> {
#Override
public void setNonNullParameter(PreparedStatement ps, int i, TestEnum parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter.getDisplay());
}
#Override
public TestEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
return TestEnum.fromDisplay(rs.getString(columnName));
}
#Override
public TestEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return TestEnum.fromDisplay(rs.getString(columnIndex));
}
#Override
public TestEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return TestEnum.fromDisplay(cs.getString(columnIndex));
}
}
Finally, register your TypeHandler in your mybatis xml:
<typeHandlers>
<typeHandler handler="blah.blah.TestEnumTypeHandler "/>
</typeHandlers>
In addition to #Malt Answer:
The reason why what you are trying doesn't works it's the MyBatis EnumTypeHandler by default sets name() value of the method and is marked with final so you cannot override it:
EnumTypeHandler.class (Line 38 to 44):
#Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
if (jdbcType == null) {
ps.setString(i, parameter.name());
} else {
ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE); // see r3589
}
}
Otherwise, the enum is created from the method valueOf(type, name) which also uses the name of the enum.
#Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String s = rs.getString(columnIndex);
return s == null ? null : Enum.valueOf(type, s);
}
#Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String s = cs.getString(columnIndex);
return s == null ? null : Enum.valueOf(type, s);
}
So definitely, you need to use a typeHandler specific to handle your enum which has a specific behaviour, but I would extends directly EnumTypeHandler in specific enum type handlers, instead of BaseTypeHandler (Malt answer), because you could reuse some functionality (not in your case, but maybe in others) so it handles a general enum behaviour.
You do not need to write any custom TypeHandler if you want to insert the value of your Enum.
The only one thing you need to do is to specify the getter method's name in your MyBatis insert.
Example:
SQL:
CREATE TABLE demo
(
id BIGINT,
value VARCHAR(10),
status CHAR(1)
);
MyBatis mapper:
#Update("UPDATE demo SET status = #{status.value} WHERE id= #{uuid}")
long updateStatus(#Param("status") Status status, #Param("uuid") String uuid);
And the Java Enum:
public enum Status {
ACTIVE("A"),
INACTIVE("I");
Status(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
In your case you can use src = #{testEnum.display} in your SQL.
We have a Spring + Hibernate Java 7 web-application. We currently use .hbm.xml Hibernate files (instead of the more common known annotations) as mapping between the Java entities and database tables.
One of our Java entity classes has a java.util.Date with millisecond precision:
public class SomeEntity extends AbstractEntity{
...
private java.util.Date someDate; // with ms precision
...
}
In our .hbm.xml file we have the following for this date:
...
<hibernate-mapping>
<class name="com.namespace.Entity" table="entity" batch-size="10">
...
<property name="SomeDate" column="somedate" type="com.namespace.CustomDateType" />
...
Which is linked to a database table with the following row:
SOMEDATE TIMESTAMP(2) NULLABLE=Yes
Our CustomDateType class:
package com.namespace.type;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Date;
import org.hibernate.usertype.UserType;
public class CustomDateType implements UserType {
private static final int[] SQL_TYPES = { Types.TIMESTAMP };
#Override
public Object assemble(final Serializable serializable, final Object o) {
return deepCopy(serializable);
}
#Override
public Object deepCopy(final Object value) {
return value;
}
#Override
public Serializable disassemble(final Object o) {
return (Serializable) deepCopy(o);
}
#Override
public boolean equals(final Object x, final Object y) {
if (x == y) {
return true;
} else if ((x == null) || (y == null)) {
return false;
}
return x.equals(y);
}
#Override
public int hashCode(final Object o) {
return ((CustomDateType) o).hashCode();
}
#Override
public boolean isMutable() {
return false;
}
#Override
public Object nullSafeGet(final ResultSet resultSet, final String[] names, final Object owner) throws SQLException {
Date result = null;
final Timestamp timestamp = resultSet.getTimestamp(names[0]); // This is where the problem is
if (!resultSet.wasNull()) {
result = new Date(timestamp.getTime());
}
return result;
}
#Override
public void nullSafeSet(final PreparedStatement statement, final Object value, final int index) throws SQLException {
final Date dateToSet = (Date) value;
statement.setTimestamp(index, dateToSet == null ? null : new Timestamp(dateToSet.getTime()));
}
#Override
public Object replace(final Object o, final Object o1, final Object o2) {
return o;
}
#Override
public Class<Date> returnedClass() {
return Date.class;
}
#Override
public int[] sqlTypes() {
return SQL_TYPES;
}
}
When debugging someDate does indeed have milliseconds in the Java class. But, in the CustomDateType#nullSafeGet method the resulting TimeStamp doesn't have any milliseconds anymore.
The resultSet parameter is the oracle.jdbc.driver.ForwardOnlyResultSet, which continues into oracle.jdbc.driver.T4CPreparedStatement, which continues into the class & method where the core of the problem is: oracle.jdbc.driver.DateTimeCommonAccessor#getTime(int, Calendar)
In this method the milliseconds are stripped and you are left with second-precision.
Is there any way to keep the milliseconds in this situation? How to save millisecond precision when going from java.util.Date to database TIMESTAMP-typed column?
NOTE: The milliseconds are already gone before going into the database table. If I put a breakpoint at final Timestamp timestamp = resultSet.getTimestamp(names[0]); and step over, the resulting timestamp will have no milliseconds anymore.
EDIT: The contents of the hava.util.Date object and java.sql.Timestamp objects during debugging:
The fraction and millis are both removed and put to 0..
A java.sql.Timestamp stores second precision in the java.util.Date fastTime field (instead of milliseconds for a normal java.util.Date), and it has a separate nanos field for sub-second precision. When you call java.sql.Timestamp.getTime() it recombines these two into a millisecond precision value.
As you have declared your field as TIMESTAMP(2), it means you only have 2 decimals of sub-second precision. If you try to store 00:00:00.001, what actually gets stored is 00:00:00:00, and that is also what you retrieve again. In other words, you need to increase precision by declaring the field as TIMESTAMP(3) (or even higher, up to 9 is supported), or you need to live with the reduced precision of two decimals.
Note that if you use an higher than millisecond precision (so more than 3 decimals), you can only get that full precision using java.sql.Timestamp.getNanos(), which contains the full sub-second precision (the nanos are only the sub second fraction and should always be less than 1 second), or by converting the Timestamp to a java.time.LocalDateTime.
My logic is eluding me on this one, I'm hoping someone has some code I can learn from.
In java I have a List of custom objects, one of the members of this object is date/time of a certain event.
I need to be able to find the next time in the List from the current/local time.
Ie, The local time is 6pm
The list contains:
1pm, 3pm, 4pm, 7pm, 10pm
I need a method to basically pull out 7pm, as the next event time to process.
Any suggestions, directions appreciated, thx
You can do it by assuming the first dateTime object in the list to be the closest initially and then iterating to find if any other dateTime from the list is more closer to your refernce dateTime.Something like this:
import java.util.ArrayList;
import java.util.List;
import org.joda.time.DateTime;
public class test {
public static void main(String[] args) {
CustomData cd1 = new CustomData("data1", new DateTime(100000));
CustomData cd2 = new CustomData("data2", new DateTime(200000));
CustomData cd3 = new CustomData("data3", new DateTime(300000));
CustomData cd4 = new CustomData("data4", new DateTime(400000));
List<CustomData> dataList = new ArrayList<CustomData>();
dataList.add(cd1);
dataList.add(cd2);
dataList.add(cd3);
dataList.add(cd4);
DateTime closestDate=dataList.get(0).getDateTime(); //initially assume first dateTime to be the closest
for(CustomData cd:dataList){
if(cd!=null && cd.getDateTime()!=null && cd.getDateTime().isBefore(closestDate.getMillis()) && cd.getDateTime().isAfter(DateTime.now())){
/*if the date time is before the closest date and after the reference DateTime you are comparing(in this case DateTime.now()) update the reference of closestDate */
closestDate=cd.getDateTime();
}
}
System.out.println(closestDate);
}
}
Also for reference the CustomData class:
import org.joda.time.DateTime;
public class CustomData {
private String name;
private DateTime dateTime;
public CustomData(String name, DateTime dateTime) {
this.name = name;
this.dateTime = dateTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public DateTime getDateTime() {
return dateTime;
}
public void setDateTime(DateTime dateTime) {
this.dateTime = dateTime;
}
}
Assuming that:
You have a class Event which has a time property,
the time property is of type java.time.LocalTime or something else which either has a natural order (like java.util.Date) or for which you can easily provide a Comparator,
you have your events in a java.util.List<Event> which is unsorted,
your class Event defines a natural order according to time (implements Comparable<Event>) or you provide a Comparator<Event> which compares Event objects by their time property,
basically the relevant essentials of class Event being this,
import java.time.LocalTime;
public class Event implements Comparable<Event> {
LocalTime time;
#Override
public int compareTo(final Event o) {
return time.compareTo(o.time);
}
}
or
import java.time.LocalTime;
public class Event {
LocalTime time;
public static final Comparator<Event> byTime = new Comparator<Event>() {
#Override
public int compare(final Event o1, final Event o2) {
return o1.time.compareTo(o2.time);
}
};
}
then you could use one of the following ways to get the event you're looking for (and there are certainly many more ways):
You could iterate through the List with a for-each loop (comparative linear search).
You could stream() your list, filter() all events after that time, sort() them, and take the first.
You could put the events in a TreeMap<LocalTime, Event> and ask the TreeMap for the ceilingKey().
Which of these solutions is best depends on how you actually store your data. If access on List<Event> is frequently done based on the time property, you might want to permanently keep a TreeMap<LocalTime, Event> in your application. In that case, the third solution is best. If it's done rarely, I'd use the Streams solution. The first solution is the naive primitive approach, and although we've been coding that way for decades, I don't like it.
Here are these three solutions:
public static Event getNextEventUsingComparison(final List<Event> unsortedEvents, final LocalTime timestamp) {
Event candidateEvent = null;
for (final Event event : unsortedEvents)
if (event.getTime().isAfter(timestamp) && (candidateEvent == null || event.getTime().isBefore(candidateEvent.getTime())))
candidateEvent = event;
return candidateEvent;
}
public static Event getNextEventUsingStreams(final List<Event> unsortedEvents, final LocalTime timestamp) {
return unsortedEvents.stream().filter(t -> t.getTime().isAfter(timestamp)).sorted().findFirst().orElse(null);
}
public static Event getNextEventUsingTreeMap(final List<Event> unsortedEvents, final LocalTime timestamp) {
final TreeMap<LocalTime, Event> timeEventMap = new TreeMap<>();
for (final Event event : unsortedEvents)
timeEventMap.put(event.getTime(), event);
final LocalTime key = timeEventMap.ceilingKey(timestamp);
return key != null ? timeEventMap.get(key) : null;
}
It should work just as well with other time classes like java.util.Date instead of java.time.LocalTime as long as they are implements Comparable or you can provide a Comparator.
In case you mess around with this code, you might want to test it. Here's the code relevant to testing which I used for this:
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
public class Event implements Comparable<Event> {
String title;
LocalTime time;
public Event(final String title, final LocalTime time) {
this.title = title;
this.time = time;
}
public Event(final String descriptor) {
this(descriptor.substring(6), LocalTime.parse(descriptor.substring(0, 5)));
}
public String getTitle() { return title; }
public LocalTime getTime() { return time; }
#Override
public int compareTo(final Event o) { return time.compareTo(o.time); }
public static List<Event> createEventList(final String... descriptors) {
final List<Event> events = new ArrayList<>();
for (final String descriptor : descriptors)
events.add(new Event(descriptor));
return events;
}
}
And the Unit Test:
import org.junit.Test;
import java.util.List;
import static java.time.LocalTime.of;
import static java.util.Collections.emptyList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static stackexchange.stackoverflow.q27350515.Event.createEventList;
import static stackexchange.stackoverflow.q27350515.FindNextDate.getNextEvent;
public class FindNextDateTest {
#Test public void givenEmptyList_whenFindingNext_thenReturnsNull() {
assertNull(getNextEvent(emptyList(), of(6, 0)));
}
#Test public void givenOneElementListWithSmallerElement_whenFindingNext_thenReturnsNull() {
final List<Event> events = createEventList("10:15 Breakfast");
assertNull(getNextEvent(events, of(11, 0)));
}
#Test public void givenOneElementListWithLargerElement_whenFindingNext_thenReturnsElement() {
final List<Event> events = createEventList("12:15 Lunch");
assertEquals(events.get(0), getNextEvent(events, of(11, 0)));
}
#Test public void givenBigList_whenFindingNext_thenReturnsElement() {
final List<Event> events = createEventList("10:15 Breakfast", "12:15 Lunch", "08:00 Morning walk", "11:15 Power nap", "14:00 Power nap", "20:00 Dinner");
assertEquals(events.get(3), getNextEvent(events, of(11, 0)));
}
}