Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fix #1018

  • Loading branch information...
commit f51d88425dba92c37b1b6463b408bf633fb5843c 1 parent 299f2e1
@nafg nafg authored
View
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] {
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
@@ -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))
View
107 persistence/mapper/src/main/scala/net/liftweb/mapper/OneToMany.scala
@@ -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.
@@ -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
@@ -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 {
@@ -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)
+ }
+
+
+
View
67 persistence/mapper/src/test/scala/net/liftweb/mapper/OneToManySpecs.scala
@@ -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]
+
+
+}
+}
Please sign in to comment.
Something went wrong with that request. Please try again.