How to call an overriden method using reflection? - scala

Consider we have the following:
class Base { def name = "Base" }
class Successor extends Base {
override def name = "Successor"
}
I have tried to do the following (took from How to call a superclass method using Java reflection):
import java.lang.invoke.{MethodHandles, MethodHandle, MethodType}
object TestApp {
def main(args: Array[String]) {
val a = new Successor;
val h1 = MethodHandles.lookup().findSpecial(classOf[Base],
"name",
MethodType.methodType(classOf[String]),
classOf[Successor]);
println(h1.invoke(a));
}
}
but I get a runtime exception:
java.lang.IllegalAccessException: no private access for invokespecial: class Successor, from TestApp$
I was told that it is possible that Java reflection may not work correctly for Scala. Is it true? Or I simply do something wrong?

Actually, you can NOT even do it in Java. Note, in the answer of "How to call a superclass method using Java reflection", it works because the Test extends the Base: public class Test extends Base {...}.

It looks like it is possible and Java reflection works for Scala as well, I just didn't read all answers for How to call a superclass method using Java reflection.
The following code works:
object TestApp {
def main(args: Array[String]) {
val a = new Successor;
val impl_lookup = classOf[MethodHandles.Lookup].getDeclaredField("IMPL_LOOKUP")
impl_lookup.setAccessible(true)
val lkp = impl_lookup.get(null).asInstanceOf[MethodHandles.Lookup];
val h1 = lkp.findSpecial(classOf[Base],
"name",
MethodType.methodType(classOf[String]),
classOf[Successor])
println(h1.invoke(a)) // prints "Base"
println(a.name) // prints "Successor"
}
}
Thanks to Jesse Glick for this solution.

Related

Scala: mock Hadoop File System

I need to mock (with scalamock 4.4.0/scalatest 3.2.9) the behaviour of Hadoop file system. I found out, that I can't mock it directly, so I'm trying to get around this problem.
On the first, try I created wrapper (in the scope of main logic):
class HadoopFsWrapper(path: Path)(implicit fs: org.apache.hadoop.fs.FileSystem) {
def listStatus(path) = fs.listStatus(path)
}
class HadoopService(path: Path)(implicit val fs: HadoopFsWrapper){
def someLogic() = fs.listStatus(path)
}
I declare this class both in main and test code with defining of implicit HadoopFsWrapper.
implicit val fs = mock[HadoopFsWrapper]
val hs = HadoopService(path)
// other test logic
And this done well.
But I don't think, its a good idea to modify main code for test purpose.
Other way I considered is to create wrapper only in test scope and apply some kind of "decorator" to deceive HadoopService, which expects FileSystem to be defined in main scope:
// only in test scope
class HadoopFsWrapper(path: Path) extends org.apache.hadoop.fs.FileSystem {
def listStatus(path) = listStatus(path)
// dummies for implemented FileSystem-methods
}
object HadoopFsWrapper {
def apply(): org.apache.hadoop.fs.FileSystem = new HadoopFsWrapper
}
// main logic
class HadoopService(path: Path)(implicit val fs: org.apache.hadoop.fs.FileSystem){
def someLogic() = fs.listStatus(path)
}
But when I'm trying to mock HadoopFsWrapper it's defined as null.

Scala SetBuffer

I am trying to update my project from Scala 2.12 to Scala 2.13. I see that the Scala team has done a lot of changes on the collection side.
I have a SetBuffer that looks like this in Scala 2.12
object SetBuffer extends MutableSetFactory[SetBuffer] {
override def empty[Elem] = new SetBuffer[Elem](Set.empty)
def apply[Elem](initialSet: Iterable[Elem]): SetBuffer[Elem] =
new SetBuffer[Elem](initialSet.toSet)
override def newBuilder[Elem] = new SetBufferBuilder[Elem]()
class SetBufferBuilder[Elem] extends mutable.GrowingBuilder[Elem, SetBuffer[Elem]](SetBuffer.empty) {
override def result(): SetBuffer[Elem] =
new SetBuffer[Elem](elems.getDelta)
}
}
class SetBuffer[ElemType](initialSet: Set[ElemType])
extends mutable.HashSet[ElemType]
with mutable.SetLike[ElemType, SetBuffer[ElemType]]
with GenericSetTemplate[ElemType, SetBuffer] {
initialSet.map(+=)
override def companion: SetBuffer.type = SetBuffer
def getDeltaBuffer: SetBuffer[ElemType] = {
SetBuffer[ElemType]((this -- initialSet).toSeq:_*)
}
def getDelta: immutable.Set[ElemType] = {
val deltaElements = (this -- initialSet).toSeq
immutable.Set[ElemType](deltaElements: _*)
}
def ++(that: Iterable[ElemType]): SetBuffer[ElemType] =
SetBuffer(toIterable ++ that)
def append(that: Iterable[ElemType]): Unit = this ++= that
def ++=(that: Iterable[ElemType]): Unit = that.map(+=)
}
Scala 2.13 removed mutable.SetLike[ElemType, SetBuffer[ElemType]] and GenericSetTemplate[ElemType, SetBuffer] and made mutable.HashSet final. Is there a way to rewrite the code in the class SetBuffer[ElemType](initialSet: Set[ElemType]) so it will behave the same and have the same performances?
Two ideas come to my mind.
First - you can try to use https://www.scala-lang.org/api/current/scala/collection/mutable/LinkedHashSet.html that seems to not be final. I am not sure if it's possible and I didn't search for performance characteristics.
Second, you go for composition over inheritance. Wrap the instance of HashSet and delegate all calls to it. You would have to implement quite a few interfaces to achieve the same behaviour though.

class can't have main() in scala?

I'm a beginner of Scala.
In Calulator.scala file
class Calulator {
var brand: String ="HP"
def add(m:Int, n:Int):Int = m+n
}
In CalculatorTest.scala file
object CalulatorTest {
def main(args:Array[String]) {
val cal = new Calulator
println(cal.add(1,2))
}
}
Compile two scala files and run CalculatorTest. This works.
My question is can I make them as one scala file like Java?
In Calulator.scala file
class Calulator {
var brand: String ="HP"
def add(m:Int, n:Int):Int = m+n
def main(args:Array[String]) {
val cal = new Calulator
println(cal.add(1,2))
}
}
This doesn't work !!!
I googled it and looks like main() should be in object.
Is there a way to put them in one file to run main()?
To put them in one file use the concept of the 'Companion Object'.
The class and companion object must be in the same file and share the same name.
class Calculator {
var brand: String = "HP"
def add(m: Int, n: Int): Int = m + n
}
object Calculator {
def main(args:Array[String]) {
val cal = new Calculator
println(cal.add(1,2))
}
}
Further reading: http://docs.scala-lang.org/tutorials/tour/singleton-objects.html
The simple answer is No
a deeper answer is that the scala compiler refers to an object as static and class it refers as regular.
when you want to run Java program your main must be declared as static otherwise the JVM will not recognize it as your program entry point.
regards the question about is it possible to union both of them ,the answer is not because thats how the scala compiler works.
The compiler would create MyClass$.class for scala object and MyClass.class for scala class thats why you should never ever call your class something like :
class MyClass$ {
}
because it will override the companion object for MyClass.

How can I pass an instance to a ServiceInjector trait with scala?

I'm following the advice from com.googlegroups.google-guice http://markmail.org/message/ljnhl6rstverrxuj
Well, it's actually almost as referred to you in the link from the other answer:
http://jonasboner.com/2008/10/06/real-world-scala-dependency-injection-di/
class MyClient {
#Inject val toBeInjected: AnotherClass = toBeInjected // !!
}
trait ServiceInjector {
ServiceInjector.inject( this )
}
object ServiceInjector {
private val injector = Guice.createInjector( Array[Module]( new YourModule ) )
def inject( obj: AnyRef ) = injector.injectMembers( obj )
}
Usage:
val client = new MyClient with ServiceInjector
or:
class InjectedMyClient extends MyClient with ServiceInjector
But I'm very new to Scala and trying to work out how I can use the following pattern for dependency injection, when the Guice Module itself needs references to instances passed in from elsewhere.
But since traits can't have constructors, and neither can the Companion Object it looks like I'm screwed?
package au.id.rleach.overmind.guice
import com.google.inject.{Provides, Guice, Binder, Module}
import org.slf4j.Logger
import org.spongepowered.api.service.ServiceManager
import org.spongepowered.api.world.TeleportHelper
import org.spongepowered.api.{GameRegistry, Game}
import org.spongepowered.api.plugin.PluginManager
import org.spongepowered.api.scoreboard.ScoreboardBuilder
import org.spongepowered.api.service.event.EventManager
class OModule(val game: Game, val logger: Logger, val pluginManager: PluginManager, val serviceManager: ServiceManager, val eventManager: EventManager, val gameRegistry: GameRegistry, val teleportHelper: TeleportHelper) extends Module {
override def configure(binder: Binder): Unit = {
binder.bind(classOf[Game]).toInstance(game)
binder.bind(classOf[Logger]).toInstance(logger)
binder.bind(classOf[PluginManager]).toInstance(pluginManager)
binder.bind(classOf[ServiceManager]).toInstance(serviceManager)
binder.bind(classOf[EventManager]).toInstance(eventManager)
binder.bind(classOf[GameRegistry]).toInstance(gameRegistry)
binder.bind(classOf[TeleportHelper]).toInstance(teleportHelper)
//bind(classOf[File]).annotatedWith(new ConfigDirAnnotation(true)).toInstance(Loader.instance.getConfigDir)
}
}
trait ServiceInjector {
ServiceInjector.inject(this)
}
object ServiceInjector {
private val injector = Guice.createInjector(
//####
new OModule()//compilation error.
//####
)
def inject(obj: AnyRef) = injector.injectMembers(obj)
}
I realize that the object is being initialized when it's first used, and that is after I have a copy of the instances to pass to OModule, but I can't seem to find a way to pass them in to the object.
Since I'm using Scala I am not a fan anymore of using DI frameworks since Scala has natively DI-like support already. This is called the Cake Pattern. There are plenty resources available on this, like this blogpost from Cake Solutions.
Both at ScalaDays 2014 and Devoxx 2014 Dick Wall also presented about a more lightweight DI solution which he called the Parfait Pattern. Both talks can be viewed on Parleys.com
If you really want to use a DI framework, Scaldi is a nice looking Scala framework utilising Scala features, but of course you can also keep on using Spring or Guice.
I'm not sure about this:
#Inject val toBeInjected: AnotherClass = toBeInjected
wouldn't work in my experience. It needs to be a var rather than val and the initial value null.
#Inject var toBeInjected: AnotherClass = null
I created a demo on GitHub which is the Play-Scala template with the index method changed as follows:
class Application extends Controller {
#Inject var ws: WSClient = null
def index = Action.async {
ws.url("http://google.com").get.map(r => Ok(r.body))
}
}
which worked well. That injected to a field, rather than as a constructor parameter. The same technique can be used with traits.

Scala's delayedInit and Scallop

SLS 5.1 says "Delayed Initializaton. The initialization code of an object or class (but not a trait) that follows the superclass constructor invocation and the mixin-evaluation of the template’s base classes is passed to a special hook, which is inaccessible from user code. Normally, that hook simply executes the code that is passed to it. But templates inheriting the scala.DelayedInit trait can override the hook by re-implementing the delayedInit method, which is defined as follows:"
def delayedInit(body: => Unit)
The ScallopConf command-line parser extends DelayedInit and using it according to the docs generates the warning Selecting value apples from class Conf, which extends scala.DelayedInit, is likely to yield an uninitialized value.
How should the following simple example be rewritten so the warning is not generated?
import org.rogach.scallop._
class Conf(arguments: Seq[String]) extends ScallopConf(arguments) {
val help = opt[Boolean](name = "help", short = 'h', descr = "Help me please.")
}
object Gen {
def main(args: Array[String]) {
val conf: Conf = new Conf(args)
if (conf.help()) {
println(s"""Usage: Gen [--help]""")
sys.exit(-1)
}
println("Do some work here")
}
}
Making help a lazy val and calling conf.afterInit() before accessing conf.help should clear the compiler warnings.