7d2454b5c8a8a0fd972d14bb26fd36ea06335a8b
[aquarium] / src / main / scala / gr / grnet / aquarium / logic / accounting / dsl / DSLTimeFrame.scala
1 /*
2  * Copyright 2011-2012 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.shortNameOfClass
39
40 import java.util.Date
41 import gr.grnet.aquarium.util.date.MutableDateCalc
42 import collection.mutable
43
44 /**
45  * Represents an effectivity timeframe.
46  *
47  * @author Georgios Gousios <gousiosg@gmail.com>
48  */
49 case class DSLTimeFrame (
50   from: Date,
51   to: Option[Date],
52   repeat: List[DSLTimeFrameRepeat] /*,
53   cronRepeat: List[DSLCronSpec]*/
54 ) {
55
56   to match {
57     case Some(x) =>
58       assert(x.after(from), "Time frame to (%s) must be after from (%s)"
59         .format(x.getTime, from.getTime))
60     case None =>
61   }
62
63   private val start = from.getTime
64   private val infinity = new Date(Long.MaxValue)
65   private val end = to.getOrElse(infinity).getTime
66
67   /*
68    *  Given the timeslot "t" and the timeslot of this frame (start,end)
69    *  compute the intersection of the two timeslots (return a list of common intervals)
70    *  which satisfy the cron expressions. If no expressions are present which just
71    *  take the intersection. If there exist cron specs (cron,start) we compute the
72    *  intersection between "t" and the fine-grained subtimeslots (of this time frame).
73    *
74    *  */
75   def intervalsOf(t:Timeslot) : List[Timeslot]=
76     if(repeat.isEmpty)
77       t.overlappingTimeslots(List(Timeslot(start,end)))
78     else {
79       val result = new mutable.ListBuffer[Timeslot]()
80       var offset = t.from
81       firstValidTimeslot(t.from) match {
82         case None => ()
83         case Some(t) =>
84           result += t
85           offset = t.to
86       }
87       val step = 1000L*60L // +1 minute step
88       var continue = true
89       while(continue)
90         minValidAfter(t,offset) match {
91           case None =>
92             continue = false
93           case Some(next_timeslot) =>
94             result += next_timeslot
95             offset =  next_timeslot.to
96         }
97       result.toList
98     }
99     /*nextValidAfter(t.from) match {
100       case None =>
101         val ret = addRest(t.from)
102         ret
103       case Some(d_next) =>
104         if(d_next.after(t.to)){
105           if(repeat.isEmpty)
106             List(t)
107           else
108             Nil
109         }
110         else {
111           val ret =  Timeslot(t.from,d_next) :: addRest(d_next)
112           ret
113         }
114     } */
115
116   /*
117    * Find the first smallest (smallest here means with the least "end" timestamp) timeslot
118    * from beginning "offset" and ending at "d_end".
119    *
120    * Return None if "offset" is not bounded in this time frame and is not bounded by
121    * a valid "start" / "end" cron spec.
122    *
123    * */
124    private def firstValidTimeslot(offset:Date) : Option[Timeslot] = {
125     def minimum (min:Option[Timeslot],d_start:Date,d_end:Date) : Option[Timeslot] = {
126       min match {
127         case None =>
128           Some(Timeslot(d_start,d_end))
129         case Some(Timeslot(_,d_min)) =>
130           if(d_min.getTime < d_start.getTime)
131             min
132           else
133             Some(Timeslot(d_start,d_end))
134       }
135     }
136     val to = this.to.getOrElse(infinity)
137     repeat.foldLeft (None:Option[Timeslot]) {
138       (min,r)  =>
139         r.getEnd.nextValidDate(from,to,offset) match {
140           case None =>
141             min
142           case Some(d_end) =>
143             r.getStart.nextValidDate(from,to,from) match {
144               case None => /* there is no valid starting date in the entire interval!*/
145                   min
146               case Some(_) =>  /**/
147                 r.getStart.nextValidDate(from,to,offset) match {
148                   case None => /* so there is at least one starting date before offset
149                                   and there are no starting dates after. So we are inside
150                                   an interval*/
151                     if(from.getTime <= offset.getTime && offset.getTime <= to.getTime)
152                        minimum(min,offset,d_end)
153                     else
154                        min
155                   case Some(d_start) =>
156                      if(d_start.getTime > d_end.getTime) {
157                         /* there is another starting date which occurs after the ending date.
158                         *  So we are within the interval*/
159                        minimum(min,offset,d_end)
160                      } else { /* we are outside the interval*/
161                        min
162                      }
163                 }
164             }
165      }
166    }
167   }
168
169
170   private def minValidAfter(t:Timeslot,offset:Date) : Option[Timeslot] =
171        repeat.foldLeft  (None:Option[Timeslot]) {
172         (min,r)  =>
173           r.getStart.nextValidDate(t.from,t.to,offset) match {
174             case None =>
175               min
176             case Some(d_start) =>
177               r.getEnd.nextValidDate(t.from,t.to,d_start) match {
178                 case None =>
179                   min
180                 case Some(d_end) =>
181                   min match {
182                     case None =>
183                       Some(Timeslot(d_start,d_end))
184                     case Some(Timeslot(d_min,_)) =>
185                       if(d_min.getTime < d_start.getTime)
186                         min
187                       else
188                         Some(Timeslot(d_start,d_end))
189                   }
190               }
191           }
192      }
193
194
195   override def toString =
196     //"%s(%s, %s,\n %s\n || cron: %s)".format(
197     "%s(%s, %s,%s)".format(
198     shortNameOfClass(classOf[DSLTimeFrame]),
199     new MutableDateCalc(from).toString,
200     to.map(t => new MutableDateCalc(t)),
201     repeat /*,
202     cronRepeat*/
203   )
204
205   /*def includes(d :Date) : Boolean = {
206     val now = d.getTime
207     start <= now && now <= end && (cronRepeat.isEmpty || (cronRepeat.exists {_ occurs d}))
208   } */
209
210   /*/*  Return the next VALID, MAXIMUM and CONTINUOUS date after "d" for this time frame.
211    *  The *returned time* is the end of the time frame if there exist no cron specs.
212    *  Otherwise, the returned time is maximum of the dates that
213    *  1. are continuous (no interruptions) from "d"
214    *  2. occur after "d"
215    *  3. belong in the time frame
216    *
217    *  The input parameter "d" must occur within the time frame. If there exist
218    *  cron specs "d" must belong in the time spec specified by cron.
219    */
220   def nextValidAfter1(d: Date) : Option[Date] = {
221     val now = d.getTime
222     if (now < start || now > end) None /* undefined valid is date not contained here*/
223     else if (cronRepeat.isEmpty) Some(to.getOrElse(infinity)) /* No specs! The next valid date is the end of time frame*/
224     else {
225       /* filter out cron specs that DO NOT contain d */
226       val includes_d = cronRepeat filter { _ includes d }
227     /* get the next CONTINUOUS valid date from "d" using the DSLCronSpec class */
228       val next_cont_d =  includes_d  map {_ nextContinuousValidDate d}
229       /* filter the resulting dates "d1" so that they occur after "d" and are within the timeframe bounds*/
230       val filter_invalid = next_cont_d filter {d1:Date =>  d1 != null &&
231                                                            d1.getTime > now &&
232                                                            start <= d1.getTime &&
233                                                            d1.getTime <= end}
234       /* sort the next dates and select the MAXIMUM one*/
235       val sorted = filter_invalid  sortWith {_.getTime > _.getTime}
236       sorted match {
237         case Nil => None /* Nothing interesting was found*/
238         case hd::_ => if(hd!=null) Some(hd) else None  /* the MAXIMAL next date */
239       }
240     }
241   }
242   var time_var : List[Long] = Nil
243   private def time[R](block: => R): R = {
244     val t0 = System.nanoTime()
245     val result = block    // call-by-name
246     val t1 = System.nanoTime()
247     time_var =  ((t1-t0)/1000000L) :: time_var
248     //Console.err.println("Time__xxxx: " + time_var)
249     result
250   } */
251   /*/* the nextValidDate cannot be outside the end limit of this time frame
252      Description: given date "d" get the nextValidDate after "d"
253    * */
254   def nextValidAfter(d:Date) : Option[Date] = {
255      def nextDate(r:DSLTimeFrameRepeat) : Option[Date] =
256        time(r.getStart.getMaxValidBeforeDate(from,to.getOrElse(infinity),d)) match {
257          case None =>
258            None
259          case Some(min) =>
260            r.getEnd.nextValidDate(from,to.getOrElse(infinity),min) match {
261              case None =>
262                None
263              case Some(d_max) =>
264                if(d_max.getTime < d.getTime)
265                  None
266                else
267                  Some(d_max)
268            }
269
270        }
271     if(repeat.isEmpty){
272       val d_time = d.getTime
273       if(d_time < start || d_time > end) None
274       else Some(to.getOrElse(infinity))
275     }
276     else {
277       var tmpDate : Option[Date] = null
278       repeat.collectFirst {
279         case r  if({tmpDate=nextDate(r);tmpDate} != None) => tmpDate.get
280       }
281     }
282   }*/
283 }
284
285 object DSLTimeFrame{
286   val emptyTimeFrame = DSLTimeFrame(new Date(0), None, List())//,List())
287 }