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