Flat project hierarchy
[aquarium] / src / main / scala / gr / grnet / aquarium / logic / accounting / dsl / DSLUtils.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 gr.grnet.aquarium.util.DateUtils
39 import java.util.{Date, GregorianCalendar, Calendar}
40
41 /**
42  * Utility functions to use when working with DSL types.
43  *
44  * @author Georgios Gousios <gousiosg@gmail.com>
45  */
46
47 trait DSLUtils extends DateUtils {
48
49   val maxdate = new Date(Int.MaxValue * 1000L)
50   val mindate = new Date(0)
51
52   def resolveEffectiveAlgorithmsForTimeslot(timeslot: Timeslot,
53                                            agr: DSLAgreement):
54   Map[Timeslot, DSLAlgorithm] =
55     resolveEffective[DSLAlgorithm](timeslot, Some(agr.algorithm))
56
57
58   def resolveEffectivePricelistsForTimeslot(timeslot: Timeslot,
59                                             agr: DSLAgreement):
60   Map[Timeslot, DSLPriceList] =
61     resolveEffective[DSLPriceList](timeslot, Some(agr.pricelist))
62
63   /**
64    * Resolves the DSLTimeBoundedItem which is active within the
65    * provided timeslot. If the provided timeslot does not fit entirely or at all
66    * into a timeslot within which a DSLTimeBoundedItem is active, then the
67    * resolution takes the following paths:
68    *
69    *  - If the provided timeslot (a) partially fits into the DSLTimeBoundedItem
70    *  timeslot (b) and the next active time slot is (c), then the provided
71    *  timeslot is split in three parts `(a.start...b.end)`,
72    *  `(b.end...c.start)` and `(c.start...a.end)`
73    *
74    */
75   def resolveEffective[T <: DSLTimeBoundedItem[T]](timeslot: Timeslot,
76                                                    tbi: Option[T]):
77   Map[Timeslot, T] = {
78
79     val policy = tbi match {
80       case None => return Map()
81       case _ => tbi.get
82     }
83
84     val eff = allEffectiveTimeslots(policy.effective,
85       oneYearBack(timeslot.from, policy.effective.from),
86       oneYearAhead (timeslot.to, policy.effective.to.getOrElse(maxdate)))
87
88     Map() ++
89       timeslot.overlappingTimeslots(eff).flatMap {
90         t => Map(t -> policy)
91       } ++
92       timeslot.nonOverlappingTimeslots(eff).flatMap {
93         t => resolveEffective(t, policy.overrides)
94       }
95   }
96
97   /**
98    * Get a list of timeslots within which a timeframe is not effective.
99    */
100   def ineffectiveTimeslots(spec: DSLTimeFrameRepeat, from: Date, to: Option[Date]):
101     List[Timeslot] = {
102
103     buildNotEffectiveList(effectiveTimeslots(spec, from, to)) sortWith sorter
104   }
105
106   private def buildNotEffectiveList(l :List[Timeslot]) :
107     List[Timeslot] = {
108
109     if (l.isEmpty) return List()
110     if (l.tail.isEmpty) return List()
111
112     assert(l.head.to.getTime < l.tail.head.from.getTime)
113
114     List[Timeslot]() ++
115       List(Timeslot(new Date(l.head.to.getTime + 1),
116         new Date(l.tail.head.from.getTime - 1))) ++
117       buildNotEffectiveList(l.tail)
118   }
119
120   /**
121    * Merges overlapping timeslots. The merge is exhaustive only if the
122    * provided list is sorted in increasing timeslot from order.
123    */
124   def mergeOverlaps(list: List[Timeslot]): List[Timeslot] = {
125     list.foldLeft(List[Timeslot]()) {
126       (a, b) =>
127         if (a.isEmpty)
128           List(b)
129         else if (a.tail.isEmpty)
130           a.head.merge(b)
131         else {
132           val merged = a.tail.head.merge(b)
133           a ++ (if (merged.size == 1) merged else List(b))
134         }
135     }
136   }
137
138   /**
139    * Get a list of all timeslots within which the provided time frame
140    * is effective.
141    */
142   def allEffectiveTimeslots(spec: DSLTimeFrame):
143   List[Timeslot] = {
144
145     val l = spec.repeat.flatMap {
146       r => effectiveTimeslots(r, spec.from, spec.to)
147     } sortWith sorter
148     mergeOverlaps(l)
149   }
150
151   /**
152    * Get a list of all timeslots within which a timeframe
153    * is effective, whithin the provided time bounds.
154    */
155   def allEffectiveTimeslots(spec: DSLTimeFrame, from: Date, to: Date):
156   List[Timeslot] = {
157
158     //A timeframe with no repetition defined
159     if (spec.repeat.isEmpty) {
160       val fromDate = if (spec.from.before(from)) from else spec.from
161       val toDate = if (spec.to.getOrElse(to).after(to)) to else spec.to.getOrElse(to)
162       return List(Timeslot(fromDate, toDate))
163     }
164
165     val l = spec.repeat.flatMap {
166       r => effectiveTimeslots(r, from, Some(to))
167     } sortWith sorter
168     mergeOverlaps(l)
169   }
170
171   /**
172    * Get a list of all time periods within which a time frame is active.
173    * If the to date is None, the expansion takes place within a timeframe
174    * between `from .. from` + 1 year. The result is returned sorted by
175    * timeframe start date.
176    */
177   def effectiveTimeslots(spec: DSLTimeFrameRepeat, from: Date, to: Option[Date]):
178     List[Timeslot] = {
179
180     assert(spec.start.size == spec.end.size)
181
182     val endDate = to match {
183       case None => //One year from now
184         val c = new GregorianCalendar()
185         c.setTime(from)
186         c.add(Calendar.YEAR, 1)
187         c.getTime
188       case Some(y) => y
189     }
190
191     coExpandTimespecs(spec.start.zip(spec.end), from, endDate) sortWith sorter
192   }
193
194   /**
195    * Utility function to put timeslots in increasing start timestamp order
196    */
197   def sorter(x: Timeslot, y: Timeslot) : Boolean =
198     if (y.from after x.from) true else false
199
200   /**
201    * Calculate periods of activity for a list of timespecs
202    */
203   private def coExpandTimespecs(input: List[(DSLTimeSpec, DSLTimeSpec)],
204                                 from: Date, to: Date): List[Timeslot] = {
205     if (input.size == 0) return List()
206
207     expandTimeSpec(input.head._1, from, to).zip(
208       expandTimeSpec(input.head._2, from, to)).map(
209         l => Timeslot(l._1, l._2)
210       ) ++
211       coExpandTimespecs(input.tail, from, to)
212   }
213
214   /**
215    * Expand a List of timespecs.
216    */
217   def expandTimeSpecs(spec: List[DSLTimeSpec], from: Date,  to: Date):
218     List[Date] =
219     spec.flatMap { t => expandTimeSpec(t, from, to)}
220
221   /**
222    * Get the list of time points prescribed by the provided timespec,
223    * within the timeframe between from and to.
224    */
225   def expandTimeSpec(spec: DSLTimeSpec, from: Date,  to: Date) : List[Date] = {
226     val adjusted = adjustToTime(from, spec.hour, spec.min)
227     findDays(adjusted, to, {
228       c =>
229         (if (spec.mon >= 0) {c.get(Calendar.MONTH) == spec.getCalendarMonth()} else true) &&
230         (if (spec.dom >= 0) {c.get(Calendar.DAY_OF_MONTH) == spec.dom} else true) &&
231         (if (spec.dow >= 0) {c.get(Calendar.DAY_OF_WEEK) == spec.getCalendarDow()} else true)
232     })
233   }
234 }