Skip to content
This repository
Browse code

Fix #1018

  • Loading branch information...
commit 2816e53c3158dddbd0f8ef93f6c825a147f78a25 1 parent 411aedb
nafg authored May 19, 2011
108  persistence/mapper/src/main/scala/net/liftweb/mapper/FieldFinder.scala
... ...
@@ -0,0 +1,108 @@
  1
+/*
  2
+ * Copyright 2006-2011 WorldWide Conferencing, LLC
  3
+ *
  4
+ * Licensed under the Apache License, Version 2.0 (the "License");
  5
+ * you may not use this file except in compliance with the License.
  6
+ * You may obtain a copy of the License at
  7
+ *
  8
+ *     http://www.apache.org/licenses/LICENSE-2.0
  9
+ *
  10
+ * Unless required by applicable law or agreed to in writing, software
  11
+ * distributed under the License is distributed on an "AS IS" BASIS,
  12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+ * See the License for the specific language governing permissions and
  14
+ * limitations under the License.
  15
+ */
  16
+
  17
+package net.liftweb
  18
+package mapper
  19
+
  20
+
  21
+class FieldFinder[A <: Mapper[A], T: ClassManifest](metaMapper: Mapper[A], logger: net.liftweb.common.Logger) {
  22
+  import java.lang.reflect._
  23
+
  24
+  logger.debug("Created FieldFinder for " + classManifest[T].erasure)
  25
+
  26
+  def isMagicObject(m: Method) = m.getReturnType.getName.endsWith("$"+m.getName+"$") && m.getParameterTypes.length == 0
  27
+
  28
+  def typeFilter: Class[_]=>Boolean = classManifest[T].erasure.isAssignableFrom
  29
+
  30
+  /**
  31
+    * Find the magic mapper fields on the superclass
  32
+    */
  33
+  def findMagicFields(onMagic: Mapper[A], staringClass: Class[_]): List[Method] = {
  34
+    // If a class name ends in $module, it's a subclass created for scala object instances
  35
+    def deMod(in: String): String =
  36
+      if (in.endsWith("$module")) in.substring(0, in.length - 7)
  37
+      else in
  38
+
  39
+    // find the magic fields for the given superclass
  40
+    def findForClass(clz: Class[_]): List[Method] = clz match {
  41
+      case null => Nil
  42
+      case c =>
  43
+        // get the names of fields that represent the type we want
  44
+
  45
+        val fields = Map(c.getDeclaredFields.
  46
+                          filter{f =>
  47
+                            val ret = typeFilter(f.getType)
  48
+                            logger.debug("typeFilter(" + f.getType + "); T=" + classManifest[T].erasure)
  49
+                            ret
  50
+                          }.
  51
+                          map(f => (deMod(f.getName), f)) :_*)
  52
+
  53
+        logger.debug("fields: " + fields)
  54
+
  55
+        // this method will find all the super classes and super-interfaces
  56
+        def getAllSupers(clz: Class[_]): List[Class[_]] = clz match {
  57
+          case null => Nil
  58
+          case c =>
  59
+            c :: c.getInterfaces.toList.flatMap(getAllSupers) :::
  60
+            getAllSupers(c.getSuperclass)
  61
+        }
  62
+
  63
+        // does the method return an actual instance of an actual class that's
  64
+        // associated with this Mapper class
  65
+        def validActualType(meth: Method): Boolean = {
  66
+          try {
  67
+            // invoke the method
  68
+            meth.invoke(onMagic) match {
  69
+              case null =>
  70
+                logger.debug("Not a valid mapped field: %s".format(meth.getName))
  71
+                false
  72
+              case inst =>
  73
+                // do we get a T of some sort back?
  74
+                if (!typeFilter(inst.getClass)) false
  75
+                else {
  76
+                  // find out if the class name of the actual thing starts
  77
+                  // with the name of this class or some superclass...
  78
+                  // basically, is an inner class of this class
  79
+                  getAllSupers(clz).find{
  80
+                    c =>
  81
+                    inst.getClass.getName.startsWith(c.getName)}.isDefined
  82
+                }
  83
+            }
  84
+
  85
+          } catch {
  86
+            case e =>
  87
+              logger.debug("Not a valid mapped field: %s, got exception: %s".format(meth.getName, e))
  88
+              false
  89
+          }
  90
+        }
  91
+
  92
+        // find all the declared methods
  93
+        val meths = c.getDeclaredMethods.toList.
  94
+        filter(_.getParameterTypes.length == 0). // that take no parameters
  95
+        filter(m => Modifier.isPublic(m.getModifiers)). // that are public
  96
+        filter(m => fields.contains(m.getName) && // that are associated with private fields
  97
+                fields(m.getName).getType == m.getReturnType).
  98
+        filter(validActualType) // and have a validated type
  99
+
  100
+        meths ::: findForClass(clz.getSuperclass)
  101
+    }
  102
+
  103
+    findForClass(staringClass).distinct
  104
+  }
  105
+
  106
+  lazy val accessorMethods = findMagicFields(metaMapper, metaMapper.getClass.getSuperclass)
  107
+}
  108
+
10  persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala
@@ -1090,10 +1090,11 @@ trait MetaMapper[A<:Mapper[A]] extends BaseMetaMapper with Mapper[A] {
1090 1090
   this.runSafe {
1091 1091
     logger.debug("Initializing MetaMapper for %s".format(internalTableName_$_$))
1092 1092
     val tArray = new ListBuffer[FieldHolder]
1093  
-
1094  
-    def isMagicObject(m: Method) = m.getReturnType.getName.endsWith("$"+m.getName+"$") && m.getParameterTypes.length == 0
1095  
-    def isMappedField(m: Method) = classOf[MappedField[Nothing, A]].isAssignableFrom(m.getReturnType)
1096 1093
     def isLifecycle(m: Method) = classOf[LifecycleCallbacks].isAssignableFrom(m.getReturnType)
  1094
+    /*
  1095
+    def isMagicObject(m: Method) = m.getReturnType.getName.endsWith("$"+m.getName+"$") && m.getParameterTypes.length == 0
  1096
+    //this is not currently used
  1097
+    //def isMappedField(m: Method) = classOf[MappedField[Nothing, A]].isAssignableFrom(m.getReturnType)
1097 1098
 
1098 1099
     import java.lang.reflect.Modifier
1099 1100
 
@@ -1171,6 +1172,9 @@ trait MetaMapper[A<:Mapper[A]] extends BaseMetaMapper with Mapper[A] {
1171 1172
     }
1172 1173
 
1173 1174
     val mapperAccessMethods = findMagicFields(this, this.getClass.getSuperclass)
  1175
+    */
  1176
+    val finder = new FieldFinder[A, MappedField[_,_]](this, logger)
  1177
+    val mapperAccessMethods = finder.accessorMethods
1174 1178
 
1175 1179
     mappedCallbacks = mapperAccessMethods.filter(isLifecycle).map(v => (v.getName, v))
1176 1180
 
16  persistence/mapper/src/main/scala/net/liftweb/mapper/OneToMany.scala
@@ -20,10 +20,20 @@ package mapper
20 20
 
21 21
 /**
22 22
  * Add this trait to a Mapper for managed one-to-many support
  23
+ * For example: class Contact extends LongKeyedMapper[Contact] with OneToMany[Long, Contact] { ... }
  24
+ * @tparam K the type of the primary key
  25
+ * @tparam T the mapper type
23 26
  * @author nafg
24 27
  */
25 28
 trait OneToMany[K,T<:KeyedMapper[K, T]] extends KeyedMapper[K,T] { this: T =>
26  
-  private var oneToManyFields: List[MappedOneToManyBase[_]] = Nil
  29
+  //private var oneToManyFields: List[MappedOneToManyBase[_]] = Nil
  30
+  private[mapper] lazy val oneToManyFields: List[MappedOneToManyBase[_]] = {
  31
+    new FieldFinder[T, MappedOneToManyBase[_]](
  32
+      getSingleton,
  33
+      net.liftweb.common.Logger(classOf[OneToMany[K,T]])
  34
+    ).accessorMethods map (_.invoke(this).asInstanceOf[MappedOneToManyBase[_]])
  35
+  }
  36
+  
27 37
   /**
28 38
    * An override for save to propagate the save to all children
29 39
    * of this parent.
@@ -100,7 +110,7 @@ trait OneToMany[K,T<:KeyedMapper[K, T]] extends KeyedMapper[K,T] { this: T =>
100 110
     }
101 111
     protected def delegate_=(d: List[O]) = _delegate = d
102 112
 
103  
-    oneToManyFields = this :: oneToManyFields
  113
+    //oneToManyFields = this :: oneToManyFields
104 114
 
105 115
     /**
106 116
      * Takes ownership of e. Sets e's foreign key to our primary key
@@ -265,5 +275,3 @@ trait OneToMany[K,T<:KeyedMapper[K, T]] extends KeyedMapper[K,T] { this: T =>
265 275
   }
266 276
 }
267 277
 
268  
-
269  
-
67  persistence/mapper/src/test/scala/net/liftweb/mapper/OneToManySpecs.scala
... ...
@@ -0,0 +1,67 @@
  1
+/*
  2
+ * Copyright 2009-2010 WorldWide Conferencing, LLC
  3
+ *
  4
+ * Licensed under the Apache License, Version 2.0 (the "License");
  5
+ * you may not use this file except in compliance with the License.
  6
+ * You may obtain a copy of the License at
  7
+ *
  8
+ *     http://www.apache.org/licenses/LICENSE-2.0
  9
+ *
  10
+ * Unless required by applicable law or agreed to in writing, software
  11
+ * distributed under the License is distributed on an "AS IS" BASIS,
  12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+ * See the License for the specific language governing permissions and
  14
+ * limitations under the License.
  15
+ */
  16
+
  17
+package net.liftweb {
  18
+package mapper {
  19
+
  20
+import org.specs._
  21
+import _root_.org.specs.runner.{JUnit3, ConsoleRunner}
  22
+
  23
+class OneToManySpecsAsTest extends JUnit3(OneToManySpecs)
  24
+object OneToManySpecsRunner extends ConsoleRunner(OneToManySpecs)
  25
+
  26
+object OneToManySpecs extends Specification {
  27
+
  28
+  val provider = DbProviders.H2MemoryProvider
  29
+
  30
+  private def ignoreLogger(f: => AnyRef): Unit = ()
  31
+  def setupDB {
  32
+    MapperRules.createForeignKeys_? = c => false
  33
+    provider.setupDB
  34
+    Schemifier.destroyTables_!!(ignoreLogger _,  Contact, Phone)
  35
+    Schemifier.schemify(true, ignoreLogger _, Contact, Phone)
  36
+  }
  37
+
  38
+  "OneToMany" should {
  39
+    "detect all MappedOneToMany fields" in {
  40
+      setupDB
  41
+      val contact = Contact.create
  42
+      val fields = contact.oneToManyFields
  43
+      fields.length must_== 1
  44
+      fields(0).asInstanceOf[Any] must_== contact.phones
  45
+    }
  46
+  }
  47
+
  48
+}
  49
+
  50
+
  51
+
  52
+class Contact extends LongKeyedMapper[Contact] with IdPK with OneToMany[Long, Contact] {
  53
+  def getSingleton = Contact
  54
+  object phones extends MappedOneToMany(Phone, Phone.contact)
  55
+}
  56
+object Contact extends Contact with LongKeyedMetaMapper[Contact]
  57
+
  58
+class Phone extends LongKeyedMapper[Phone] with IdPK {
  59
+  def getSingleton = Phone
  60
+  object contact extends LongMappedMapper(this, Contact)
  61
+  object number extends MappedString(this, 10)
  62
+}
  63
+object Phone extends Phone with LongKeyedMetaMapper[Phone]
  64
+
  65
+
  66
+}
  67
+}

0 notes on commit 2816e53

Please sign in to comment.
Something went wrong with that request. Please try again.