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

org.squeryl.internals.FieldMetaData.scala Maven / Gradle / Ivy

Go to download

A Scala ORM and DSL for talking with Databases using minimum verbosity and maximum type safety

The newest version!
/*******************************************************************************
 * Copyright 2010 Maxime Lévesque
 * 
 * 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.squeryl.internals

import java.lang.annotation.Annotation
import java.lang.reflect.{Field, Method, Constructor, InvocationTargetException, Type, ParameterizedType}
import java.sql.ResultSet
import scala.annotation.tailrec
import org.squeryl.annotations.{ColumnBase, Column}
import collection.mutable.{HashMap, HashSet}
import org.squeryl.Session
import org.squeryl.dsl.CompositeKey
import org.squeryl.customtypes.CustomType
import org.json4s.scalap.scalasig._
import java.lang.reflect.Member
import org.squeryl.dsl.ast.ConstantTypedExpression
import org.squeryl.customtypes.CustomType

class FieldMetaData(
        val parentMetaData: PosoMetaData[_],
        val nameOfProperty:String,
        val fieldType: Class[_], // if isOption, this fieldType is the type param of Option, i.e. the T in Option[T]
        val wrappedFieldType: Class[_], //in primitive type mode fieldType == wrappedFieldType, in custom type mode wrappedFieldType is the 'real'
        // type, i.e. the (primitive) type that jdbc understands
        val customTypeFactory: Option[AnyRef => Product1[Any] with AnyRef],
        val isOption: Boolean,
        getter: Option[Method],
        setter: Option[Method],
        field:  Option[Field],
        columnAnnotation: Option[Column],
        val isOptimisticCounter: Boolean,
        val sampleValue: Any) {

  def nativeJdbcType =
    this.schema.fieldMapper.nativeJdbcTypeFor(wrappedFieldType)

  /**
   * None if this FieldMetaData is not an enumeration, Some(theParentEnumeration) otherwise
   */
  val enumeration: Option[Enumeration] =
    sampleValue match {
      case Some(e: Enumeration#Value) => Some(Utils.enumerationForValue(e))
      case e: Enumeration#Value => Some(Utils.enumerationForValue(e))
      case _ => None
    }


  def canonicalEnumerationValueFor(id: Int) =
    if(sampleValue == null)
      org.squeryl.internals.Utils.throwError("classes with Enumerations must have a zero param constructor that assigns a sample to the enumeration field")
    else
      enumeration flatMap { e: Enumeration =>
        e.values find { _.id == id }
      } get

  /**
   * This field is mutable only by the Schema trait, and only during the Schema instantiation,
   * so it can safely be considered immutable (read only) by the columnAttributes accessor 
   */
  private val _columnAttributes = new HashSet[ColumnAttribute]


  private [squeryl] def _clearColumnAttributes = {
    _columnAttributes.clear
  }

  private [squeryl] def _addColumnAttribute(ca: ColumnAttribute) =
      _columnAttributes.add(ca)

  /**
   * In some circumstances (like in the test suite) a Schema instance must run on multiple database types,
   * this Map keeps the sequence names 'per schema'
   */
  private val _sequenceNamePerDBAdapter = new HashMap[Class[_],String]

  def sequenceName: String = {

    val ai = _columnAttributes.find(_.isInstanceOf[AutoIncremented]).
      getOrElse(org.squeryl.internals.Utils.throwError(this + " is not declared as autoIncremented, hence it has no sequenceName")).
        asInstanceOf[AutoIncremented]

    if(ai.nameOfSequence != None) {
      return ai.nameOfSequence.get
    }

    synchronized {
      val c = Session.currentSession.databaseAdapter.getClass

      val s = _sequenceNamePerDBAdapter.get(c)

      if(s != None)
        return s.get

      val s0 = Session.currentSession.databaseAdapter.createSequenceName(this)
      
      _sequenceNamePerDBAdapter.put(c, s0)

      return s0
    }
  }

  def isIdFieldOfKeyedEntity =
    parentMetaData.viewOrTable.ked.map(_.idPropertyName == nameOfProperty).getOrElse(false)

  if(isIdFieldOfKeyedEntity && ! classOf[CompositeKey].isAssignableFrom(wrappedFieldType)) {
    schema.defaultColumnAttributesForKeyedEntityId(wrappedFieldType).foreach(ca => {

      if(ca.isInstanceOf[AutoIncremented] && ! (wrappedFieldType.isAssignableFrom(classOf[java.lang.Long]) || wrappedFieldType.isAssignableFrom(classOf[java.lang.Integer])))
        org.squeryl.internals.Utils.throwError("Schema " + schema.getClass.getName + " has method defaultColumnAttributesForKeyedEntityId returning AutoIncremented \nfor " +
          " all KeyedEntity tables, while class " + parentMetaData.clasz.getName +
          "\n has it's id field of type " + fieldType.getName + ", that is neither an Int or a Long, \n the only two types that can " +
          "be auto incremented")

      _addColumnAttribute(ca)
    })
  }

  private [squeryl] var _defaultValue: Option[ConstantTypedExpression[_,_]] = None

  def columnAttributes: Iterable[ColumnAttribute] = _columnAttributes

  def defaultValue: Option[ConstantTypedExpression[_,_]] = _defaultValue

  /**
   * The db column type declaration overriden in the schema, if None, it means that it is the default value for
   * the adapter (see Correspondance of field types to database column types http://squeryl.org/schema-definition.html)  
   */
  def explicitDbTypeDeclaration: Option[String] = {
    val dbt = _columnAttributes.find(_.isInstanceOf[DBType])
    if(dbt == None)
      None
    else
      Some(dbt.get.asInstanceOf[DBType].declaration)
  }
  
  /**
   * If explicit db type case has been requested
   */
  def explicitDbTypeCast: Boolean = _columnAttributes.find(_.isInstanceOf[DBType]).map(a => a.asInstanceOf[DBType].explicit).getOrElse(false)
  
  def isTransient =
    _columnAttributes.exists(_.isInstanceOf[IsTransient])

  def isCustomType = customTypeFactory != None

  /**
   * @return the length defined in org.squeryl.annotations.Column.length
   * if it is defined, or the default length for Java primitive types.
   * The unit of the length is dependent on the type, the convention is
   * that numeric types have a length in byte, boolean is bits
   * date has -1, and for string the lenght is in chars.  
   * double,long -> 8, float,int -> 4, byte -> 1, boolean -> 1
   * java.util.Date -> -1.
   *
   * The use of this field is to help custom schema generators select
   * the most appropriate column type  
   */
  def length: Int =
    if(columnAnnotation == None || columnAnnotation.get.length == -1) {
      FieldMetaData.defaultFieldLength(wrappedFieldType, this)
    }
    else
      columnAnnotation.get.length

  def scale: Int =
    if(columnAnnotation == None || columnAnnotation.get.scale == -1)
      schema.defaultSizeOfBigDecimal._2
    else
      columnAnnotation.get.scale   

  def schema = parentMetaData.schema

  /**
   * The name of the database column
   */
  def columnName =
    if(columnAnnotation == None) {
      val nameDefinedInSchema = _columnAttributes.find(_.isInstanceOf[Named]).map(_.asInstanceOf[Named].name)      
      parentMetaData.schema.columnNameFromPropertyName(nameDefinedInSchema.getOrElse(nameOfProperty))
    }
    else {
      val ca = columnAnnotation.get
      var res = ca.name

      if(res == "")
        res = ca.value

      if(res == "")
        parentMetaData.schema.columnNameFromPropertyName(nameOfProperty)
      else
        res
    }
  
  protected def createResultSetHandler =
    this.schema.fieldMapper.resultSetHandlerFor(wrappedFieldType)

  val resultSetHandler = createResultSetHandler
    
  if(!isCustomType)
    assert(fieldType == wrappedFieldType,
      "expected fieldType == wrappedFieldType in primitive type mode, got "+
      fieldType.getName + " != " + wrappedFieldType.getName)

  override def toString =
    parentMetaData.clasz.getSimpleName + "." + columnName + ":" + displayType

  def isStringType =
    wrappedFieldType.isAssignableFrom(classOf[String])  

  def displayType =
     (if(isOption)
        "Option[" + fieldType.getName + "]"
      else
        fieldType.getName)

  /**
   * When true, will cause Schema generation to declare as PrimaryKey, Note that for
   * KeyedEntity[]s,  declaredAsPrimaryKeyInSchema is always true, and the cannot be made otherwise,
   * the inverse is not true, a field can be declared as primary key in the Shema without it being the
   * id of a KeyedEntity[], ex. :
   *  
   * 
   * on(myTable)(t =>declare(
   *   myField.is(primaryKey)  // myField doesn't need to be a KeyedEntity.id 
   * ))
   * 
* *
   * on(myKeyedEntityTable)(t =>declare(
   *   id.is(autoIncremented)  // omiting primaryKey here has no effect, it is equivalent as id.is(primaryKey,autoIncremented)
   * ))
   * 
*/ def declaredAsPrimaryKeyInSchema = columnAttributes.exists(_.isInstanceOf[PrimaryKey]) def isAutoIncremented = columnAttributes.exists(_.isInstanceOf[AutoIncremented]) /** * Inserts will only set values for a column if isInsertable is true */ def isInsertable = !columnAttributes.exists(_.isInstanceOf[Uninsertable]) /** * Updates will only set values for a column if isUpdatable is true */ def isUpdatable = !columnAttributes.exists(_.isInstanceOf[Unupdatable]) /** * gets the value of the field from the object. * Note that it will unwrap Option[] and return null instead of None, i.e. * if converts None and Some to null and some.get respectively * @param o the object that owns the field */ def get(o:AnyRef): AnyRef = try { val res = if(getter != None) _getFromGetter(o) else _getFromField(o) if(isOption) { if(res == None) null else res.asInstanceOf[Option[_]].get.asInstanceOf[AnyRef] } else res } catch { case e: IllegalArgumentException => org.squeryl.internals.Utils.throwError(wrappedFieldType.getName + " used on " + o.getClass.getName) } def getNativeJdbcValue(o:AnyRef): AnyRef = { val r = get(o) schema.fieldMapper.nativeJdbcValueFor(wrappedFieldType, r) } def setFromResultSet(target: AnyRef, rs: ResultSet, index: Int) = { val v = resultSetHandler(rs, index) set(target, v) } /** * Sets the value 'v' to the object, the value will be converted to Some or None * if the field is an Option[], (if isOption). */ def set(target: AnyRef, v: AnyRef): Unit = { try { val v0: AnyRef = if(v == null) null else if(enumeration != None) canonicalEnumerationValueFor(v.asInstanceOf[java.lang.Integer].intValue) else if(customTypeFactory == None) v else { val f = customTypeFactory.get if(v.isInstanceOf[CustomType[_]]) { val r = v.asInstanceOf[CustomType[_]]._1 f(if(r == null) null else r.asInstanceOf[AnyRef]) } else { f(v) } } val actualValue = if(!isOption) v0 else if(v0 == null) None else Some(v0) if(setter != None) _setWithSetter(target, actualValue) else _setWithField(target, actualValue) } catch { case e: Exception => { val typeOfV = if(v == null) "null" else v.getClass.getCanonicalName org.squeryl.internals.Utils.throwError( this + " was invoked with value '" + v + "' of type " + typeOfV + " on object of type " + target.getClass.getName + " \n" + e) } } } private def _getFromGetter(o:AnyRef) = getter.get.invoke(o) private def _setWithSetter(target: AnyRef, v: AnyRef) = setter.get.invoke(target, v) private def _getFromField(o:AnyRef) = field.get.get(o) private def _setWithField(target: AnyRef, v: AnyRef) = field.get.set(target, v) } trait FieldMetaDataFactory { def hideFromYieldInspection(o: AnyRef, f: Field): Boolean = false def build(parentMetaData: PosoMetaData[_], name: String, property: (Option[Field], Option[Method], Option[Method], Set[Annotation]), sampleInstance4OptionTypeDeduction: AnyRef, isOptimisticCounter: Boolean): FieldMetaData def createPosoFactory(posoMetaData: PosoMetaData[_]): ()=>AnyRef } object FieldMetaData { private val _EMPTY_ARRAY = new Array[Object](0) var factory = new FieldMetaDataFactory { def createPosoFactory(posoMetaData: PosoMetaData[_]): ()=>AnyRef = () => { val c = posoMetaData.constructor c._1.newInstance(c._2 :_*).asInstanceOf[AnyRef]; } def build(parentMetaData: PosoMetaData[_], name: String, property: (Option[Field], Option[Method], Option[Method], Set[Annotation]), sampleInstance4OptionTypeDeduction: AnyRef, isOptimisticCounter: Boolean) = { val fieldMapper = parentMetaData.schema.fieldMapper val field = property._1 val getter = property._2 val setter = property._3 val annotations = property._4 val colAnnotation = annotations.find(a => a.isInstanceOf[ColumnBase]).map(a => a.asInstanceOf[ColumnBase]) /* * Retrieve the member in use, its class and its generic type */ val (member, clsOfField, typeOfField) = (setter.map(s => (s: Member, s.getParameterTypes.head, s.getGenericParameterTypes.head)) .orElse(getter.map(g => (g: Member, g.getReturnType, g.getGenericReturnType))) .orElse(field.map(f => (f: Member, f.getType, f.getType))) .getOrElse(org.squeryl.internals.Utils.throwError("invalid field group"))) /* * Look for a value in the sample type. If one exists and * it is not None, we can use it to deduce the Option type. */ var v: AnyRef = if(sampleInstance4OptionTypeDeduction != null) { field flatMap { f => f.get(sampleInstance4OptionTypeDeduction) match { case a: AnyRef => Some(a) case _ => None } } orElse { getter flatMap { _.invoke(sampleInstance4OptionTypeDeduction, _EMPTY_ARRAY : _*) match { case a: AnyRef => Some(a) case _ => None } } } getOrElse createDefaultValue(fieldMapper, member, clsOfField, Some(typeOfField), colAnnotation) } else null if(v != null && v == None) // can't deduce the type from None keep trying v = null val constructorSuppliedDefaultValue = v var customTypeFactory: Option[AnyRef=>Product1[Any] with AnyRef] = None if(classOf[Product1[Any]].isAssignableFrom(clsOfField)) customTypeFactory = _createCustomTypeFactory(fieldMapper, parentMetaData.clasz, clsOfField) if(customTypeFactory != None) { val f = customTypeFactory.get v = f(null) // this creates a dummy (sample) field } if(v == null) /* * If we have not yet been able to deduce the value of the field, delegate to createDefaultValue * in order to do so. */ v = createDefaultValue(fieldMapper, member, clsOfField, Some(typeOfField), colAnnotation) val deductionFailed = v match { case Some(None) => true case null => true case a:Any => false } if(deductionFailed) { val errorMessage = "Could not deduce Option[] type of field '" + name + "' of class " + parentMetaData.clasz.getName org.squeryl.internals.Utils.throwError(errorMessage) } val isOption = v.isInstanceOf[Some[_]] val typeOfFieldOrTypeOfOption = if(!isOption) v.getClass else v.asInstanceOf[Option[AnyRef]].get.getClass val primitiveFieldType = if(v.isInstanceOf[Product1[_]]) v.asInstanceOf[Product1[Any]]._1.asInstanceOf[AnyRef].getClass else if(isOption && v.asInstanceOf[Option[AnyRef]].get.isInstanceOf[Product1[_]]) { //if we get here, customTypeFactory has not had a chance to get created customTypeFactory = _createCustomTypeFactory(fieldMapper, parentMetaData.clasz, typeOfFieldOrTypeOfOption) v.asInstanceOf[Option[AnyRef]].get.asInstanceOf[Product1[Any]]._1.asInstanceOf[AnyRef].getClass } else typeOfFieldOrTypeOfOption if(typeOfFieldOrTypeOfOption == None.getClass) { Utils.throwError( "class " + parentMetaData.clasz.getCanonicalName +" used in table " + parentMetaData.viewOrTable.name + ", needs a zero arg constructor with sample values for Option[] field " + name ) } new FieldMetaData( parentMetaData, name, typeOfFieldOrTypeOfOption, primitiveFieldType, customTypeFactory, isOption, getter, setter, field, colAnnotation, isOptimisticCounter, constructorSuppliedDefaultValue) } } /** * creates a closure that takes a java.lang. primitive wrapper (ex.: java.lang.Integer) and * that creates an instance of a custom type with it, the factory accepts null to create * default values for non nullable primitive types (int, long, etc...) */ private def _createCustomTypeFactory(fieldMapper: FieldMapper, ownerClass: Class[_], typeOfField: Class[_]): Option[AnyRef=>Product1[Any] with AnyRef] = { // run through the given class hierarchy and return the first method // which is called "value" and doesn't return java.lang.Object @tailrec def find(c: Class[_]): Option[Method] = if(c != null) c.getMethods.find(m => m.getName == "value" && m.getReturnType != classOf[java.lang.Object]) match { case Some(m) => Some(m) case None => find(c.getSuperclass) } else None // invoke the given constructor and expose possible exceptions to the caller. def invoke(c: Constructor[_], value: AnyRef) = try { c.newInstance(value).asInstanceOf[Product1[Any] with AnyRef] } catch { case ex: InvocationTargetException => throw ex.getTargetException } find(typeOfField) flatMap(m => { val pType = m.getReturnType assert(fieldMapper.isSupported(pType), "enclosed type %s of CustomType %s is not a supported field type!" .format(pType.getName, typeOfField.getName)) val c = typeOfField.getConstructor(pType) val defaultValue = createDefaultValue(fieldMapper, c, pType, None, None) if(defaultValue == null) None else Some((i: AnyRef) => if(i == null) invoke(c, defaultValue) else invoke(c, i) ) }) } def defaultFieldLength(fieldType: Class[_], fmd: FieldMetaData) = { if(classOf[String].isAssignableFrom(fieldType)) fmd.schema.defaultLengthOfString else if(classOf[java.math.BigDecimal].isAssignableFrom(fieldType) || classOf[scala.math.BigDecimal].isAssignableFrom(fieldType)) { fmd.schema.defaultSizeOfBigDecimal._1 } else { fmd.schema.fieldMapper.defaultColumnLength(fieldType) } } def optionTypeFromScalaSig(member: Member): Option[Class[_]] = { val scalaSigOption = ScalaSigParser.parse(member.getDeclaringClass()) scalaSigOption flatMap { scalaSig => val result = scalaSig.symbols.filter { sym => member.getName == sym.name }.collect { case sym: MethodSymbol => sym.infoType }.collect { case tpe: NullaryMethodType => tpe.resultType }.collect { case TypeRefType(_, _, Seq(TypeRefType(_, tpe, _))) => PartialFunction.condOpt(tpe.name){ case "Int" => classOf[scala.Int] case "Short" => classOf[scala.Short] case "Long" => classOf[scala.Long] case "Double" => classOf[scala.Double] case "Float" => classOf[scala.Float] case "Boolean" => classOf[scala.Boolean] case "Byte" => classOf[scala.Byte] case "Char" => classOf[scala.Char] } } assert(result.size <= 1) result.headOption.flatten } } def createDefaultValue(fieldMapper: FieldMapper, member: Member, p: Class[_], t: Option[Type], optionFieldsInfo: Option[Column]): Object = { if (p.isAssignableFrom(classOf[Option[Any]])) { /* * First we'll look at the annotation if it exists as it's the lowest cost. */ optionFieldsInfo.flatMap(ann => if(ann.optionType != classOf[Object]) Some(createDefaultValue(fieldMapper, member, ann.optionType, None, None)) else None).orElse{ /* * Next we'll try the Java generic type. This will fail if the generic parameter is a primitive as * we'll see Object instead of scala.X */ t match { case Some(pt: ParameterizedType) => { pt.getActualTypeArguments.toList match { case oType :: Nil => { if(classOf[Class[_]].isInstance(oType)) { /* * Primitive types are seen by Java reflection as classOf[Object], * if that's what we find then we need to get the real value from @ScalaSignature */ val trueTypeOption = if (classOf[Object] == oType) optionTypeFromScalaSig(member) else Some(oType.asInstanceOf[Class[_]]) trueTypeOption flatMap { trueType => val deduced = createDefaultValue(fieldMapper, member, trueType, None, optionFieldsInfo) if (deduced != null) Some(deduced) else None //Couldn't create default for type param } } else{ None //Type parameter is not a Class } } case _ => None //Not a single type parameter } } case _ => None //Not a parameterized type } } } else { fieldMapper.trySampleValueFor(p) } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy