Skip to content

Commit

Permalink
Fix #1018
Browse files Browse the repository at this point in the history
  • Loading branch information
Naftoli Gugenheim committed May 20, 2011
1 parent 299f2e1 commit f51d884
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 6 deletions.
Expand Up @@ -1090,10 +1090,11 @@ trait MetaMapper[A<:Mapper[A]] extends BaseMetaMapper with Mapper[A] {
this.runSafe {
logger.debug("Initializing MetaMapper for %s".format(internalTableName_$_$))
val tArray = new ListBuffer[FieldHolder]

def isMagicObject(m: Method) = m.getReturnType.getName.endsWith("$"+m.getName+"$") && m.getParameterTypes.length == 0
def isMappedField(m: Method) = classOf[MappedField[Nothing, A]].isAssignableFrom(m.getReturnType)
def isLifecycle(m: Method) = classOf[LifecycleCallbacks].isAssignableFrom(m.getReturnType)
/*
def isMagicObject(m: Method) = m.getReturnType.getName.endsWith("$"+m.getName+"$") && m.getParameterTypes.length == 0
//this is not currently used
//def isMappedField(m: Method) = classOf[MappedField[Nothing, A]].isAssignableFrom(m.getReturnType)
import java.lang.reflect.Modifier
Expand Down Expand Up @@ -1171,6 +1172,9 @@ trait MetaMapper[A<:Mapper[A]] extends BaseMetaMapper with Mapper[A] {
}
val mapperAccessMethods = findMagicFields(this, this.getClass.getSuperclass)
*/
val finder = new FieldFinder[A, MappedField[_,_]](this, logger)
val mapperAccessMethods = finder.accessorMethods

mappedCallbacks = mapperAccessMethods.filter(isLifecycle).map(v => (v.getName, v))

Expand Down
107 changes: 104 additions & 3 deletions persistence/mapper/src/main/scala/net/liftweb/mapper/OneToMany.scala
Expand Up @@ -20,10 +20,20 @@ package mapper

/**
* Add this trait to a Mapper for managed one-to-many support
* For example: class Contact extends LongKeyedMapper[Contact] with OneToMany[Long, Contact] { ... }
* @tparam K the type of the primary key
* @tparam T the mapper type
* @author nafg
*/
trait OneToMany[K,T<:KeyedMapper[K, T]] extends KeyedMapper[K,T] { this: T =>
private var oneToManyFields: List[MappedOneToManyBase[_]] = Nil
//private var oneToManyFields: List[MappedOneToManyBase[_]] = Nil
private[mapper] lazy val oneToManyFields: List[MappedOneToManyBase[_]] = {
new FieldFinder[T, MappedOneToManyBase[_]](
getSingleton,
net.liftweb.common.Logger(classOf[OneToMany[K,T]])
).accessorMethods map (_.invoke(this).asInstanceOf[MappedOneToManyBase[_]])
}

/**
* An override for save to propagate the save to all children
* of this parent.
Expand Down Expand Up @@ -100,7 +110,7 @@ trait OneToMany[K,T<:KeyedMapper[K, T]] extends KeyedMapper[K,T] { this: T =>
}
protected def delegate_=(d: List[O]) = _delegate = d

oneToManyFields = this :: oneToManyFields
//oneToManyFields = this :: oneToManyFields

/**
* Takes ownership of e. Sets e's foreign key to our primary key
Expand Down Expand Up @@ -282,7 +292,8 @@ class LongMappedMapper[T<:Mapper[T], O<:KeyedMapper[Long,O]](theOwner: T, foreig
* @deprecated Use LongMappedMapper instead, to avoid mixing in MappedLongForeignKey manually. May be folded into it in the future.
* @author nafg
*/
@deprecated
//TODO fold into LongMappedMapper
@deprecated("Use LongMappedMapper or LongMappedForeignLey instead")
trait LongMappedForeignMapper[T<:Mapper[T],O<:KeyedMapper[Long,O]]
extends MappedLongForeignKey[T,O]
with LifecycleCallbacks {
Expand Down Expand Up @@ -316,3 +327,93 @@ trait LongMappedForeignMapper[T<:Mapper[T],O<:KeyedMapper[Long,O]]
else Nil
}

class FieldFinder[A <: Mapper[A], T: ClassManifest](metaMapper: Mapper[A], logger: net.liftweb.common.Logger) {
import java.lang.reflect._

logger.debug("Created FieldFinder for " + classManifest[T].erasure)

def isMagicObject(m: Method) = m.getReturnType.getName.endsWith("$"+m.getName+"$") && m.getParameterTypes.length == 0

def typeFilter: Class[_]=>Boolean = classManifest[T].erasure.isAssignableFrom

/**
* Find the magic mapper fields on the superclass
*/
def findMagicFields(onMagic: Mapper[A], staringClass: Class[_]): List[Method] = {
// If a class name ends in $module, it's a subclass created for scala object instances
def deMod(in: String): String =
if (in.endsWith("$module")) in.substring(0, in.length - 7)
else in

// find the magic fields for the given superclass
def findForClass(clz: Class[_]): List[Method] = clz match {
case null => Nil
case c =>
// get the names of fields that represent the type we want

val fields = Map(c.getDeclaredFields.
filter{f =>
val ret = typeFilter(f.getType)
logger.debug("typeFilter(" + f.getType + "); T=" + classManifest[T].erasure)
ret
}.
map(f => (deMod(f.getName), f)) :_*)

logger.debug("fields: " + fields)

// this method will find all the super classes and super-interfaces
def getAllSupers(clz: Class[_]): List[Class[_]] = clz match {
case null => Nil
case c =>
c :: c.getInterfaces.toList.flatMap(getAllSupers) :::
getAllSupers(c.getSuperclass)
}

// does the method return an actual instance of an actual class that's
// associated with this Mapper class
def validActualType(meth: Method): Boolean = {
try {
// invoke the method
meth.invoke(onMagic) match {
case null =>
logger.debug("Not a valid mapped field: %s".format(meth.getName))
false
case inst =>
// do we get a T of some sort back?
if (!typeFilter(inst.getClass)) false
else {
// find out if the class name of the actual thing starts
// with the name of this class or some superclass...
// basically, is an inner class of this class
getAllSupers(clz).find{
c =>
inst.getClass.getName.startsWith(c.getName)}.isDefined
}
}

} catch {
case e =>
logger.debug("Not a valid mapped field: %s, got exception: %s".format(meth.getName, e))
false
}
}

// find all the declared methods
val meths = c.getDeclaredMethods.toList.
filter(_.getParameterTypes.length == 0). // that take no parameters
filter(m => Modifier.isPublic(m.getModifiers)). // that are public
filter(m => fields.contains(m.getName) && // that are associated with private fields
fields(m.getName).getType == m.getReturnType).
filter(validActualType) // and have a validated type

meths ::: findForClass(clz.getSuperclass)
}

findForClass(staringClass).distinct
}

lazy val accessorMethods = findMagicFields(metaMapper, metaMapper.getClass.getSuperclass)
}



@@ -0,0 +1,67 @@
/*
* 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 net.liftweb {
package mapper {

import org.specs._
import _root_.org.specs.runner.{JUnit3, ConsoleRunner}

class OneToManySpecsAsTest extends JUnit3(OneToManySpecs)
object OneToManySpecsRunner extends ConsoleRunner(OneToManySpecs)

object OneToManySpecs extends Specification {

val provider = DbProviders.H2MemoryProvider

private def ignoreLogger(f: => AnyRef): Unit = ()
def setupDB {
MapperRules.createForeignKeys_? = c => false
provider.setupDB
Schemifier.destroyTables_!!(ignoreLogger _, Contact, Phone)
Schemifier.schemify(true, ignoreLogger _, Contact, Phone)
}

"OneToMany" should {
"detect all MappedOneToMany fields" in {
setupDB
val contact = Contact.create
val fields = contact.oneToManyFields
fields.length must_== 1
fields(0).asInstanceOf[Any] must_== contact.phones
}
}

}



class Contact extends LongKeyedMapper[Contact] with IdPK with OneToMany[Long, Contact] {
def getSingleton = Contact
object phones extends MappedOneToMany(Phone, Phone.contact)
}
object Contact extends Contact with LongKeyedMetaMapper[Contact]

class Phone extends LongKeyedMapper[Phone] with IdPK {
def getSingleton = Phone
object contact extends LongMappedMapper(this, Contact)
object number extends MappedString(this, 10)
}
object Phone extends Phone with LongKeyedMetaMapper[Phone]


}
}

0 comments on commit f51d884

Please sign in to comment.