|
| 1 | +/* |
| 2 | + * Scala (https://www.scala-lang.org) |
| 3 | + * |
| 4 | + * Copyright EPFL and Lightbend, Inc. dba Akka |
| 5 | + * |
| 6 | + * Licensed under Apache License 2.0 |
| 7 | + * (http://www.apache.org/licenses/LICENSE-2.0). |
| 8 | + * |
| 9 | + * See the NOTICE file distributed with this work for |
| 10 | + * additional information regarding copyright ownership. |
| 11 | + */ |
| 12 | + |
| 13 | +package scala |
| 14 | + |
| 15 | +import scala.collection.{SpecificIterableFactory, StrictOptimizedIterableOps, View, immutable, mutable} |
| 16 | +import java.lang.reflect.{Field => JField, Method => JMethod} |
| 17 | + |
| 18 | +import scala.annotation.{implicitNotFound, tailrec} |
| 19 | +import scala.reflect.NameTransformer._ |
| 20 | +import scala.util.matching.Regex |
| 21 | + |
| 22 | +/** Defines a finite set of values specific to the enumeration. Typically |
| 23 | + * these values enumerate all possible forms something can take and provide |
| 24 | + * a lightweight alternative to case classes. |
| 25 | + * |
| 26 | + * Each call to a `Value` method adds a new unique value to the enumeration. |
| 27 | + * To be accessible, these values are usually defined as `val` members of |
| 28 | + * the enumeration. |
| 29 | + * |
| 30 | + * All values in an enumeration share a common, unique type defined as the |
| 31 | + * `Value` type member of the enumeration (`Value` selected on the stable |
| 32 | + * identifier path of the enumeration instance). |
| 33 | + * |
| 34 | + * Values SHOULD NOT be added to an enumeration after its construction; |
| 35 | + * doing so makes the enumeration thread-unsafe. If values are added to an |
| 36 | + * enumeration from multiple threads (in a non-synchronized fashion) after |
| 37 | + * construction, the behavior of the enumeration is undefined. |
| 38 | + * |
| 39 | + * @example {{{ |
| 40 | + * // Define a new enumeration with a type alias and work with the full set of enumerated values |
| 41 | + * object WeekDay extends Enumeration { |
| 42 | + * type WeekDay = Value |
| 43 | + * val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value |
| 44 | + * } |
| 45 | + * import WeekDay._ |
| 46 | + * |
| 47 | + * def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun) |
| 48 | + * |
| 49 | + * WeekDay.values filter isWorkingDay foreach println |
| 50 | + * // output: |
| 51 | + * // Mon |
| 52 | + * // Tue |
| 53 | + * // Wed |
| 54 | + * // Thu |
| 55 | + * // Fri |
| 56 | + * }}} |
| 57 | + * |
| 58 | + * @example {{{ |
| 59 | + * // Example of adding attributes to an enumeration by extending the Enumeration.Val class |
| 60 | + * object Planet extends Enumeration { |
| 61 | + * protected case class PlanetVal(mass: Double, radius: Double) extends super.Val { |
| 62 | + * def surfaceGravity: Double = Planet.G * mass / (radius * radius) |
| 63 | + * def surfaceWeight(otherMass: Double): Double = otherMass * surfaceGravity |
| 64 | + * } |
| 65 | + * import scala.language.implicitConversions |
| 66 | + * implicit def valueToPlanetVal(x: Value): PlanetVal = x.asInstanceOf[PlanetVal] |
| 67 | + * |
| 68 | + * val G: Double = 6.67300E-11 |
| 69 | + * val Mercury = PlanetVal(3.303e+23, 2.4397e6) |
| 70 | + * val Venus = PlanetVal(4.869e+24, 6.0518e6) |
| 71 | + * val Earth = PlanetVal(5.976e+24, 6.37814e6) |
| 72 | + * val Mars = PlanetVal(6.421e+23, 3.3972e6) |
| 73 | + * val Jupiter = PlanetVal(1.9e+27, 7.1492e7) |
| 74 | + * val Saturn = PlanetVal(5.688e+26, 6.0268e7) |
| 75 | + * val Uranus = PlanetVal(8.686e+25, 2.5559e7) |
| 76 | + * val Neptune = PlanetVal(1.024e+26, 2.4746e7) |
| 77 | + * } |
| 78 | + * |
| 79 | + * println(Planet.values.filter(_.radius > 7.0e6)) |
| 80 | + * // output: |
| 81 | + * // Planet.ValueSet(Jupiter, Saturn, Uranus, Neptune) |
| 82 | + * }}} |
| 83 | + * |
| 84 | + * @param initial The initial value from which to count the integers that |
| 85 | + * identifies values at run-time. |
| 86 | + */ |
| 87 | +@SerialVersionUID(8476000850333817230L) |
| 88 | +abstract class Enumeration (initial: Int) extends Serializable { |
| 89 | + thisenum => |
| 90 | + |
| 91 | + def this() = this(0) |
| 92 | + |
| 93 | + /* Note that `readResolve` cannot be private, since otherwise |
| 94 | + the JVM does not invoke it when deserializing subclasses. */ |
| 95 | + protected def readResolve(): AnyRef = thisenum.getClass.getField(MODULE_INSTANCE_NAME).get(null) |
| 96 | + |
| 97 | + /** The name of this enumeration. |
| 98 | + */ |
| 99 | + override def toString: String = |
| 100 | + ((getClass.getName stripSuffix MODULE_SUFFIX_STRING split '.').last split |
| 101 | + Regex.quote(NAME_JOIN_STRING)).last |
| 102 | + |
| 103 | + /** The mapping from the integer used to identify values to the actual |
| 104 | + * values. */ |
| 105 | + private val vmap: mutable.Map[Int, Value] = new mutable.HashMap |
| 106 | + |
| 107 | + /** The cache listing all values of this enumeration. */ |
| 108 | + @transient private var vset: ValueSet = null |
| 109 | + @transient @volatile private var vsetDefined = false |
| 110 | + |
| 111 | + /** The mapping from the integer used to identify values to their |
| 112 | + * names. */ |
| 113 | + private[this] val nmap: mutable.Map[Int, String] = new mutable.HashMap |
| 114 | + |
| 115 | + /** The values of this enumeration as a set. |
| 116 | + */ |
| 117 | + def values: ValueSet = { |
| 118 | + if (!vsetDefined) { |
| 119 | + vset = (ValueSet.newBuilder ++= vmap.values).result() |
| 120 | + vsetDefined = true |
| 121 | + } |
| 122 | + vset |
| 123 | + } |
| 124 | + |
| 125 | + /** The integer to use to identify the next created value. */ |
| 126 | + protected var nextId: Int = initial |
| 127 | + |
| 128 | + /** The string to use to name the next created value. */ |
| 129 | + protected var nextName: Iterator[String] = _ |
| 130 | + |
| 131 | + private def nextNameOrNull = |
| 132 | + if (nextName != null && nextName.hasNext) nextName.next() else null |
| 133 | + |
| 134 | + /** The highest integer amongst those used to identify values in this |
| 135 | + * enumeration. */ |
| 136 | + private[this] var topId = initial |
| 137 | + |
| 138 | + /** The lowest integer amongst those used to identify values in this |
| 139 | + * enumeration, but no higher than 0. */ |
| 140 | + private[this] var bottomId = if(initial < 0) initial else 0 |
| 141 | + |
| 142 | + /** The one higher than the highest integer amongst those used to identify |
| 143 | + * values in this enumeration. */ |
| 144 | + final def maxId = topId |
| 145 | + |
| 146 | + /** The value of this enumeration with given id `x` |
| 147 | + */ |
| 148 | + final def apply(x: Int): Value = vmap(x) |
| 149 | + |
| 150 | + /** Return a `Value` from this `Enumeration` whose name matches |
| 151 | + * the argument `s`. The names are determined automatically via reflection. |
| 152 | + * |
| 153 | + * @param s an `Enumeration` name |
| 154 | + * @return the `Value` of this `Enumeration` if its name matches `s` |
| 155 | + * @throws NoSuchElementException if no `Value` with a matching |
| 156 | + * name is in this `Enumeration` |
| 157 | + */ |
| 158 | + final def withName(s: String): Value = values.byName.getOrElse(s, |
| 159 | + throw new NoSuchElementException(s"No value found for '$s'")) |
| 160 | + |
| 161 | + /** Creates a fresh value, part of this enumeration. */ |
| 162 | + protected final def Value: Value = Value(nextId) |
| 163 | + |
| 164 | + /** Creates a fresh value, part of this enumeration, identified by the |
| 165 | + * integer `i`. |
| 166 | + * |
| 167 | + * @param i An integer that identifies this value at run-time. It must be |
| 168 | + * unique amongst all values of the enumeration. |
| 169 | + * @return Fresh value identified by `i`. |
| 170 | + */ |
| 171 | + protected final def Value(i: Int): Value = Value(i, nextNameOrNull) |
| 172 | + |
| 173 | + /** Creates a fresh value, part of this enumeration, called `name`. |
| 174 | + * |
| 175 | + * @param name A human-readable name for that value. |
| 176 | + * @return Fresh value called `name`. |
| 177 | + */ |
| 178 | + protected final def Value(name: String): Value = Value(nextId, name) |
| 179 | + |
| 180 | + /** Creates a fresh value, part of this enumeration, called `name` |
| 181 | + * and identified by the integer `i`. |
| 182 | + * |
| 183 | + * @param i An integer that identifies this value at run-time. It must be |
| 184 | + * unique amongst all values of the enumeration. |
| 185 | + * @param name A human-readable name for that value. |
| 186 | + * @return Fresh value with the provided identifier `i` and name `name`. |
| 187 | + */ |
| 188 | + protected final def Value(i: Int, name: String): Value = new Val(i, name) |
| 189 | + |
| 190 | + private def populateNameMap(): Unit = { |
| 191 | + @tailrec def getFields(clazz: Class[_], acc: Array[JField]): Array[JField] = { |
| 192 | + if (clazz == null) |
| 193 | + acc |
| 194 | + else |
| 195 | + getFields(clazz.getSuperclass, if (clazz.getDeclaredFields.isEmpty) acc else acc ++ clazz.getDeclaredFields) |
| 196 | + } |
| 197 | + val fields = getFields(getClass.getSuperclass, getClass.getDeclaredFields) |
| 198 | + def isValDef(m: JMethod): Boolean = fields exists (fd => fd.getName == m.getName && fd.getType == m.getReturnType) |
| 199 | + |
| 200 | + // The list of possible Value methods: 0-args which return a conforming type |
| 201 | + val methods: Array[JMethod] = getClass.getMethods filter (m => m.getParameterTypes.isEmpty && |
| 202 | + classOf[Value].isAssignableFrom(m.getReturnType) && |
| 203 | + m.getDeclaringClass != classOf[Enumeration] && |
| 204 | + isValDef(m)) |
| 205 | + methods foreach { m => |
| 206 | + val name = m.getName |
| 207 | + // invoke method to obtain actual `Value` instance |
| 208 | + val value = m.invoke(this).asInstanceOf[Value] |
| 209 | + // verify that outer points to the correct Enumeration: ticket #3616. |
| 210 | + if (value.outerEnum eq thisenum) { |
| 211 | + val id: Int = value.id |
| 212 | + nmap += ((id, name)) |
| 213 | + } |
| 214 | + } |
| 215 | + } |
| 216 | + |
| 217 | + /* Obtains the name for the value with id `i`. If no name is cached |
| 218 | + * in `nmap`, it populates `nmap` using reflection. |
| 219 | + */ |
| 220 | + private def nameOf(i: Int): String = synchronized { nmap.getOrElse(i, { populateNameMap() ; nmap(i) }) } |
| 221 | + |
| 222 | + /** The type of the enumerated values. */ |
| 223 | + @SerialVersionUID(7091335633555234129L) |
| 224 | + abstract class Value extends Ordered[Value] with Serializable { |
| 225 | + /** the id and bit location of this enumeration value */ |
| 226 | + def id: Int |
| 227 | + /** a marker so we can tell whose values belong to whom come reflective-naming time */ |
| 228 | + private[Enumeration] val outerEnum = thisenum |
| 229 | + |
| 230 | + override def compare(that: Value): Int = |
| 231 | + if (this.id < that.id) -1 |
| 232 | + else if (this.id == that.id) 0 |
| 233 | + else 1 |
| 234 | + override def equals(other: Any): Boolean = other match { |
| 235 | + case that: Enumeration#Value => (outerEnum eq that.outerEnum) && (id == that.id) |
| 236 | + case _ => false |
| 237 | + } |
| 238 | + override def hashCode: Int = id.## |
| 239 | + |
| 240 | + /** Create a ValueSet which contains this value and another one */ |
| 241 | + def + (v: Value): ValueSet = ValueSet(this, v) |
| 242 | + } |
| 243 | + |
| 244 | + /** A class implementing the [[scala.Enumeration.Value]] type. This class |
| 245 | + * can be overridden to change the enumeration's naming and integer |
| 246 | + * identification behaviour. |
| 247 | + */ |
| 248 | + @SerialVersionUID(0 - 3501153230598116017L) |
| 249 | + protected class Val(i: Int, name: String) extends Value with Serializable { |
| 250 | + def this(i: Int) = this(i, nextNameOrNull) |
| 251 | + def this(name: String) = this(nextId, name) |
| 252 | + def this() = this(nextId) |
| 253 | + |
| 254 | + assert(!vmap.isDefinedAt(i), "Duplicate id: " + i) |
| 255 | + vmap(i) = this |
| 256 | + vsetDefined = false |
| 257 | + nextId = i + 1 |
| 258 | + if (nextId > topId) topId = nextId |
| 259 | + if (i < bottomId) bottomId = i |
| 260 | + def id: Int = i |
| 261 | + override def toString(): String = |
| 262 | + if (name != null) name |
| 263 | + else try thisenum.nameOf(i) |
| 264 | + catch { case _: NoSuchElementException => "<Invalid enum: no field for #" + i + ">" } |
| 265 | + |
| 266 | + protected def readResolve(): AnyRef = { |
| 267 | + val enumeration = thisenum.readResolve().asInstanceOf[Enumeration] |
| 268 | + if (enumeration.vmap == null) this |
| 269 | + else enumeration.vmap(i) |
| 270 | + } |
| 271 | + } |
| 272 | + |
| 273 | + /** An ordering by id for values of this set */ |
| 274 | + implicit object ValueOrdering extends Ordering[Value] { |
| 275 | + |
| 276 | + // IMPORTANT: |
| 277 | + // Scala 3 removes unnecessary outer pointers while Scala 2 doesn't |
| 278 | + // This is important to capture the outer pointer when compiling with |
| 279 | + // dotc to maintain our binary compatibility requirements |
| 280 | + private val _ = Enumeration.this |
| 281 | + def compare(x: Value, y: Value): Int = x compare y |
| 282 | + } |
| 283 | + |
| 284 | + /** A class for sets of values. |
| 285 | + * Iterating through this set will yield values in increasing order of their ids. |
| 286 | + * |
| 287 | + * @param nnIds The set of ids of values (adjusted so that the lowest value does |
| 288 | + * not fall below zero), organized as a `BitSet`. |
| 289 | + * @define Coll `collection.immutable.SortedSet` |
| 290 | + */ |
| 291 | + @SerialVersionUID(7229671200427364242L) |
| 292 | + class ValueSet private[ValueSet] (private[this] var nnIds: immutable.BitSet) |
| 293 | + extends immutable.AbstractSet[Value] |
| 294 | + with immutable.SortedSet[Value] |
| 295 | + with immutable.SortedSetOps[Value, immutable.SortedSet, ValueSet] |
| 296 | + with StrictOptimizedIterableOps[Value, immutable.Set, ValueSet] |
| 297 | + with Serializable { |
| 298 | + |
| 299 | + implicit def ordering: Ordering[Value] = ValueOrdering |
| 300 | + def rangeImpl(from: Option[Value], until: Option[Value]): ValueSet = |
| 301 | + new ValueSet(nnIds.rangeImpl(from.map(_.id - bottomId), until.map(_.id - bottomId))) |
| 302 | + |
| 303 | + override def empty: ValueSet = ValueSet.empty |
| 304 | + override def knownSize: Int = nnIds.size |
| 305 | + override def isEmpty: Boolean = nnIds.isEmpty |
| 306 | + def contains(v: Value): Boolean = nnIds contains (v.id - bottomId) |
| 307 | + def incl (value: Value): ValueSet = new ValueSet(nnIds + (value.id - bottomId)) |
| 308 | + def excl (value: Value): ValueSet = new ValueSet(nnIds - (value.id - bottomId)) |
| 309 | + def iterator: Iterator[Value] = nnIds.iterator map (id => thisenum.apply(bottomId + id)) |
| 310 | + override def iteratorFrom(start: Value): Iterator[Value] = nnIds iteratorFrom start.id map (id => thisenum.apply(bottomId + id)) |
| 311 | + override def className: String = s"$thisenum.ValueSet" |
| 312 | + /** Creates a bit mask for the zero-adjusted ids in this set as a |
| 313 | + * new array of longs */ |
| 314 | + def toBitMask: Array[Long] = nnIds.toBitMask |
| 315 | + |
| 316 | + override protected def fromSpecific(coll: IterableOnce[Value]): ValueSet = ValueSet.fromSpecific(coll) |
| 317 | + override protected def newSpecificBuilder = ValueSet.newBuilder |
| 318 | + |
| 319 | + def map(f: Value => Value): ValueSet = fromSpecific(new View.Map(this, f)) |
| 320 | + def flatMap(f: Value => IterableOnce[Value]): ValueSet = fromSpecific(new View.FlatMap(this, f)) |
| 321 | + |
| 322 | + // necessary for disambiguation: |
| 323 | + override def map[B](f: Value => B)(implicit @implicitNotFound(ValueSet.ordMsg) ev: Ordering[B]): immutable.SortedSet[B] = |
| 324 | + super[SortedSet].map[B](f) |
| 325 | + override def flatMap[B](f: Value => IterableOnce[B])(implicit @implicitNotFound(ValueSet.ordMsg) ev: Ordering[B]): immutable.SortedSet[B] = |
| 326 | + super[SortedSet].flatMap[B](f) |
| 327 | + override def zip[B](that: IterableOnce[B])(implicit @implicitNotFound(ValueSet.zipOrdMsg) ev: Ordering[(Value, B)]): immutable.SortedSet[(Value, B)] = |
| 328 | + super[SortedSet].zip[B](that) |
| 329 | + override def collect[B](pf: PartialFunction[Value, B])(implicit @implicitNotFound(ValueSet.ordMsg) ev: Ordering[B]): immutable.SortedSet[B] = |
| 330 | + super[SortedSet].collect[B](pf) |
| 331 | + |
| 332 | + @transient private[Enumeration] lazy val byName: Map[String, Value] = iterator.map( v => v.toString -> v).toMap |
| 333 | + } |
| 334 | + |
| 335 | + /** A factory object for value sets */ |
| 336 | + @SerialVersionUID(3L) |
| 337 | + object ValueSet extends SpecificIterableFactory[Value, ValueSet] { |
| 338 | + private final val ordMsg = "No implicit Ordering[${B}] found to build a SortedSet[${B}]. You may want to upcast to a Set[Value] first by calling `unsorted`." |
| 339 | + private final val zipOrdMsg = "No implicit Ordering[${B}] found to build a SortedSet[(Value, ${B})]. You may want to upcast to a Set[Value] first by calling `unsorted`." |
| 340 | + |
| 341 | + /** The empty value set */ |
| 342 | + val empty: ValueSet = new ValueSet(immutable.BitSet.empty) |
| 343 | + /** A value set containing all the values for the zero-adjusted ids |
| 344 | + * corresponding to the bits in an array */ |
| 345 | + def fromBitMask(elems: Array[Long]): ValueSet = new ValueSet(immutable.BitSet.fromBitMask(elems)) |
| 346 | + /** A builder object for value sets */ |
| 347 | + def newBuilder: mutable.Builder[Value, ValueSet] = new mutable.Builder[Value, ValueSet] { |
| 348 | + private[this] val b = new mutable.BitSet |
| 349 | + def addOne (x: Value) = { b += (x.id - bottomId); this } |
| 350 | + def clear() = b.clear() |
| 351 | + def result() = new ValueSet(b.toImmutable) |
| 352 | + } |
| 353 | + def fromSpecific(it: IterableOnce[Value]): ValueSet = |
| 354 | + newBuilder.addAll(it).result() |
| 355 | + } |
| 356 | +} |
0 commit comments