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

org.json4s.Meta.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2009-2010 WorldWide Conferencing, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.json4s

import java.lang.reflect.{Constructor => JConstructor, Field, Type, ParameterizedType, GenericArrayType}
import java.util.Date
import java.sql.Timestamp
import com.thoughtworks.paranamer.{ParameterNamesNotFoundException, BytecodeReadingParanamer, CachingParanamer}
import scalashim._

case class TypeInfo(clazz: Class[_], parameterizedType: Option[ParameterizedType])

trait ParameterNameReader {
  def lookupParameterNames(constructor: JConstructor[_]): Traversable[String]
}

object Meta {
  import com.thoughtworks.paranamer._

  /** Intermediate metadata format for case classes.
   *  This ADT is constructed (and then memoized) from given case class using reflection.
   *
   *  Example mapping.
   *
   *  package xx
   *  case class Person(name: String, address: Address, children: List[Child])
   *  case class Address(street: String, city: String)
   *  case class Child(name: String, age: BigInt)
   *
   *  will produce following Mapping:
   *
   *  Constructor("xx.Person", List(
   *    Arg("name", Value(classOf[String])),
   *    Arg("address", Constructor("xx.Address", List(Value("street"), Value("city")))),
   *    Arg("children", Col(classOf[List[_]], Constructor("xx.Child", List(Value("name"), Value("age")))))))
   */
  sealed abstract class Mapping
  case class Arg(path: String, mapping: Mapping, optional: Boolean, default: Option[() => Any]) extends Mapping
  case class Value(targetType: Class[_], default: Option[() => Any]) extends Mapping
  case class Cycle(targetType: Type) extends Mapping
  case class Dict(mapping: Mapping) extends Mapping
  case class Col(targetType: TypeInfo, mapping: Mapping) extends Mapping
  case class Constructor(targetType: TypeInfo, choices: List[DeclaredConstructor], companion: Option[(Class[_], AnyRef)]) extends Mapping {


    def bestMatching(argNames: List[String]): Option[DeclaredConstructor] = {
      val names = Set(argNames: _*)
      def countOptionals(args: List[Arg]) =
        args.foldLeft(0)((n, x) => {
          val defv = companion flatMap {
            case (cc, co) => Reflection.defaultValue(cc, co, x.path, argNames.indexOf(x.path))
          }
          if (x.optional || defv.isDefined) n+1 else n
        })
      def score(args: List[Arg]) =
        args.foldLeft(0)((s, arg) => if (names.contains(arg.path)) s+1 else -100)

      if (choices.isEmpty) None
      else {
        val best = choices.tail.foldLeft((choices.head, score(choices.head.args))) { (best, c) =>
          val newScore = score(c.args)
          if (newScore == best._2) {
            if (countOptionals(c.args) < countOptionals(best._1.args))
              (c, newScore) else best
          } else if (newScore > best._2) (c, newScore) else best
        }
        Some(best._1)
      }
    }
  }

  case class DeclaredConstructor(constructor: JConstructor[_], args: List[Arg])

  // Current constructor parsing context. (containingClass + allArgs could be replaced with Constructor)
  case class Context(argName: String, containingClass: Class[_], allArgs: List[(String, Type)])

  private val mappings = new Memo[Type, Mapping]
  private val unmangledNames = new Memo[String, String]
  private val paranamer = new CachingParanamer(new BytecodeReadingParanamer)

  object ParanamerReader extends ParameterNameReader {
    def lookupParameterNames(constructor: JConstructor[_]): Traversable[String] =
      paranamer.lookupParameterNames(constructor)
  }

  private[json4s] def mappingOf(clazz: Type, typeArgs: Seq[Class[_]] = Seq())
                             (implicit formats: Formats): Mapping = {
    import Reflection._

    def constructors(t: Type, visited: Set[Type], context: Option[Context]) = {
      Reflection.constructors(t, formats.parameterNameReader, context).map { case (c, args) =>
        DeclaredConstructor(c, args.map { case (name, tt) =>
          toArg(unmangleName(name), tt, visited, Context(name, c.getDeclaringClass, args)) })
      }
    }

    def toArg(name: String, genericType: Type, visited: Set[Type], context: Context): Arg = {
      def mkContainer(t: Type, k: Kind, valueTypeIndex: Int, factory: Mapping => Mapping) =
        if (typeConstructor_?(t)) {
          val typeArgs = typeConstructors(t, k)(valueTypeIndex)
          factory(fieldMapping(typeArgs, None)._1) // TODO: default values
        } else factory(fieldMapping(typeParameters(t, k, context)(valueTypeIndex), None)._1) // TODO: default values

      def parameterizedTypeOpt(t: Type) = t match {
        case x: ParameterizedType => 
          val typeArgs = x.getActualTypeArguments.toList.zipWithIndex
            .map { case (t, idx) =>
              if (t == classOf[java.lang.Object]) 
                ScalaSigReader.readConstructor(context.argName, context.containingClass, idx, context.allArgs.map(_._1))
              else t
            }
          Some(mkParameterizedType(x.getRawType, typeArgs))
        case _ => None
      }

      def mkConstructor(t: Type) = 
        if (visited.contains(t)) (Cycle(t), false)
        else {
          val companion: Option[(Class[_], AnyRef)] = {
            val clazz = rawClassOf(t)
            val path = if (clazz.getName.endsWith("$")) clazz.getName else "%s$".format(clazz.getName)
            ScalaSigReader.resolveClass[AnyRef](path, Vector(clazz.getClassLoader)) map { sig =>
              (sig, sig.getField(ScalaSigReader.ModuleFieldName).get(null))
            }
          }

          (Constructor(TypeInfo(rawClassOf(t), parameterizedTypeOpt(t)), constructors(t, visited + t, Some(context)), companion), false)
        }

      def fieldMapping(t: Type, default: Option[() => Any]): (Mapping, Boolean) = t match {
        case pType: ParameterizedType => 
          val raw = rawClassOf(pType)
          val info = TypeInfo(raw, Some(pType))
          if (classOf[Set[_]].isAssignableFrom(raw))
            (mkContainer(t, `* -> *`, 0, Col.apply(info, _)), false)
          else if (raw.isArray)
            (mkContainer(t, `* -> *`, 0, Col.apply(info, _)), false)
          else if (classOf[Option[_]].isAssignableFrom(raw))
            (mkContainer(t, `* -> *`, 0, identity _), true)
          else if (classOf[Map[_, _]].isAssignableFrom(raw))
            (mkContainer(t, `(*,*) -> *`, 1, Dict.apply _), false)
          else if (classOf[Seq[_]].isAssignableFrom(raw))
            (mkContainer(t, `* -> *`, 0, Col.apply(info, _)), false)
          else 
            mkConstructor(t)
        case aType: GenericArrayType =>
          // Couldn't find better way to reconstruct proper array type:
          val raw = java.lang.reflect.Array.newInstance(rawClassOf(aType.getGenericComponentType), 0: Int).getClass
          (Col(TypeInfo(raw, None), fieldMapping(aType.getGenericComponentType, None)._1), false)
        case raw: Class[_] =>
          if (primitive_?(raw)) (Value(raw, default), false)
          else if (raw.isArray)
            (mkContainer(t, `* -> *`, 0, Col.apply(TypeInfo(raw, None), _)), false)
          else 
            mkConstructor(t)
        case x => (Constructor(TypeInfo(classOf[AnyRef], None), Nil, None), false)
      }

      val default = {
        val idx = context.allArgs.map(_._1).indexOf(context.argName)
        if (idx > -1) {
          val companion: Option[(Class[_], AnyRef)] = {
            val c = context.containingClass
            val path = if (c.getName.endsWith("$")) c.getName else "%s$".format(c.getName)
            ScalaSigReader.resolveClass[AnyRef](path, Vector(c.getClassLoader)) map { sig =>
              (sig, sig.getField(ScalaSigReader.ModuleFieldName).get(null))
            }
          }
          companion flatMap { case (cc, co) => defaultValue(cc, co, context.argName, idx) }
        } else None

      }
      val (mapping, optional) = fieldMapping(genericType, default)

      Arg(name, mapping, optional, default)
    }

    if (primitive_?(clazz)) {
      Value(rawClassOf(clazz), None)
    } else {
      mappings.memoize(clazz, t => {
        val c = rawClassOf(t)
        val (pt, typeInfo) = 
          if (typeArgs.isEmpty) (t, TypeInfo(c, None))
          else {
            val t = mkParameterizedType(c, typeArgs)
            (t, TypeInfo(c, Some(t)))
          }

        val companion: Option[(Class[_], AnyRef)] = {

          val path = if (c.getName.endsWith("$")) c.getName else "%s$".format(c.getName)
          ScalaSigReader.resolveClass[AnyRef](path, Vector(c.getClassLoader)) map { sig =>
            (sig, sig.getField(ScalaSigReader.ModuleFieldName).get(null))
          }
        }
        Constructor(typeInfo, constructors(pt, Set(), None), companion)
      })
    }
  }

  private[json4s] def rawClassOf(t: Type): Class[_] = t match {
    case c: Class[_] => c
    case p: ParameterizedType => rawClassOf(p.getRawType)
    case x => fail("Raw type of " + x + " not known")
  }

  private[json4s] def mkParameterizedType(owner: Type, typeArgs: Seq[Type]) = 
    new ParameterizedType {
      def getActualTypeArguments = typeArgs.toArray
      def getOwnerType = owner
      def getRawType = owner
      override def toString = getOwnerType + "[" + getActualTypeArguments.mkString(",") + "]"
    }

  private[json4s] def unmangleName(name: String) =
    unmangledNames.memoize(name, scala.reflect.NameTransformer.decode)

  private[json4s] def fail(msg: String, cause: Exception = null) = throw new MappingException(msg, cause)

  private class Memo[A, R] {
    private var cache = Map[A, R]()

    def memoize(x: A, f: A => R): R = synchronized {
      if (cache contains x) cache(x) else {
        val ret = f(x)
        cache += (x -> ret)
        ret
      }
    }
  }

  object Reflection {
    import java.lang.reflect._
    import scala.collection.JavaConversions._

    sealed abstract class Kind
    case object `* -> *` extends Kind
    case object `(*,*) -> *` extends Kind

    val primitives = Map[Class[_], Unit]() ++ (List[Class[_]](
      classOf[String], classOf[Int], classOf[Long], classOf[Double],
      classOf[Float], classOf[Byte], classOf[BigInt], classOf[Boolean],
      classOf[Short], classOf[java.lang.Integer], classOf[java.lang.Long],
      classOf[java.lang.Double], classOf[java.lang.Float],
      classOf[java.lang.Byte], classOf[java.lang.Boolean], classOf[Number],
      classOf[java.lang.Short], classOf[Date], classOf[Timestamp], classOf[Symbol], classOf[JValue],
      classOf[JObject], classOf[JArray]).map((_, ())))

    private val primaryConstructors = new Memo[Class[_], List[(String, Type)]]
    private val declaredFields = new Memo[(Class[_], String), Boolean]

    def constructors(t: Type, names: ParameterNameReader, context: Option[Context]): List[(JConstructor[_], List[(String, Type)])] =
      rawClassOf(t).getDeclaredConstructors.map(c => (c, constructorArgs(t, c, names, context))).toList

    def constructorArgs(t: Type, constructor: JConstructor[_], 
                        nameReader: ParameterNameReader, context: Option[Context]): List[(String, Type)] = {
      def argsInfo(c: JConstructor[_], typeArgs: Map[TypeVariable[_], Type]) = {
        val Name = """^((?:[^$]|[$][^0-9]+)+)([$][0-9]+)?$"""r
        def clean(name: String) = name match {
          case Name(text, junk) => text
        }
        try {
          val names = nameReader.lookupParameterNames(c).map(clean)
          val types = c.getGenericParameterTypes.toList.zipWithIndex map {
            case (v: TypeVariable[_], idx) => 
              val arg = typeArgs.getOrElse(v, v)
              if (arg == classOf[java.lang.Object]) 
                context.map(ctx => ScalaSigReader.readConstructor(ctx.argName, ctx.containingClass, idx, ctx.allArgs.map(_._1))).getOrElse(arg)
              else arg
            case (x, _) => x
          }
          names.toList.zip(types)
        } catch {
          case e: ParameterNamesNotFoundException => Nil
        }
      }

      t match {
        case c: Class[_] => argsInfo(constructor, Map())
        case p: ParameterizedType =>
          val vars = 
            Map() ++ rawClassOf(p).getTypeParameters.toList.map(_.asInstanceOf[TypeVariable[_]]).zip(p.getActualTypeArguments.toList) // FIXME this cast should not be needed
          argsInfo(constructor, vars)
        case x => fail("Do not know how query constructor info for " + x)
      }
    }

    def primaryConstructorArgs(c: Class[_])(implicit formats: Formats) = {
      def findMostComprehensive(c: Class[_]): List[(String, Type)] = {
        val ord = Ordering[Int].on[JConstructor[_]](_.getParameterTypes.size)
        val primary = c.getDeclaredConstructors.max(ord)
        constructorArgs(c, primary, formats.parameterNameReader, None)
      }

      primaryConstructors.memoize(c, findMostComprehensive(_))
    }

    def typeParameters(t: Type, k: Kind, context: Context): List[Class[_]] = {
      def term(i: Int) = t match {
        case ptype: ParameterizedType => ptype.getActualTypeArguments()(i) match {
          case c: Class[_] => 
            if (c == classOf[java.lang.Object]) 
              ScalaSigReader.readConstructor(context.argName, context.containingClass, i, context.allArgs.map(_._1))
            else c
          case p: ParameterizedType => p.getRawType.asInstanceOf[Class[_]]
          case x => fail("do not know how to get type parameter from " + x)
        }
        case clazz: Class[_] if (clazz.isArray) => i match {
          case 0 => clazz.getComponentType.asInstanceOf[Class[_]]
          case _ => fail("Arrays only have one type parameter")
        }
        case clazz: GenericArrayType => i match {
          case 0 => clazz.getGenericComponentType.asInstanceOf[Class[_]]
          case _ => fail("Arrays only have one type parameter")
        }
        case _ => fail("Unsupported Type: " + t + " (" + t.getClass + ")")
      }

      k match {
        case `* -> *`     => List(term(0))
        case `(*,*) -> *` => List(term(0), term(1))
      }
    }

    def typeConstructors(t: Type, k: Kind): List[Type] = {
      def types(i: Int): Type = {
        val ptype = t.asInstanceOf[ParameterizedType]
        ptype.getActualTypeArguments()(i) match {
          case p: ParameterizedType => p
          case c: Class[_] => c
        }
      }

      k match {
        case `* -> *`     => List(types(0))
        case `(*,*) -> *` => List(types(0), types(1))
      }
    }

    def primitive_?(t: Type) = t match {
      case clazz: Class[_] => primitives contains clazz
      case _ => false
    }

    def static_?(f: Field) = Modifier.isStatic(f.getModifiers)
    def typeConstructor_?(t: Type) = t match {
      case p: ParameterizedType =>
        p.getActualTypeArguments.exists(_.isInstanceOf[ParameterizedType])
      case _ => false
    }

    def array_?(x: Any) = x != null && classOf[scala.Array[_]].isAssignableFrom(x.asInstanceOf[AnyRef].getClass)

    def fields(clazz: Class[_]): List[(String, TypeInfo)] = {
      val fs = clazz.getDeclaredFields.toList
        .filterNot(f => Modifier.isStatic(f.getModifiers) || Modifier.isTransient(f.getModifiers))
        .map(f => (f.getName, TypeInfo(f.getType, f.getGenericType match {
          case p: ParameterizedType => Some(p)
          case _ => None
        })))
      fs ::: (if (clazz.getSuperclass == null) Nil else fields(clazz.getSuperclass))
    }

    def setField(a: AnyRef, name: String, value: Any) = {      
      val f = findField(a.getClass, name)
      f.setAccessible(true)
      f.set(a, value)
    }

    def getField(a: AnyRef, name: String) = {
      val f = findField(a.getClass, name)
      f.setAccessible(true)
      f.get(a)
    }

    def findField(clazz: Class[_], name: String): Field = try {
      clazz.getDeclaredField(name)
    } catch {
      case e: NoSuchFieldException => 
        if (clazz.getSuperclass == null) throw e 
        else findField(clazz.getSuperclass, name)
    }

    def hasDeclaredField(clazz: Class[_], name: String): Boolean = {
      def declaredField = try {
        clazz.getDeclaredField(name)
        true
      } catch {
        case e: NoSuchFieldException => false
      }

      declaredFields.memoize((clazz, name), _ => declaredField)
    }

    def mkJavaArray(x: Any, componentType: Class[_]) = {
      val arr = x.asInstanceOf[scala.Array[_]]
      val a = java.lang.reflect.Array.newInstance(componentType, arr.size)
      var i = 0
      while (i < arr.size) {
        java.lang.reflect.Array.set(a, i, arr(i))
        i += 1
      }
      a
    }

    def defaultValue(compClass: Class[_], compObj: AnyRef, argName: String, argIndex: Int) = {
//      println("Getting default for %s on %s" format (argName, compClass))
      try {
        // Some(null) is actually "desirable" here because it allows using null as a default value for an ignored field
        val a = Option(compClass.getMethod("apply$default$%d".format(argIndex + 1))) map { meth => () => meth.invoke(compObj) }
//        println("default for %s on %s is %s" format(argName, compClass, a))
        a
      }
      catch {
        case _ =>
//          println("no default found for %s on %s" format (argName, compClass))
          None // indicates no default value was supplied
      }
    }

    def primitive2jvalue(a: Any)(implicit formats: Formats) = a match {
      case x: String => JString(x)
      case x: Int => JInt(x)
      case x: Long => JInt(x)
      case x: Double => JDouble(x)
      case x: Float => JDouble(x)
      case x: Byte => JInt(BigInt(x))
      case x: BigInt => JInt(x)
      case x: Boolean => JBool(x)
      case x: Short => JInt(BigInt(x))
      case x: java.lang.Integer => JInt(BigInt(x.asInstanceOf[Int]))
      case x: java.lang.Long => JInt(BigInt(x.asInstanceOf[Long]))
      case x: java.lang.Double => JDouble(x.asInstanceOf[Double])
      case x: java.lang.Float => JDouble(x.asInstanceOf[Float])
      case x: java.lang.Byte => JInt(BigInt(x.asInstanceOf[Byte]))
      case x: java.lang.Boolean => JBool(x.asInstanceOf[Boolean])
      case x: java.lang.Short => JInt(BigInt(x.asInstanceOf[Short]))
      case x: Date => JString(formats.dateFormat.format(x))
      case x: Symbol => JString(x.name)
      case _ => sys.error("not a primitive " + a.asInstanceOf[AnyRef].getClass)
    }
  }
}

case class MappingException(msg: String, cause: Exception) extends Exception(msg, cause) {
  def this(msg: String) = this(msg, null)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy