Merge branch 'master'
[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 import annotation.tailrec
41
42 /**
43  * A representation of a timeslot with a start and end date.
44  *
45  * @author Georgios Gousios <gousiosg@gmail.com>
46  */
47 final case class Timeslot(from: Date, to: Date)
48   extends DSLItem with Ordered[Timeslot] {
49
50   /* Preconditions to ensure correct object creations */
51   assert(from != null)
52   assert(to != null)
53   assert(from.before(to))
54
55   def startsBefore(t: Timeslot) : Boolean = this.from.before(t.from)
56
57   def startsAfter(t: Timeslot) : Boolean = this.from.after(t.from)
58
59   def endsBefore(t: Timeslot) : Boolean = this.to.before(t.to)
60
61   def endsAfter(t: Timeslot) : Boolean = this.to.after(t.to)
62
63   def after(t: Timeslot): Boolean = if (this.from.after(t.to)) true else false
64
65   def before(t: Timeslot): Boolean = if (this.to.before(t.from)) true else false
66
67   /**
68    * Check whether this time slot fully contains the provided one.
69    */
70   def contains(t: Timeslot) : Boolean = t.startsAfter(this) && t.endsBefore(this)
71
72   /**
73    * Check whether this timeslot contains the provided time instant.
74    */
75   def includes(t: Date) : Boolean =
76     if (from.before(t) && to.after(t)) true else false
77
78   /**
79    * Check whether this timeslot overlaps with the provided one.
80    */
81   def overlaps(t: Timeslot) : Boolean = {
82     if (contains(t) || t.contains(this))
83       return true
84
85     if (this.includes(t.from) || this.includes(t.to))
86       return true
87
88     false
89   }
90
91   /**
92    * Merges this timeslot with the provided one. If the timeslots overlap,
93    * a list with the resulting merge is returned. If the timeslots do not
94    * overlap, the returned list contains both timeslots in increasing start
95    * date order.
96    */
97   def merge(t: Timeslot) : List[Timeslot] = {
98     if (overlaps(t)) {
99       val nfrom = if (from.before(t.from)) from else t.from
100       val nto   = if (to.after(t.to)) to else t.to
101       List(Timeslot(nfrom, nto))
102     } else
103       if (this.from.before(t.from))
104         List(this, t)
105       else
106         List(t, this)
107   }
108
109   /**
110    * Split the timeslot in two parts at the provided timestamp, if the
111    * timestamp falls within the timeslot boundaries.
112    */
113   def slice(d: Date) : List[Timeslot] =
114     if (includes(d))
115       List(Timeslot(from, d), Timeslot(d,to))
116     else
117       List(this)
118
119   /**
120    * Find and return the timeslots within which this Timeslot overrides
121    * with the provided list of timeslots. For example if:
122    * 
123    *  - `this == Timeslot(7,20)`
124    *  - `list == List(Timeslot(1,3), Timeslot(6,8), Timeslot(11,15))`
125    *
126    * the result will be: `List(Timeslot(7,8), Timeslot(11,15))`
127    */
128   def overlappingTimeslots(list: List[Timeslot]) : List[Timeslot] = {
129
130     val result = new mutable.ListBuffer[Timeslot]()
131
132     list.foreach {
133       t =>
134         if (t.contains(this)) result += this
135         else if (this.contains(t)) result += t
136         else if (t.overlaps(this) && t.startsBefore(this)) result += this.slice(t.to).head
137         else if (t.overlaps(this) && t.startsAfter(this)) result += this.slice(t.from).last
138     }
139     result.toList
140   }
141
142   /**
143    * Find and return the timeslots whithin which this Timeslot does not
144    * override with the provided list of timeslots. For example if:
145    *
146    *  - `this == Timeslot(7,20)`
147    *  - `list == List(Timeslot(1,3), Timeslot(6,8), Timeslot(11,15))`
148    *
149    * the result will be: `List(Timeslot(9,10), Timeslot(15,20))`
150    */
151   def nonOverlappingTimeslots(list: List[Timeslot]): List[Timeslot] = {
152
153     val overlaps = list.filter(t => this.overlaps(t))
154
155     if (overlaps.isEmpty)
156       return List(this)
157
158     def build(acc: List[Timeslot], listPart: List[Timeslot]): List[Timeslot] = {
159
160       listPart match {
161         case Nil => acc
162         case x :: Nil => build(acc, List())
163         case x :: y :: rest =>
164           build(acc ++ List(Timeslot(x.to,  y.from)), y :: rest)
165       }
166     }
167
168     val head = overlaps.head
169     val last = overlaps.reverse.head
170
171     val start = if (head.startsAfter(this)) List(Timeslot(this.from, head.from)) else List()
172     val end = if (last.endsBefore(this)) List(Timeslot(last.to, this.to)) else List()
173
174     start ++ build(List(), overlaps) ++ end
175   }
176
177   /**
178    * Align a list of consecutive timeslots to the boundaries
179    * defined by this timeslot. Elements that do not overlap
180    * with this timeslot are rejected, while elements not
181    * contained in the timeslot are trimmed to this timeslot's
182    * start and end time.
183    */
184   def align(l: List[Timeslot]): List[Timeslot] = {
185     if (l.isEmpty) return List()
186
187     val result =
188       if (!this.overlaps(l.head)) List()
189       else if (this.contains(l.head)) List(l.head)
190       else if (l.head.startsBefore(this)) List(Timeslot(this.from, l.head.to))
191       else if (l.head.endsAfter(this)) List(Timeslot(l.head.from, this.to))
192       else List(this)
193
194     if (!result.isEmpty)
195       result.head :: align(l.tail)
196     else
197       align(l.tail)
198   }
199
200   /**
201    * Compares the starting times of two timeslots.
202    */
203   def compare(that: Timeslot): Int = {
204     if (this.startsBefore(that)) -1
205     else if (this.startsAfter(that)) 1
206     else 0
207   }
208
209   /**
210    * Converts the timeslot to the amount of hours it represents
211    */
212   def hours: Double = (to.getTime - from.getTime).toDouble / 1000.0 / 60.0 / 60.0
213
214   def deltaMillis = to.getTime - from.getTime
215   override def toString() = "from: %l, to: %l".format(from.getTime, to.getTime)
216 }