tags:

views:

132

answers:

2

I would like to call the following java method from scala:

protected final FilterKeyBindingBuilder filter(String urlPattern, String... morePatterns) {
    return filtersModuleBuilder.filter(Lists.newArrayList(urlPattern, morePatterns));
}

my scala caller looks like this

def test(url: String, urls: String*) {
  filter(url, urls: _*).through(classOf[MyTestWhateverFilter]) 
}

this compiles, however, executing the code gives an exception:

java.lang.ClassCastException: scala.collection.mutable.WrappedArray$ofRef cannot be cast to [Ljava.lang.String;

I also tried this:

def test(url: String, urls: String*) {
  filter(url, urls.map(_.asInstanceOf[java.lang.String]) :_*).through(classOf[MyTestWhateverFilter]) 
}

in this case the exception was:

java.lang.ClassCastException: scala.collection.mutable.ArrayBuffer cannot be cast to [Ljava.lang.String;

I thought that in 2.8 Array[String] is passed to java as String[] array and no extra unboxing is necessary.

Any ideas?

Thanks in advance!

EDIT:

how to replicate it:

import com.google.inject.servlet.ServletModule

trait ScalaServletModule extends ServletModule{
  def test(s: String,strs: String*) = {
    println(strs.getClass)
    println(super.filter(s,strs:_*))
  }
}
object Test {
  def main(args: Array[String]) {
      val module  = new ServletModule with ScalaServletModule
      module.test("/rest")
  }
}



/opt/local/lib/scala28/bin/scala -cp /Users/p.user/Downloads/guice-2.0/guice-2.0.jar:/Users/p.user/Downloads/guice-2.0/guice-servlet-2.0.jar:/Users/p.user/Downloads/guice-2.0/aopalliance.jar:/Users/p.user/Downloads/javax.jar/javax.jar:. Test

result:

class scala.collection.mutable.WrappedArray$ofRef
java.lang.ClassCastException: scala.collection.mutable.WrappedArray$ofRef cannot be cast to [Ljava.lang.String;
    at ScalaServletModule$class.test(test.scala:6)
    at Test$$anon$1.test(test.scala:11)
    at Test$.main(test.scala:12)
    at Test.main(test.scala)
A: 

The Scala and Java approach for varargs doesn't match: Scala varargs are based on Seqs (or so?) and Java varargs on arrays. Did you try

filter(url, urls.toArray:_*).through(classOf[MyTestWhateverFilter]) 

?

At least this seems to have worked here: http://stackoverflow.com/questions/1008783/using-varargs-from-scala

Landei
thanks for your answer. String*'s type is WrapperArray and toArray won't change that. so this will result in the first error message
poko
Strange, for me something like `System.out.printf("%s %s", Array("Hello","World"):_*)` works fine.
Landei
+3  A: 

I've just tried to reproduce your error using Scala 2.8.0 and can't. Here's my code

// Example.java
public class Example {
  public static void test(String... args) {
    System.out.println(args.getClass());
  }
}

// In test.scala
object Test {
  def main(args: Array[String]) {
      test("1", "2", "3")
  }
  def test(strs: String*) = {
    println(strs.getClass)
    Example.test(strs:_*)
  }
}

I get the following output:

class scala.collection.mutable.WrappedArray$ofRef
class [Ljava.lang.String;

So it looks like the compiler is inserting the correct conversion to convert the WrappedArray.ofRef to a String[].

Edit

Just tried running your example. It looks like some interaction of super-accessors in traits with converting Scala varargs to Java varargs. If you change the trait to a class it works.

From the decompiled output of ScalaServletModule$class, it looks like it doesn't do the necessary conversion from String* to String[] when calling the super accessor (line 19).

public static void test(ScalaServletModule, java.lang.String, scala.collection.Seq);
  Code:
   0:   getstatic   #11; //Field scala/Predef$.MODULE$:Lscala/Predef$;
   3:   aload_2
   4:   invokevirtual   #18; //Method java/lang/Object.getClass:()Ljava/lang/Class;
   7:   invokevirtual   #22; //Method scala/Predef$.println:(Ljava/lang/Object;)V
   10:  getstatic   #11; //Field scala/Predef$.MODULE$:Lscala/Predef$;
   13:  aload_0
   14:  aload_1
   15:  aload_2
   16:  checkcast   #24; //class "[Ljava/lang/String;"
   19:  invokeinterface #30,  3; //InterfaceMethod ScalaServletModule.ScalaServletModule$$super$filter:(Ljava/lang/String;[Ljava/lang/String;)Lcom/google/inject/servlet/ServletModule$FilterKeyBindingBuilder;
   24:  invokevirtual   #22; //Method scala/Predef$.println:(Ljava/lang/Object;)V
   27:  return
Ben Lings
I tried it with your example it does seem to work. hmm not sure why calling that guice servlet's method makes a difference.
poko
(added an example)
poko
interesting! I might report this as a bug
poko