Pefromance tests to justify selections
[aquarium] / logic / 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
48 trait DSLUtils extends DateUtils {
49
50   def resolveEffectiveAlgorithmsForTimeslot(timeslot: (Date, Date),
51                                            agr: DSLAgreement):
52   Map[(Date, Date), DSLAlgorithm] =
53     resolveEffective[DSLAlgorithm](timeslot, Some(agr.algorithm))
54
55
56   def resolveEffectivePricelistsForTimeslot(timeslot: (Date, Date),
57                                             agr: DSLAgreement):
58   Map[(Date, Date), DSLPriceList] =
59     resolveEffective[DSLPriceList](timeslot, Some(agr.pricelist))
60
61   /**
62    * Resolves the DSLTimeBoundedItem which is active within the
63    * provided timeslot. If the provided timeslot does not fit entirely or at all
64    * into a timeslot within which a DSLTimeBoundedItem is active, then the
65    * resolution takes the following paths:
66    *
67    *  - If the provided timeslot (a) partially fits into the DSLTimeBoundedItem
68    *  timeslot (b) and the next active time slot is (c), then the provided
69    *  timeslot is split in three parts `(a.start...b.end)`,
70    *  `(b.end...c.start)` and `(c.start...a.end)`
71    *
72    */
73   def resolveEffective[T <: DSLTimeBoundedItem[T]](timeslot: (Date, Date),
74                                                    tbi: Option[T]):
75   Map[(Date, Date), T] = {
76
77     val item = tbi match {
78       case None => return Map()
79       case _ => tbi.get
80     }
81
82     val eff = allEffectiveTimeslots(item.effective,
83       item.effective.from, timeslot._2)
84
85     val res = eff.find(t => contains(t, timeslot)) match {
86       case Some(x) => Map(x -> item)
87       case None => eff.find(t => contains(t, timeslot._1)) match {
88         case Some(y) =>
89           val next = if (eff.lastIndexOf(y) == eff.size - 1)
90                        (new Date(Int.MaxValue), new Date(Int.MaxValue))
91                      else
92                        eff.apply(eff.lastIndexOf(y) + 1)
93           Map((timeslot._1, y._2) -> item) ++ (
94             if (timeslot._2.before(next._1))
95               resolveEffective((y._2, timeslot._2), item.overrides)
96             else
97               resolveEffective((y._2, next._1), item.overrides) ++
98               resolveEffective((next._1, timeslot._2), item.overrides)
99             )
100         case None => resolveEffective(timeslot, item.overrides)
101       }
102     }
103
104     Map() ++ res
105   }
106
107   /**
108    * Get a list of timeslots within which a timeframe is not effective.
109    */
110   def ineffectiveTimeslots(spec: DSLTimeFrameRepeat, from: Date, to: Option[Date]):
111     List[(Date, Date)] = {
112
113     buildNotEffectiveList(effectiveTimeslots(spec, from, to)) sortWith sorter
114   }
115
116   private def buildNotEffectiveList(l :List[(Date, Date)]) :
117     List[(Date, Date)] = {
118
119     if (l.isEmpty) return List()
120     if (l.tail.isEmpty) return List()
121
122     assert(l.head._2.getTime < l.tail.head._1.getTime)
123
124     List[(Date, Date)]() ++
125       List((new Date(l.head._2.getTime + 1),
126         new Date(l.tail.head._1.getTime - 1))) ++
127       buildNotEffectiveList(l.tail)
128   }
129
130   /**
131    * Get a list of all timeslots within which the provided time frame
132    * is effective.
133    */
134   def allEffectiveTimeslots(spec: DSLTimeFrame):
135   List[(Date, Date)] = {
136
137     spec.repeat.flatMap {
138       r => effectiveTimeslots(r, spec.from, spec.to)
139     } sortWith sorter
140   }
141
142   /**
143    * Get a list of all timeslots within which a timeframe
144    * is effective, whithin the provided time bounds.
145    */
146   def allEffectiveTimeslots(spec: DSLTimeFrame, from: Date, to: Date):
147   List[(Date, Date)] = {
148
149     spec.repeat.flatMap {
150       r => effectiveTimeslots(r, from, Some(to))
151     } sortWith sorter
152   }
153
154   /**
155    * Get a list of all time periods within which a time frame is active.
156    * If the to date is None, the expansion takes place within a timeframe
157    * between `from .. from` + 1 year. The result is returned sorted by
158    * timeframe start date.
159    */
160   def effectiveTimeslots(spec: DSLTimeFrameRepeat, from: Date, to: Option[Date]):
161     List[(Date, Date)] = {
162
163     assert(spec.start.size == spec.end.size)
164
165     val endDate = to match {
166       case None => //One year from now
167         val c = new GregorianCalendar()
168         c.setTime(from)
169         c.add(Calendar.YEAR, 1)
170         c.getTime
171       case Some(y) => y
172     }
173
174     coExpandTimespecs(spec.start.zip(spec.end), from, endDate) sortWith sorter
175   }
176
177   /**
178    * Utility function to put timeslots in increasing start timestamp order
179    */
180   def sorter(x: (Date, Date), y: (Date, Date)) : Boolean =
181     if (y._1 after x._1) true else false
182
183   /**
184    * Calculate periods of activity for a list of timespecs
185    */
186   private def coExpandTimespecs(input : List[(DSLTimeSpec, DSLTimeSpec)],
187                                 from: Date, to: Date) : List[(Date, Date)] = {
188     if (input.size == 0) return List()
189
190     expandTimeSpec(input.head._1, from, to).zip(
191       expandTimeSpec(input.head._2, from, to)) ++
192         coExpandTimespecs(input.tail, from, to)
193   }
194
195   /**
196    * Expand a List of timespecs.
197    */
198   def expandTimeSpecs(spec: List[DSLTimeSpec], from: Date,  to: Date):
199     List[Date] =
200     spec.flatMap { t => expandTimeSpec(t, from, to)}
201
202   /**
203    * Get the list of time points prescribed by the provided timespec,
204    * within the timeframe between from and to.
205    */
206   def expandTimeSpec(spec: DSLTimeSpec, from: Date,  to: Date) : List[Date] = {
207     val adjusted = adjustToTime(from, spec.hour, spec.min)
208     findDays(adjusted, to, {
209       c =>
210         (if (spec.mon >= 0) {c.get(Calendar.MONTH) == spec.getCalendarMonth()} else true) &&
211         (if (spec.dom >= 0) {c.get(Calendar.DAY_OF_MONTH) == spec.dom} else true) &&
212         (if (spec.dow >= 0) {c.get(Calendar.DAY_OF_WEEK) == spec.getCalendarDow()} else true)
213     })
214   }
215 }