All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.eharmony.aloha.reflect.RefInfoOps.scala Maven / Gradle / Ivy

The newest version!
package com.eharmony.aloha.reflect

import scala.language.higherKinds
import scala.util.matching.Regex
import scalaz.{ Validation, ValidationNel }
import scalaz.syntax.validation.ToValidationV // scalaz.syntax.validation.ToValidationOps for latest scalaz

import java.lang.reflect.Modifier
import com.eharmony.aloha.semantics.func.GenAggFunc
import scala.reflect.ManifestFactory

import com.eharmony.aloha.util.EitherHelpers
import scala.collection.{immutable => sci}
/**
 * A facade layer on top of the scala reflection APIs to avoid bugs currently in the reflection implementation.
 * For more information, see [[https://issues.scala-lang.org/browse/SI-7555 SI-7555]].
 * @tparam RefInfoType The reflection meta-information container.
 */
sealed trait RefInfoOps[RefInfoType[_]] {

  def typeParams[A](implicit a: RefInfoType[A]): List[RefInfoType[_]]

  /**
    * Attempt to determine if `possibleIterable` is a `scala.collection.immutable.Iterable[A]` but not
    * a `scala.collection.Map`.
    *
    * This is kind of a stopgap solution because the subtype operator `<:<` doesn't really work well
    * for Manifests.  This is an issue because currently, Aloha uses Manifests rather than
    * `TypeTag`s for type checking.  Comments in ClassManifestDeprecatedApis says:
    *
    * "''this part is wrong for punting unless the rhs has no type arguments, but it's better
    * than a blindfolded pinata swing.''"
    *
    * @param a RefInfo instance of element type
    * @param possibleIterable a possible Iterable
    * @tparam A element type
    * @tparam Iter possible iterable type.
    * @return true if `possibleIterable` is a `scala.collection.immutable.Iterable[A]`
    *         but not a `scala.collection.Map[_, _]`.
    */
  def isImmutableIterableButNotMap[A, Iter](implicit a: RefInfoType[A], possibleIterable: RefInfoType[Iter]): Boolean

  /**
   * Is Sub a subtype of Super
   * @param sub RefInfo instance of type A
   * @param sup RefInfo instance of type A
   * @tparam Sub subtype in test
   * @tparam Super supertype in test
   */
  def isSubType[Sub, Super](implicit sub: RefInfoType[Sub], sup: RefInfoType[Super]): Boolean

  /**
   * Provides a generic way of lifting A into a container for which a RefInfoType instance can be retrieved.
   * @tparam A type to be lifted.
   */
  trait Lift1[A] {
    def in[M[_]](implicit w: RefInfoType[M[A]]): RefInfoType[M[A]] = w
  }

  private[this] class C1[A: RefInfoType] extends Lift1[A]

  /**
   * Provides a generic way of lifting A and B into a container for which a RefInfoType instance can be retrieved.
   * @tparam A first type to be lifted.
   * @tparam B second type to be lifted.
   */
  trait Lift2[A, B] {
    def in[M[_, _]](implicit w: RefInfoType[M[A, B]]): RefInfoType[M[A, B]] = w
  }

  private[this] class C2[A: RefInfoType, B: RefInfoType] extends Lift2[A, B]

  /**
   * Get the RefInfoType instance for type A.
   * @tparam A type for which reflection information should be retrieved.
   * @return
   */
  def refInfo[A: RefInfoType] = implicitly[RefInfoType[A]]

  /**
   * Get a Lift1 instance to wrap type A in a RefInfoType.
   * {{{
   * val w = RefInfoOps.wrap[String]
   * val o1 = w.in[Option]
   * val o2 = RefInfoOps.option[String]
   * assert(o1 == o2)
   * }}}
   * @tparam A type being lifted
   * @return
   */
  def wrap[A: RefInfoType] = new C1[A].asInstanceOf[Lift1[A]]

  /**
   * Provides a generic mechanism to wrap the types A and B inside a container taking two parameters.
   *
   * {{{
   * val w = RefInfoOps.wrap[String, Double]
   * // pass around w to some other object ...
   * val e: RefInfo[Either[String, Double]] = w.in[Either]
   *
   * import scalaz.Validation
   * val v1 = RefInfoOps.wrap[String, Double].in[Validation]
   * val v2 = RefInfoOps.validation[String, Double]
   * assert(v1 == v2)
   * }}}
   *
   * @tparam A first type being lifted.
   * @tparam B second type being lifted.
   * @return a Lift2 instance containing information about A and B that can be used to later construct a
   *         RefInfoType from a kind passed to the in function.
   */
  def wrap[A: RefInfoType, B: RefInfoType] = new C2[A, B].asInstanceOf[Lift2[A, B]]

  /**
   * Get reflection info from a simple Class.  Will throw an exception if we find that the type is parametrized.
   * @param clazz a Class object
   * @tparam A the type of the RefInfoType to create
   * @return
   */
  @throws[IllegalArgumentException]("If clazz is null RefInfoType's string representation contains brackets")
  def fromSimpleClass[A](clazz: Class[_ <: A]): RefInfoType[A]

  def execStaticNoArgFunc[A: RefInfo](name: String): Either[Seq[String], AnyRef]

  /**
   * Get reflection information about scalaz.Validation.
   * @tparam A left type parameter of Validation
   * @tparam B right type parameter of Validation
   * @return reflection information about scalaz.Validation
   */
  def validation[A: RefInfoType, B: RefInfoType]: RefInfoType[Validation[A, B]]

  /**
   * Get reflection information about scalaz.ValidationNel.
   * @tparam A left type parameter of ValidationNel
   * @tparam B right type parameter of ValidationNel
   * @return reflection information about scalaz.ValidationNel
   */
  def validationNel[A: RefInfoType, B: RefInfoType]: RefInfoType[ValidationNel[A, B]]

  /**
   * Get reflection information about [[com.eharmony.aloha.semantics.func.GenAggFunc]]
   * @tparam A function input type
   * @tparam B function output type
   * @return reflection information about [[com.eharmony.aloha.semantics.func.GenAggFunc]]
   */
  def genAggFunc[A: RefInfoType, B: RefInfoType]: RefInfoType[GenAggFunc[A, B]]

  /**
   * Get reflection information about scala.util.Either.
   * @tparam A left type parameter of Either
   * @tparam B right type parameter of Either
   * @return reflection information about scala.util.Either
   */
  def either[A: RefInfoType, B: RefInfoType]: RefInfoType[Either[A, B]]

  /**
   * Get reflection information about Option.
   * @tparam A type of Option
   * @return reflection information about Option.
   */
  def option[A: RefInfoType]: RefInfoType[Option[A]]

  /**
   * Get a string representation of the RefInfoType.  This should be adequate to full characterize the type
   * and any type parameters (i.e., should contain the erased type information)
   * @tparam A type of the reflected object whose string-based representation the function is retrieving.
   * @return string-based representation of A
   */
  def toString[A: RefInfoType]: String
}

/**
 * Concrete implementations for retrieving and manipulating reflection meta information.  All reflection
 * in the library should use this facade and should not use Manifests or TypeTags.
 */
object RefInfoOps extends RefInfoOps[RefInfo] with EitherHelpers {

  def typeParams[A](implicit a: RefInfo[A]): List[RefInfo[_]] = a.typeArguments

  /**
    * Attempt to determine if `possibleIterable` is a `scala.collection.immutable.Iterable[A]` but not
    * a `scala.collection.Map`.
    *
    * This is kind of a stopgap solution because the subtype operator `<:<` doesn't really work well
    * for Manifests.  This is an issue because currently, Aloha uses Manifests rather than
    * `TypeTag`s for type checking.  Comments in ClassManifestDeprecatedApis says:
    *
    * "''this part is wrong for punting unless the rhs has no type arguments, but it's better
    * than a blindfolded pinata swing.''"
    *
    * This implementation covers many simple cases in the intersection of scala 2.10
    * and 2.11 instances.  See test for coverage.
    *
    * @param a RefInfo instance of element type
    * @param possibleIterable a possible Iterable
    * @tparam A element type
    * @tparam Iter possible iterable type.
    * @return true if `possibleIterable` is a `scala.collection.immutable.Iterable[A]`
    *         but not a `scala.collection.Map[_, _]`.
    */
  def isImmutableIterableButNotMap[A, Iter](implicit a: RefInfo[A], possibleIterable: RefInfo[Iter]): Boolean = {
    classOf[scala.collection.immutable.Iterable[_]].isAssignableFrom(possibleIterable.runtimeClass) &&
    !classOf[scala.collection.Map[_, _]].isAssignableFrom(possibleIterable.runtimeClass) &&
    (List(a) == possibleIterable.typeArguments ||
      (possibleIterable.typeArguments.isEmpty && (
        ((a == RefInfo.Int || a == RefInfo.JavaInteger) &&
          (possibleIterable == RefInfo[sci.BitSet] ||
           possibleIterable == RefInfo[sci.BitSet.BitSet1] ||
           possibleIterable == RefInfo[sci.BitSet.BitSet2] ||
           possibleIterable == RefInfo[sci.BitSet.BitSetN] ||
           possibleIterable == RefInfo[sci.Range])) ||
        ((a == RefInfo.Char || a == RefInfo.JavaCharacter) &&
           possibleIterable == RefInfo[sci.WrappedString]))))
  }


  /**
   * Is Sub a subtype of Super
   * @param sub RefInfo instance of type A
   * @param sup RefInfo instance of type A
   * @tparam Sub subtype in test
   * @tparam Super supertype in test
   */
  @SuppressWarnings(Array("deprecation"))
  def isSubType[Sub, Super](implicit sub: RefInfo[Sub], sup: RefInfo[Super]): Boolean = sub <:< sup

  def isJavaInterface[A](implicit a: RefInfo[A]) = a.runtimeClass.isInterface

  def classRegex[A](implicit a: RefInfo[A]): Regex = {
    val erasure = a.runtimeClass
    val pkg = erasure.getPackage.getName
    val simpleName = erasure.getSimpleName
    val canonicalName = erasure.getCanonicalName
    val name = canonicalName.drop(pkg.length)
    Seq(canonicalName, name, simpleName).distinct.mkString("(", "|", ")").replace(".", "\\.").r
  }

  /**
   * Get reflection info from a simple Class.  Will throw an exception if we find that the type is parametrized.
   * @param clazz a Class object
   * @tparam A the type of the RefInfoType to create
   * @return reflection information about simple type represented by clazz.
   */
  @throws[IllegalArgumentException]("If clazz is null RefInfoType's string representation contains brackets")
  def fromSimpleClass[A](clazz: Class[_ <: A]): RefInfo[A] = {
    require(clazz != null, "clazz must not be null")
    val ri = ManifestFactory.classType[A](clazz)
    val s = RefInfoOps.toString[A](ri)
    require(s.find(c => c == '[' || c == ']').isEmpty, s"clazz appears to be parametrized: $s")
    ri
  }

  def execStaticNoArgFunc[A: RefInfo](name: String): Either[Seq[String], AnyRef] = {
    // See http://stackoverflow.com/a/15096862/189964 when changing over to TypeTag-based reflection.

    val v: ValidationNel[String, AnyRef] =
      try {
        val r = for {
          methods <- implicitly[RefInfo[A]].runtimeClass.getMethods.successNel
          filtered <- methods.filter(m => m.getName == name && Modifier.isStatic(m.getModifiers) && 0 == m.getParameterTypes.size).successNel
          f <- filtered.headOption map { _.successNel } getOrElse s"Couldn't find method $name in ${RefInfoOps.toString[A]}".failNel
          y <- f.invoke(null).successNel
        } yield y
        r
      } catch {
        case e: Throwable => s"Couldn't execute ${RefInfoOps.toString[A]}.$name.  Error: ${e.getMessage}".failNel
      }
    fromValidationNel(v)
  }

  /**
   * Get reflection information about scalaz.Validation.
   * @tparam A left type parameter of Validation
   * @tparam B right type parameter of Validation
   * @return reflection information about scalaz.Validation
   */
  def validation[A: RefInfo, B: RefInfo] = wrap[A, B].in[Validation]

  /**
   * Get reflection information about scalaz.ValidationNel.
   * @tparam A left type parameter of ValidationNel
   * @tparam B right type parameter of ValidationNel
   * @return reflection information about scalaz.ValidationNel
   */
  def validationNel[A: RefInfo, B: RefInfo] = wrap[A, B].in[ValidationNel]

  /**
   * Get reflection information about [[com.eharmony.aloha.semantics.func.GenAggFunc]]
   * @tparam A function input type
   * @tparam B function output type
   * @return reflection information about [[com.eharmony.aloha.semantics.func.GenAggFunc]]
   */
  def genAggFunc[A: RefInfo, B: RefInfo] = wrap[A, B].in[GenAggFunc]

  /**
   * Get reflection information about scala.util.Either.
   * @tparam A left type parameter of Either
   * @tparam B right type parameter of Either
   * @return reflection information about scala.util.Either
   */
  def either[A: RefInfo, B: RefInfo] = wrap[A, B].in[Either]

  /**
   * Get reflection information about Option.
   * @tparam A type of Option
   * @return reflection information about Option.
   */
  def option[A: RefInfo] = wrap[A].in[Option]

  /**
   * Get a string representation of the RefInfoType.  This should be adequate to full characterize the type
   * and any type parameters (i.e., should contain the erased type information)
   * @tparam A type of the reflected object whose string-based representation the function is retrieving.
   * @return string-based representation of A
   */
  def toString[A: RefInfo] = implicitly[RefInfo[A]].toString().replace("$", ".")
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy