diff options
3 files changed, 96 insertions, 33 deletions
diff --git a/src/library/scala/collection/convert/Wrappers.scala b/src/library/scala/collection/convert/Wrappers.scala index 4410ddc7d8..56f1802509 100644 --- a/src/library/scala/collection/convert/Wrappers.scala +++ b/src/library/scala/collection/convert/Wrappers.scala @@ -212,6 +212,15 @@ private[collection] trait Wrappers { } } } + + override def containsKey(key: AnyRef): Boolean = try { + // Note: Subclass of collection.Map with specific key type may redirect generic + // contains to specific contains, which will throw a ClassCastException if the + // wrong type is passed. This is why we need a type cast to A inside a try/catch. + underlying.contains(key.asInstanceOf[A]) + } catch { + case ex: ClassCastException => false + } } case class MutableMapWrapper[A, B](underlying: mutable.Map[A, B]) extends MapWrapper[A, B](underlying) { diff --git a/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/index.js b/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/index.js index 96689ae701..c201b324e7 100644 --- a/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/index.js +++ b/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/index.js @@ -383,51 +383,56 @@ function compilePattern(query) { // Filters all focused templates and packages. This function should be made less-blocking. // @param query The string of the query function textFilter() { - scheduler.clear("filter"); - - $('#tpl').html(''); - var query = $("#textfilter input").attr("value") || ''; var queryRegExp = compilePattern(query); - var index = 0; + if ((typeof textFilter.lastQuery === "undefined") || (textFilter.lastQuery !== query)) { + + textFilter.lastQuery = query; - var searchLoop = function () { - var packages = Index.keys(Index.PACKAGES).sort(); + scheduler.clear("filter"); - while (packages[index]) { - var pack = packages[index]; - var children = Index.PACKAGES[pack]; - index++; + $('#tpl').html(''); - if (focusFilterState) { - if (pack == focusFilterState || - pack.indexOf(focusFilterState + '.') == 0) { - ; - } else { - continue; + var index = 0; + + var searchLoop = function () { + var packages = Index.keys(Index.PACKAGES).sort(); + + while (packages[index]) { + var pack = packages[index]; + var children = Index.PACKAGES[pack]; + index++; + + if (focusFilterState) { + if (pack == focusFilterState || + pack.indexOf(focusFilterState + '.') == 0) { + ; + } else { + continue; + } } - } - var matched = $.grep(children, function (child, i) { - return queryRegExp.test(child.name); - }); + var matched = $.grep(children, function (child, i) { + return queryRegExp.test(child.name); + }); - if (matched.length > 0) { - $('#tpl').append(Index.createPackageTree(pack, matched, - focusFilterState)); - scheduler.add('filter', searchLoop); - return; + if (matched.length > 0) { + $('#tpl').append(Index.createPackageTree(pack, matched, + focusFilterState)); + scheduler.add('filter', searchLoop); + return; + } } - } - $('#tpl a.packfocus').click(function () { - focusFilter($(this).parent().parent()); - }); - configureHideFilter(); - }; + $('#tpl a.packfocus').click(function () { + focusFilter($(this).parent().parent()); + }); + configureHideFilter(); + }; - scheduler.add('filter', searchLoop); + scheduler.add('filter', searchLoop); + } } /* Configures the hide tool by adding the hide link to all packages. */ diff --git a/test/junit/scala/collection/convert/MapWrapperTest.scala b/test/junit/scala/collection/convert/MapWrapperTest.scala new file mode 100644 index 0000000000..060b6b5937 --- /dev/null +++ b/test/junit/scala/collection/convert/MapWrapperTest.scala @@ -0,0 +1,49 @@ +package scala.collection.convert + +import org.junit.Assert._ +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(classOf[JUnit4]) +class MapWrapperTest { + + /* Test for SI-7883 */ + @Test + def testContains() { + import scala.collection.JavaConverters.mapAsJavaMapConverter + import scala.language.reflectiveCalls // for accessing containsCounter + + // A HashMap which throws an exception when the iterator() method is called. + // Before the fix for SI-7883, calling MapWrapper.containsKey() used to + // iterate through every element of the wrapped Map, and thus would crash + // in this case. + val scalaMap = new scala.collection.mutable.HashMap[String, String] { + var containsCounter = 0 // keep track of how often contains() has been called. + override def iterator = throw new UnsupportedOperationException + + override def contains(key: String): Boolean = { + containsCounter += 1 + super.contains(key) + } + } + + val javaMap = scalaMap.asJava + + scalaMap("hello") = "world" + scalaMap(null) = "null's value" + + assertEquals(0, scalaMap.containsCounter) + assertTrue(javaMap.containsKey("hello")) // positive test + assertTrue(javaMap.containsKey(null)) // positive test, null key + + assertFalse(javaMap.containsKey("goodbye")) // negative test + // Note: this case does NOT make it to scalaMap's contains() method because the runtime + // cast fails in MapWrapper, so the containsCounter is not incremented in this case. + assertFalse(javaMap.containsKey(42)) // negative test, wrong key type + + assertEquals(Some("null's value"), scalaMap.remove(null)) + assertFalse(javaMap.containsKey(null)) // negative test, null key + assertEquals(4, scalaMap.containsCounter) + } +} |