Switch to using Long.MaxValue to denote open timeframes
[aquarium] / src / main / scala / gr / grnet / aquarium / logic / accounting / dsl / Timeslot.scala
1 /*
2  * Copyright 2011 GRNET S.A. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or
5  * without modification, are permitted provided that the following
6  * conditions are met:
7  *
8  *   1. Redistributions of source code must retain the above
9  *      copyright notice, this list of conditions and the following
10  *      disclaimer.
11  *
12  *   2. Redistributions in binary form must reproduce the above
13  *      copyright notice, this list of conditions and the following
14  *      disclaimer in the documentation and/or other materials
15  *      provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
18  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
21  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  *
30  * The views and conclusions contained in the software and
31  * documentation are those of the authors and should not be
32  * interpreted as representing official policies, either expressed
33  * or implied, of GRNET S.A.
34  */
35
36 package gr.grnet.aquarium.logic.accounting.dsl
37
38 import java.util.Date
39 import scala.collection.mutable
40
41 /**
42  * A representation of a timeslot with a start and end date.
43  *
44  * @author Georgios Gousios <gousiosg@gmail.com>
45  */
46 case class Timeslot(from: Date, to: Date)
47   extends DSLItem with Ordered[Timeslot] {
48
49   /* Preconditions to ensure correct object creations */
50   assert(from != null)
51   assert(to != null)
52   assert(from.before(to))
53
54   def startsBefore(t: Timeslot) : Boolean = this.from.before(t.from)
55
56   def startsAfter(t: Timeslot) : Boolean = this.from.after(t.from)
57
58   def endsBefore(t: Timeslot) : Boolean = this.to.before(t.to)
59
60   def endsAfter(t: Timeslot) : Boolean = this.to.after(t.to)
61
62   /**
63    * Check whether this time slot fully contains the provided one.
64    */
65   def contains(t: Timeslot) : Boolean = t.startsAfter(this) && t.endsBefore(this)
66
67   /**
68    * Check whether this timeslot contains the provided time instant.
69    */
70   def includes(t: Date) : Boolean =
71     if (from.before(t) && to.after(t)) true else false
72
73   /**
74    * Check whether this timeslot overlaps with the provided one.
75    */
76   def overlaps(t: Timeslot) : Boolean = {
77     if (contains(t) || t.contains(this))
78       return true
79
80     if (this.includes(t.from) || this.includes(t.to))
81       return true
82
83     false
84   }
85
86   /**
87    * Merges this timeslot with the provided one. If the timeslots overlap,
88    * a list with the resulting merge is returned. If the timeslots do not
89    * overlap, the returned list contains both timeslots in increasing start
90    * date order.
91    */
92   def merge(t: Timeslot) : List[Timeslot] = {
93     if (overlaps(t)) {
94       val nfrom = if (from.before(t.from)) from else t.from
95       val nto   = if (to.after(t.to)) to else t.to
96       List(Timeslot(nfrom, nto))
97     } else
98       if (this.from.before(t.from))
99         List(this, t)
100       else
101         List(t, this)
102   }
103
104   /**
105    * Split the timeslot in two parts at the provided timestamp, if the
106    * timestamp falls within the timeslot boundaries.
107    */
108   def slice(d: Date) : List[Timeslot] =
109     if (includes(d))
110       List(Timeslot(from, d), Timeslot(d,to))
111     else
112       List(this)
113
114   /**
115    * Find and return the timeslots whithin which this Timeslot overrides
116    * with the provided list of timeslots. For example if:
117    * 
118    *  - `this == Timeslot(7,20)`
119    *  - `list == List(Timeslot(1,3), Timeslot(6,8), Timeslot(11,15))`
120    *
121    * the result will be: `List(Timeslot(7,8), Timeslot(11,15))`
122    */
123   def overlappingTimeslots(list: List[Timeslot]) : List[Timeslot] = {
124
125     val result = new mutable.ListBuffer[Timeslot]()
126
127     list.foreach {
128       t =>
129         if (t.contains(this)) result += this
130         else if (this.contains(t)) result += t
131         else if (t.overlaps(this) && t.startsBefore(this)) result += this.slice(t.to).head
132         else if (t.overlaps(this) && t.startsAfter(this)) result += this.slice(t.from).tail.head
133     }
134     result.toList
135   }
136
137   /**
138    * Find and return the timeslots whithin which this Timeslot does not
139    * override with the provided list of timeslots. For example if:
140    *
141    *  - `this == Timeslot(7,20)`
142    *  - `list == List(Timeslot(1,3), Timeslot(6,8), Timeslot(11,15))`
143    *
144    * the result will be: `List(Timeslot(9,10), Timeslot(15,20))`
145    */
146   def nonOverlappingTimeslots(list: List[Timeslot]): List[Timeslot] = {
147
148     val overlaps = list.filter(t => this.overlaps(t))
149
150     if (overlaps.isEmpty)
151       return List(this)
152
153     def build(acc: List[Timeslot], listPart: List[Timeslot]): List[Timeslot] = {
154
155       listPart match {
156         case Nil => acc
157         case x :: Nil => build(acc, List())
158         case x :: y :: rest =>
159           build(acc ++ List(Timeslot(x.to,  y.from)), y :: rest)
160       }
161     }
162
163     val head = overlaps.head
164     val last = overlaps.reverse.head
165
166     val start = if (head.startsAfter(this)) List(Timeslot(this.from, head.from)) else List()
167     val end = if (last.endsBefore(this)) List(Timeslot(last.to, this.to)) else List()
168
169     start ++ build(List(), overlaps) ++ end
170   }
171
172   /**
173    * Compares the starting times of two timeslots.
174    */
175   def compare(that: Timeslot): Int = {
176     if (this.startsBefore(that)) -1
177     else if (this.startsAfter(that)) 1
178     else 0
179   }
180
181   /**
182    * Converts the timeslot to the amount of hours it represents
183    */
184   def hours: Double = (to.getTime - from.getTime).toDouble / 1000.0 / 60.0 / 60.0
185 }