WIP: ResourceEvent-related refactorings
[aquarium] / src / main / scala / gr / grnet / aquarium / util / package.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
37
38 import scala.collection.mutable.ListBuffer
39 import com.ckkloverdos.maybe.{Failed, Just, Maybe}
40 import java.nio.charset.Charset
41 import java.io.{PrintWriter, StringWriter}
42 import annotation.tailrec
43
44
45 /**
46  * Utility functions.
47  *
48  * @author Christos KK Loverdos <loverdos@gmail.com>.
49  */
50 package object util {
51   final val UTF_8_Charset = Charset.forName("UTF-8")
52
53   def tryOption[A](f: => A): Option[A] = {
54     try Some(f)
55     catch {
56       case _: Exception => None
57     }
58   }
59
60   def withCloseable[C <: { def close(): Unit}, A](c: C)(f: C => A): A = {
61     try {
62       f(c)
63     } finally {
64       c.close()
65     }
66   }
67
68   def afterLastIndexOf(separator: String, input: String) = {
69     input.substring(input.lastIndexOf(separator) + 1)
70   }
71   /**
72    * Compute the class name excluding any leading packages.
73    *
74    * This is basically the name after the last dot.
75    */
76   def shortNameOfClass(theClass: Class[_]): String = {
77     afterLastIndexOf(".", theClass.getName)
78   }
79
80   /**
81    * Compute the class name excluding any leading packages and any `$` prefixes.
82    *
83    * This is basically the name after the last dot and after any dollar sign.
84    */
85   def simpleNameOfClass(theClass: Class[_]): String = {
86     afterLastIndexOf("$", shortNameOfClass(theClass))
87   }
88
89   /**
90    * For the class of the provided object, compute the class name excluding any leading packages.
91    *
92    * This is basically the name after the last dot.
93    *
94    * The `null` value is mapped to string `"null"`.
95    */
96   def shortClassNameOf(anyRef: AnyRef): String = {
97     anyRef match {
98       case null =>
99         "<null>"
100       case clz: Class[_] =>
101         shortNameOfClass(clz)
102       case obj =>
103         shortNameOfClass(obj.getClass)
104     }
105   }
106
107   /**
108    * Compute the class name excluding any leading packages and any `$` prefixes.
109    *
110    * This is basically the name after the last dot and after any dollar sign.
111    */
112   def simpleClassNameOf(anyRef: AnyRef) = {
113     afterLastIndexOf("$", shortClassNameOf(anyRef))
114   }
115
116   def safeToStringOrNull(obj: AnyRef): String = obj match {
117     case null => null
118     case _ => obj.toString
119   }
120
121   def displayableObjectInfo(obj: AnyRef): String = {
122     "[%s] %s".format(obj.getClass, obj)
123   }
124
125   /**
126    * This basically turns an [[scala.Option]] into a [[com.ckkloverdos.maybe.Maybe]] when asking a
127    * [[scala.collection.Map]] for a key.
128    *
129    * @param map The input map.
130    * @param key The key we are interested in.
131    * @tparam A The type of keys.
132    * @tparam B The type of values.
133    *
134    * @return A [[com.ckkloverdos.maybe.Just]] if a value was found, a
135    *           [[com.ckkloverdos.maybe.NoVal]] if nothing was found and a
136    *           [[com.ckkloverdos.maybe.Failed]] if some error happened.
137    */
138   @inline
139   def findFromMapAsMaybe[A, B <: AnyRef](map: scala.collection.Map[A, B], key: A): Maybe[B] = Maybe {
140     map.get(key) match {
141       case Some(value) ⇒
142         value
143       case None ⇒
144         null.asInstanceOf[B]
145     }
146   }
147
148   @inline
149   def findAndRemoveFromMap[A, B <: AnyRef](map: scala.collection.mutable.Map[A, B], key: A): Option[B] = {
150     map.remove(key)
151   }
152
153   // Dear scalac. Optimize this.
154   def nspaces(n: Int): String = {
155     ("" /: (1 to n)) ((string, _) => string + " ")
156   }
157
158   def rpad(s: String, size: Int) = {
159     s + nspaces((size - s.length()) max 0)
160   }
161   
162   def maxStringSize[A](trav: Traversable[A]): Int = {
163     (0 /: trav)(_ max _.toString.length)
164   }
165
166   /**
167    * Given a [[com.ckkloverdos.maybe.Maybe]] that is actually a [[com.ckkloverdos.maybe.Failed]], return the latter.
168    *
169    * Use this only when you are sure what the `maybe` contains, since the methods can break type safety.
170    *
171    * @param maybe
172    * @tparam A
173    * @return
174    */
175   def failedForSure[A](maybe: Maybe[A]): Failed = {
176     maybe.asInstanceOf[Failed]
177   }
178
179   /**
180    * Given a [[com.ckkloverdos.maybe.Maybe]] that is actually a [[com.ckkloverdos.maybe.Just]], return the latter.
181    *
182    * Use this only when you are sure what the `maybe` contains, since the methods can break type safety.
183    *
184    * @param maybe
185    * @tparam A
186    * @return
187    */
188   def justForSure[A](maybe: Maybe[A]): Just[A] = {
189     maybe.asInstanceOf[Just[A]]
190   }
191
192   /**
193    * Transform an array of bytes to a string, assuming UTF-8 encoding.
194    */
195   def makeString(bytes: Array[Byte]): String = {
196     new String(bytes, UTF_8_Charset)
197   }
198
199   /**
200    * Transform a string to an array of bytes, following a UTF-8 decoding scheme.
201    */
202   def makeBytes(string: String): Array[Byte] = {
203     string.getBytes(UTF_8_Charset)
204   }
205
206   /**
207    * Return the stack trace of the given [[java.lang.Throwable]] in the form of [[java.lang.String]].
208    *
209    * @param t
210    * @return
211    */
212   def stringOfStackTrace(t: Throwable): String = {
213     val sw = new StringWriter()
214     val pw = new PrintWriter(sw)
215
216     t.printStackTrace(pw)
217     pw.flush()
218     pw.close()
219
220     sw.toString
221   }
222
223   private[this]
224   def chainOfCausesBuffer(t: Throwable): ListBuffer[String] = {
225     @tailrec
226     def loop(t: Throwable, buffer: ListBuffer[String]): ListBuffer[String] = {
227       t match {
228         case null ⇒
229           buffer
230
231         case _ ⇒
232           buffer.append("%s: %s".format(shortClassNameOf(t), t.getMessage))
233           loop(t.getCause, buffer)
234       }
235     }
236
237     loop(t, new ListBuffer[String])
238   }
239
240   def chainOfCauses(t: Throwable): List[String] = {
241     chainOfCausesBuffer(t).toList
242   }
243
244   def sourceOfTraceElement(st: StackTraceElement): Option[String] = {
245     tryOption {
246       val path = Class.forName(st.getClassName).getProtectionDomain.getCodeSource.getLocation.getPath
247       val file = new java.io.File(path)
248       file.getName
249     }
250   }
251
252   def formatTraceElement(st: StackTraceElement): String = {
253     val source = sourceOfTraceElement(st)
254     "%s.%s(%s:%s)%s".format(
255       st.getClassName,
256       st.getMethodName,
257       st.getFileName,
258       st.getLineNumber,
259       if(source.isDefined) " [%s]".format(source.get) else ""
260     )
261   }
262
263   def chainOfCausesForLogging(t: Throwable, caughtTraceIndex: Int = 3) = {
264     val buf = chainOfCausesBuffer(t)
265     val happenedTrace = t.getStackTrace()(0)
266     val caughtTrace = new Exception().getStackTrace()(caughtTraceIndex)
267
268     buf.prepend("[Happened @] %s".format(formatTraceElement(happenedTrace)))
269     buf.prepend("[Caught   @] %s".format(formatTraceElement(caughtTrace)))
270
271     val noNL = buf.map { line ⇒
272       "!! " + line.replaceAll("""[\n\r]+""", ", ")
273     }
274
275     noNL mkString "\n"
276   }
277
278   private[this] lazy val _isRunningTests =
279     new Exception().getStackTrace().find(_.getClassName.startsWith("org.junit")).isDefined
280
281   def isRunningTests() = _isRunningTests
282 }