summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntonio Cunei <antonio.cunei@epfl.ch>2010-09-07 20:03:52 +0000
committerAntonio Cunei <antonio.cunei@epfl.ch>2010-09-07 20:03:52 +0000
commit60ff8410bfbb46249265c61b453d135a4fdb3815 (patch)
tree5642487776f6e58c63dab288edd4bf6573578d1a
parent42949207a82909496879759aecc59eba4bb8ff40 (diff)
downloadscala-60ff8410bfbb46249265c61b453d135a4fdb3815.tar.gz
scala-60ff8410bfbb46249265c61b453d135a4fdb3815.tar.bz2
scala-60ff8410bfbb46249265c61b453d135a4fdb3815.zip
Merged 22949,22463,22446, addresses some of Har...
Merged 22949,22463,22446, addresses some of Harrah's suggestions concerning 2.8.1. Includes: auto-embedding of REPL, and fix for Enumeration class loader leak. Merged revisions 22446,22463,22949 via svnmerge from https://lampsvn.epfl.ch/svn-repos/scala/scala/trunk ........ r22446 | extempore | 2010-07-01 00:57:41 +0200 (Thu, 01 Jul 2010) | 9 lines Enumeration fixes. There was no way to do reflection-based naming correctly simply by inspecting method signatures (because a Value from a different Enumeration stored in a val looks identical to one from this Enumeration) so I have Value store the outer Enum for comparison purposes. This won't make anything new uncollectable because they already have an $outer pointing there. This also simplified the reflection logic: it's an eq test rather than a series of heuristics. Closes #3616, #3615. Review by phaller. ........ r22463 | extempore | 2010-07-01 23:31:21 +0200 (Thu, 01 Jul 2010) | 7 lines Took a cue from mharrah that we don't need to build global static data to keep track of something when we know where it's kept. Altered the Enumeration deserialization scheme to use reflection, preserving the singleton property by delivering the MODULE$ singleton. This solves the GC issue and lets us drop synchronization to boot. Also added some graceful failure for malformed Enumerations. All tests look good but a second opinion is in order: closes #2214, review by phaller. ........ r22949 | extempore | 2010-09-07 19:48:09 +0200 (Tue, 07 Sep 2010) | 3 lines Some modifications to repl classloading to make it usable in managed classloader environments. Contributed by mark harrah. Review by rytz. ........
-rw-r--r--src/compiler/scala/tools/nsc/Interpreter.scala15
-rw-r--r--src/compiler/scala/tools/nsc/InterpreterLoop.scala3
-rw-r--r--src/compiler/scala/tools/nsc/settings/MutableSettings.scala28
-rw-r--r--src/library/scala/Enumeration.scala153
-rw-r--r--test/files/run/bug3616.check1
-rw-r--r--test/files/run/bug3616.scala12
6 files changed, 109 insertions, 103 deletions
diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala
index 5d11973a74..f456039fa6 100644
--- a/src/compiler/scala/tools/nsc/Interpreter.scala
+++ b/src/compiler/scala/tools/nsc/Interpreter.scala
@@ -229,7 +229,9 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
private def methodByName(c: Class[_], name: String): reflect.Method =
c.getMethod(name, classOf[Object])
- protected def parentClassLoader: ClassLoader = this.getClass.getClassLoader()
+ protected def parentClassLoader: ClassLoader =
+ settings.explicitParentLoader.getOrElse( this.getClass.getClassLoader() )
+
def getInterpreterClassLoader() = classLoader
// Set the current Java "context" class loader to this interpreter's class loader
@@ -1266,15 +1268,16 @@ object Interpreter {
}
}
}
- def breakIf(assertion: => Boolean, args: DebugParam[_]*): Unit =
- if (assertion) break(args.toList)
+ // provide the enclosing type T
+ // in order to set up the interpreter's classpath and parent class loader properly
+ def breakIf[T: Manifest](assertion: => Boolean, args: DebugParam[_]*): Unit =
+ if (assertion) break[T](args.toList)
// start a repl, binding supplied args
- def break(args: List[DebugParam[_]]): Unit = {
+ def break[T: Manifest](args: List[DebugParam[_]]): Unit = {
val intLoop = new InterpreterLoop
intLoop.settings = new Settings(Console.println)
- // XXX come back to the dot handling
- intLoop.settings.classpath.value = "."
+ intLoop.settings.embeddedDefaults[T]
intLoop.createInterpreter
intLoop.in = InteractiveReader.createDefault(intLoop.interpreter)
diff --git a/src/compiler/scala/tools/nsc/InterpreterLoop.scala b/src/compiler/scala/tools/nsc/InterpreterLoop.scala
index f68ce4a5b4..bdcd7b9f58 100644
--- a/src/compiler/scala/tools/nsc/InterpreterLoop.scala
+++ b/src/compiler/scala/tools/nsc/InterpreterLoop.scala
@@ -117,7 +117,8 @@ class InterpreterLoop(in0: Option[BufferedReader], protected val out: PrintWrite
settings.classpath append addedClasspath
interpreter = new Interpreter(settings, out) {
- override protected def parentClassLoader = classOf[InterpreterLoop].getClassLoader
+ override protected def parentClassLoader =
+ settings.explicitParentLoader.getOrElse( classOf[InterpreterLoop].getClassLoader )
}
interpreter.setContextClassLoader()
// interpreter.quietBind("settings", "scala.tools.nsc.InterpreterSettings", interpreter.isettings)
diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
index 1e0e00e01d..c15a4279fb 100644
--- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
@@ -11,6 +11,7 @@ package settings
import io.{AbstractFile, VirtualDirectory}
import scala.tools.util.StringOps
import scala.collection.mutable.ListBuffer
+import scala.io.Source
/** A mutable Settings object.
*/
@@ -160,6 +161,33 @@ class MutableSettings(val errorFn: String => Unit) extends AbsSettings with Scal
doArgs(args)
}
+ /** Initializes these settings for embedded use by type `T`.
+ * The class loader defining `T` should provide resources `app.class.path`
+ * and `boot.class.path`. These resources should contain the application
+ * and boot classpaths in the same form as would be passed on the command line.*/
+ def embeddedDefaults[T: Manifest]: Unit =
+ embeddedDefaults(implicitly[Manifest[T]].erasure.getClassLoader)
+
+ /** Initializes these settings for embedded use by a class from the given class loader.
+ * The class loader for `T` should provide resources `app.class.path`
+ * and `boot.class.path`. These resources should contain the application
+ * and boot classpaths in the same form as would be passed on the command line.*/
+ def embeddedDefaults(loader: ClassLoader) {
+ explicitParentLoader = Option(loader) // for the Interpreter parentClassLoader
+ getClasspath("app", loader) foreach { classpath.value = _ }
+ getClasspath("boot", loader) foreach { bootclasspath append _ }
+ }
+
+ /** The parent loader to use for the interpreter.*/
+ private[nsc] var explicitParentLoader: Option[ClassLoader] = None
+
+ /** Retrieves the contents of resource "${id}.class.path" from `loader`
+ * (wrapped in Some) or None if the resource does not exist.*/
+ private def getClasspath(id: String, loader: ClassLoader): Option[String] =
+ Option(loader).flatMap(ld => Option(ld.getResource(id + ".class.path"))).map { cp =>
+ Source.fromURL(cp).mkString
+ }
+
// a wrapper for all Setting creators to keep our list up to date
private def add[T <: Setting](s: T): T = {
allSettings += s
diff --git a/src/library/scala/Enumeration.scala b/src/library/scala/Enumeration.scala
index b5a46f7018..c7c247fed7 100644
--- a/src/library/scala/Enumeration.scala
+++ b/src/library/scala/Enumeration.scala
@@ -6,23 +6,12 @@
** |/ **
\* */
-
-
package scala
import scala.collection.SetLike
-import scala.collection.mutable.{Builder, AddingBuilder, Map, HashMap}
-import scala.collection.immutable.{Set, BitSet}
-import scala.collection.generic.CanBuildFrom
-
-private object Enumeration {
-
- /* This map is used to cache enumeration instances for
- resolving enumeration _values_ to equal objects (by-reference)
- when values are deserialized. */
- private val emap: Map[Class[_], Enumeration] = new HashMap
-
-}
+import scala.collection.{ mutable, immutable, generic }
+import java.lang.reflect.{ Modifier, Method => JMethod, Field => JField }
+import java.util.NoSuchElementException
/** <p>
* Defines a finite set of values specific to the enumeration. Typically
@@ -64,48 +53,23 @@ private object Enumeration {
*/
@serializable
@SerialVersionUID(8476000850333817230L)
-abstract class Enumeration(initial: Int, names: String*) { thisenum =>
+abstract class Enumeration(initial: Int, names: String*) {
+ thisenum =>
def this() = this(0, null)
def this(names: String*) = this(0, names: _*)
- Enumeration.synchronized {
- Enumeration.emap.get(getClass) match {
- case None =>
- Enumeration.emap += (getClass -> this)
- case Some(_) =>
- /* do nothing */
- }
- }
-
/* Note that `readResolve` cannot be private, since otherwise
the JVM does not invoke it when deserializing subclasses. */
- protected def readResolve(): AnyRef = Enumeration.synchronized {
- Enumeration.emap.get(getClass) match {
- case None =>
- Enumeration.emap += (getClass -> this)
- this
- case Some(existing) =>
- existing
- }
- }
+ protected def readResolve(): AnyRef = thisenum.getClass.getField("MODULE$").get()
/** The name of this enumeration.
*/
- override def toString = {
- val name = this.getClass.getName
- var string =
- if (name endsWith "$") name.substring(0, name.length - 1) else name
- val idx1 = string.lastIndexOf('.' : Int)
- if (idx1 != -1) string = string.substring(idx1 + 1)
- val idx2 = string.indexOf('$')
- if (idx2 != -1) string = string.substring(idx2 + 1)
- string
- }
+ override def toString = (getClass.getName stripSuffix "$" split '.' last) split '$' last
/** The mapping from the integer used to identify values to the actual
* values. */
- private val vmap: Map[Int, Value] = new HashMap
+ private val vmap: mutable.Map[Int, Value] = new mutable.HashMap
/** The cache listing all values of this enumeration. */
@transient private var vset: ValueSet = null
@@ -113,13 +77,13 @@ abstract class Enumeration(initial: Int, names: String*) { thisenum =>
/** The mapping from the integer used to identify values to their
* names. */
- private val nmap: Map[Int, String] = new HashMap
+ private val nmap: mutable.Map[Int, String] = new mutable.HashMap
/** The values of this enumeration as a set.
*/
def values: ValueSet = {
if (!vsetDefined) {
- vset = new ValueSet(BitSet.empty ++ (vmap.values map (_.id)))
+ vset = new ValueSet(immutable.BitSet.empty ++ (vmap.values map (_.id)))
vsetDefined = true
}
vset
@@ -130,6 +94,8 @@ abstract class Enumeration(initial: Int, names: String*) { thisenum =>
/** The string to use to name the next created value. */
protected var nextName = names.iterator
+ private def nextNameOrElse(orElse: => String) =
+ if (nextName.hasNext) nextName.next else orElse
/** The highest integer amongst those used to identify values in this
* enumeration. */
@@ -171,8 +137,7 @@ abstract class Enumeration(initial: Int, names: String*) { thisenum =>
* unique amongst all values of the enumeration.
* @return ..
*/
- protected final def Value(i: Int): Value =
- Value(i, if (nextName.hasNext) nextName.next else null)
+ protected final def Value(i: Int): Value = Value(i, nextNameOrElse(null))
/** Creates a fresh value, part of this enumeration, called <code>name</code>.
*
@@ -190,32 +155,27 @@ abstract class Enumeration(initial: Int, names: String*) { thisenum =>
*/
protected final def Value(i: Int, name: String): Value = new Val(i, name)
+ private def populateNameMap() {
+ // The list of possible Value methods: 0-args which return a conforming type
+ val methods = getClass.getMethods filter (m => m.getParameterTypes.isEmpty && classOf[Value].isAssignableFrom(m.getReturnType))
+
+ methods foreach { m =>
+ val name = m.getName
+ // invoke method to obtain actual `Value` instance
+ val value = m.invoke(this).asInstanceOf[Value]
+ // verify that outer points to the correct Enumeration: ticket #3616.
+ if (value.outerEnum eq thisenum) {
+ val id = Int.unbox(classOf[Val] getMethod "id" invoke value)
+ nmap += ((id, name))
+ }
+ }
+ }
+
/* Obtains the name for the value with id `i`. If no name is cached
* in `nmap`, it populates `nmap` using reflection.
*/
private def nameOf(i: Int): String = synchronized {
- def isValDef(m: java.lang.reflect.Method) =
- getClass.getDeclaredFields.exists(fd => fd.getName == m.getName &&
- fd.getType == m.getReturnType)
- nmap.get(i) match {
- case Some(name) => name
- case None =>
- val methods = getClass.getMethods
- for (m <- methods
- if (classOf[Value].isAssignableFrom(m.getReturnType) &&
- !java.lang.reflect.Modifier.isFinal(m.getModifiers) &&
- m.getParameterTypes.isEmpty &&
- isValDef(m))) {
- val name = m.getName
- // invoke method to obtain actual `Value` instance
- val value = m.invoke(this)
- // invoke `id` method
- val idMeth = classOf[Val].getMethod("id")
- val id: Int = idMeth.invoke(value).asInstanceOf[java.lang.Integer].intValue()
- nmap += (id -> name)
- }
- nmap(i)
- }
+ nmap.getOrElse(i, { populateNameMap() ; nmap(i) })
}
/** The type of the enumerated values. */
@@ -224,12 +184,14 @@ abstract class Enumeration(initial: Int, names: String*) { thisenum =>
abstract class Value extends Ordered[Value] {
/** the id and bit location of this enumeration value */
def id: Int
+ /** a marker so we can tell whose values belong to whom come reflective-naming time */
+ private[Enumeration] val outerEnum = thisenum
+
override def compare(that: Value): Int = this.id - that.id
- override def equals(other: Any): Boolean =
- other match {
- case that: thisenum.Value => compare(that) == 0
- case _ => false
- }
+ override def equals(other: Any) = other match {
+ case that: Enumeration#Value => (outerEnum eq that.outerEnum) && (id == that.id)
+ case _ => false
+ }
override def hashCode: Int = id.##
/** this enumeration value as an <code>Int</code> bit mask.
@@ -258,29 +220,25 @@ abstract class Enumeration(initial: Int, names: String*) { thisenum =>
@serializable
@SerialVersionUID(0 - 3501153230598116017L)
protected class Val(i: Int, name: String) extends Value {
- def this(i: Int) =
- this(i, if (nextName.hasNext) nextName.next else i.toString())
- def this(name: String) = this(nextId, name)
- def this() =
- this(nextId, if (nextName.hasNext) nextName.next else nextId.toString())
- assert(!vmap.isDefinedAt(i))
+ def this(i: Int) = this(i, nextNameOrElse(i.toString))
+ def this(name: String) = this(nextId, name)
+ def this() = this(nextId)
+
+ assert(!vmap.isDefinedAt(i), "Duplicate id: " + i)
vmap(i) = this
vsetDefined = false
nextId = i + 1
if (nextId > topId) topId = nextId
def id = i
override def toString() =
- if (name eq null) Enumeration.this.nameOf(i)
- else name
+ if (name != null) name
+ else try thisenum.nameOf(i)
+ catch { case _: NoSuchElementException => "<Invalid enum: no field for #" + i + ">" }
+
protected def readResolve(): AnyRef = {
- val enum = Enumeration.synchronized {
- Enumeration.emap.get(Enumeration.this.getClass) match {
- case None => Enumeration.this
- case Some(existing) => existing
- }
- }
- if (enum.vmap ne null) enum.vmap(i)
- else this
+ val enum = thisenum.readResolve().asInstanceOf[Enumeration]
+ if (enum.vmap == null) this
+ else enum.vmap(i)
}
}
@@ -288,21 +246,24 @@ abstract class Enumeration(initial: Int, names: String*) { thisenum =>
* Iterating through this set will yield values in increasing order of their ids.
* @param ids The set of ids of values, organized as a BitSet.
*/
- class ValueSet private[Enumeration] (val ids: BitSet) extends Set[Value] with SetLike[Value, ValueSet] {
+ class ValueSet private[Enumeration] (val ids: immutable.BitSet) extends Set[Value] with SetLike[Value, ValueSet] {
override def empty = ValueSet.empty
def contains(v: Value) = ids contains (v.id)
def + (value: Value) = new ValueSet(ids + value.id)
def - (value: Value) = new ValueSet(ids - value.id)
- def iterator = ids.iterator map Enumeration.this.apply
- override def stringPrefix = Enumeration.this + ".ValueSet"
+ def iterator = ids.iterator map thisenum.apply
+ override def stringPrefix = thisenum + ".ValueSet"
}
/** A factory object for value sets */
object ValueSet {
+ import mutable.{ Builder, AddingBuilder }
+ import generic.CanBuildFrom
+
/** The empty value set */
- val empty = new ValueSet(BitSet.empty)
+ val empty = new ValueSet(immutable.BitSet.empty)
/** A value set consisting of given elements */
- def apply(elems: Value*): ValueSet = elems.foldLeft(empty)(_ + _)
+ def apply(elems: Value*): ValueSet = empty ++ elems
/** A builder object for value sets */
def newBuilder: Builder[Value, ValueSet] = new AddingBuilder(empty)
/** The implicit builder for value sets */
diff --git a/test/files/run/bug3616.check b/test/files/run/bug3616.check
new file mode 100644
index 0000000000..f31e21baff
--- /dev/null
+++ b/test/files/run/bug3616.check
@@ -0,0 +1 @@
+Fruit.ValueSet(A, B, C)
diff --git a/test/files/run/bug3616.scala b/test/files/run/bug3616.scala
new file mode 100644
index 0000000000..777b97f9ab
--- /dev/null
+++ b/test/files/run/bug3616.scala
@@ -0,0 +1,12 @@
+object X extends Enumeration {
+ val Y = Value
+}
+object Fruit extends Enumeration {
+ val x = X.Y
+ val A,B,C = Value
+}
+object Test {
+ def main(args: Array[String]): Unit = {
+ println(Fruit.values)
+ }
+}