/scalate-core/src/main/scala/org/fusesource/scalate/introspector/Introspector.scala

http://github.com/scalate/scalate · Scala · 273 lines · 150 code · 58 blank · 65 comment · 24 complexity · cdd6232dd92d7f8ba1fb4631be22bfe4 MD5 · raw file

  1. /**
  2. * Copyright (C) 2009-2011 the original author or authors.
  3. * See the notice.md file distributed with this work for additional
  4. * information regarding copyright ownership.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.fusesource.scalate.introspector
  19. import java.beans.{ PropertyDescriptor, Introspector => BeanInt }
  20. import java.util.concurrent.locks.ReentrantReadWriteLock
  21. import org.fusesource.scalate.util.ProductReflector
  22. import collection.mutable.{ HashMap, Map, WeakHashMap }
  23. import java.lang.reflect.{ Modifier, Method }
  24. object Introspector {
  25. private[this] val rwl = new ReentrantReadWriteLock()
  26. private[this] val rlock = rwl.readLock
  27. private[this] val wlock = rwl.writeLock
  28. /**
  29. * The global caching strategy for introspection which by default uses a weak hash map
  30. * to avoid keeping around cached data for classes which are garbage collected
  31. */
  32. private[this] val cache = WeakHashMap.empty[Class[_], Introspector[_]]
  33. /**
  34. * Returns the Introspector for the given type using the current cache if it is defined
  35. */
  36. def apply[T](aType: Class[T]): Introspector[T] = {
  37. safeGetOrElseUpdate(aType).asInstanceOf[Introspector[T]]
  38. }
  39. /**
  40. * Does thread-safe access and modification of the cache map using read and write locks
  41. */
  42. private def safeGetOrElseUpdate[T](key: Class[T]): Introspector[_] = {
  43. def get(): Option[Introspector[_]] = {
  44. rlock.lock
  45. try {
  46. cache.get(key).filterNot(_ == null)
  47. } finally rlock.unlock
  48. }
  49. def update(): Introspector[_] = {
  50. wlock.lock
  51. try {
  52. get() getOrElse {
  53. cache.remove(key)
  54. val d = createIntrospector(key)
  55. cache.put(key, d)
  56. d
  57. }
  58. } finally wlock.unlock
  59. }
  60. get().getOrElse(update())
  61. }
  62. /**
  63. * Creates a new introspector
  64. */
  65. def createIntrospector(aType: Class[_]): Introspector[_] = {
  66. if (classOf[Product].isAssignableFrom(aType)) {
  67. new ProductIntrospector(aType)
  68. } else {
  69. new BeanIntrospector(aType)
  70. }
  71. }
  72. }
  73. /**
  74. * @version $Revision : 1.1 $
  75. */
  76. trait Introspector[T] {
  77. private[this] lazy val _expressions = createExpressions
  78. def elementType: Class[T]
  79. /**
  80. * Returns the CSS style name of the introspection type
  81. */
  82. def typeStyleName: String = decapitalize(elementType.getSimpleName)
  83. def properties: collection.Seq[Property[T]]
  84. lazy val propertyMap = Map[String, Property[T]](properties.map(p => p.name -> p).toSeq: _*)
  85. def property(name: String): Option[Property[T]] = propertyMap.get(name) match {
  86. case s: Some[Property[T]] => s
  87. case _ =>
  88. // lets allow bad case to find the property if it finds exactly one match
  89. val found = properties.filter(_.name.equalsIgnoreCase(name))
  90. if (found.size == 1) {
  91. Some(found.head)
  92. } else {
  93. None
  94. }
  95. }
  96. /**
  97. * Returns the value by name on the given instance.
  98. *
  99. * Typically this method returns the named property or returns the method with no parameters.
  100. * If no property or method with zero arguments is available then a suitable function is searched for
  101. * such as a function which takes a String parameter if using higher order functions or tags on objects.
  102. */
  103. def get(name: String, instance: T): Option[Any] = {
  104. expressions.get(name) match {
  105. case Some(e: Expression[T]) =>
  106. Some(e.evaluate(instance))
  107. case _ => None
  108. }
  109. }
  110. /**
  111. * Returns all the expressions available for the given type
  112. */
  113. def expressions: Map[String, Expression[T]] = _expressions
  114. protected def createExpressions: Map[String, Expression[T]] = {
  115. val answer = new HashMap[String, Expression[T]]
  116. for (p <- properties) {
  117. answer += p.name -> p
  118. }
  119. /**
  120. * Lazily create and add the given property using the method name
  121. * and the bean property style name of the method if it has not already
  122. * been added
  123. */
  124. def add(method: Method, property: => Property[T]): Unit = {
  125. val name = method.getName
  126. answer.getOrElseUpdate(name, property)
  127. if (name.matches("get\\p{javaUpperCase}.*")) {
  128. val propertyName = decapitalize(name.substring(3))
  129. answer.getOrElseUpdate(propertyName, property)
  130. }
  131. }
  132. val nonVoidPublicMethods = elementType.getMethods.filter(m => Modifier.isPublic(m.getModifiers) && isValidReturnType(m.getReturnType))
  133. // include all methods which have no arguments
  134. for (m <- nonVoidPublicMethods if m.getParameterTypes.isEmpty) {
  135. add(m, new MethodProperty(m))
  136. }
  137. // include all methods which have a single String parameter
  138. for (m <- nonVoidPublicMethods if isSingleParameterOfType(m, classOf[String])) {
  139. add(m, new StringFunctorProperty(m))
  140. }
  141. answer
  142. }
  143. protected def isSingleParameterOfType(method: Method, paramType: Class[_]): Boolean = {
  144. val types = method.getParameterTypes
  145. types.size == 1 && paramType.isAssignableFrom(types(0))
  146. }
  147. protected def decapitalize(name: String): String = BeanInt.decapitalize(name)
  148. protected def isValidReturnType(clazz: Class[_]): Boolean = clazz != classOf[Void] && clazz != Void.TYPE
  149. }
  150. trait Expression[T] {
  151. def evaluate(instance: T): Any
  152. }
  153. trait Property[T] extends Expression[T] {
  154. def name: String
  155. def propertyType: Class[_]
  156. def label: String
  157. def description: String
  158. def readOnly: Boolean
  159. def optional: Boolean
  160. def apply(instance: T): Any = evaluate(instance)
  161. def evaluate(instance: T): Any
  162. def set(instance: T, value: Any): Unit
  163. }
  164. class BeanIntrospector[T](val elementType: Class[T]) extends Introspector[T] {
  165. val beanInfo = BeanInt.getBeanInfo(elementType)
  166. val _properties = beanInfo.getPropertyDescriptors.filter(p => p.getReadMethod != null && p.getName != "class").map(createProperty(_))
  167. def properties = {
  168. _properties
  169. }
  170. protected def createProperty(descriptor: PropertyDescriptor) = new BeanProperty[T](descriptor)
  171. }
  172. /**
  173. * A property for a Java Bean property
  174. */
  175. case class BeanProperty[T](descriptor: PropertyDescriptor) extends Property[T] {
  176. def name = descriptor.getName
  177. def propertyType = descriptor.getPropertyType
  178. def readOnly = descriptor.getWriteMethod == null
  179. def optional = descriptor.getWriteMethod != null && !propertyType.isPrimitive
  180. // TODO use annotations to find description / label?
  181. def label = descriptor.getDisplayName
  182. def description = descriptor.getShortDescription
  183. def evaluate(instance: T) = descriptor.getReadMethod.invoke(instance)
  184. def set(instance: T, value: Any) = descriptor.getWriteMethod.invoke(instance, value.asInstanceOf[AnyRef])
  185. override def toString = "BeanProperty(" + name + ": " + propertyType.getName + ")"
  186. }
  187. class ProductIntrospector[T](val elementType: Class[T]) extends Introspector[T] {
  188. def properties = ProductReflector.accessorMethods(elementType).map(createProperty(_))
  189. protected def createProperty(method: Method) = new MethodProperty[T](method)
  190. }
  191. /**
  192. * A property which just maps to a method with no arguments
  193. */
  194. class MethodProperty[T](method: Method) extends Property[T] {
  195. def name = method.getName
  196. def propertyType = method.getReturnType
  197. def readOnly = true
  198. def optional = false
  199. def label = name
  200. def description = name
  201. def evaluate(instance: T) = method.invoke(instance)
  202. def set(instance: T, value: Any) = throw new UnsupportedOperationException("Cannot set " + this)
  203. override def toString = "MethodProperty(" + name + ": " + propertyType.getName + ")"
  204. }
  205. /**
  206. * A property which returns a Function which when invoked it invokes the underlying
  207. * method on the given object
  208. */
  209. class StringFunctorProperty[T](method: Method) extends MethodProperty[T](method) {
  210. override def evaluate(instance: T) = {
  211. def f(arg: String) = method.invoke(instance, arg)
  212. f _
  213. }
  214. override def toString = "StringFunctorProperty(" + name + ")"
  215. }