1. Diese Seite verwendet Cookies. Wenn du dich weiterhin auf dieser Seite aufhältst, akzeptierst du unseren Einsatz von Cookies. Weitere Informationen

Beispiel zur Einbindung des Scala-Interpreters in kompilierte Scala-Anwendungen

Dieses Thema im Forum "Java" wurde erstellt von Konstantin Denerz, 7. August 2008.

  1. Hallo,

    hier ist ein kleines Beispiel, dass zeigen soll, wie einfach man in seine kompilierte Scala-Anwendung, den Scala-Interpreter einbauen kann, um bestimmte Bereiche der Anwendung flexibel zu halten. Die Flexibilität erfolgt über in Scala geschriebene Skripte, die zur Laufzeit dynamisch kompiliert (interpretiert) werden. In dem Beispiel wird auch gezeigt, wie man bestimmte Kontexte an ein Skript bzw. an den Interpreter bindet. Damit lassen sich die Kontexte durch Skripte abrufen und manipulieren.

    Umgebung:
    Eclipse 3.4
    Scala Plugin Beta (nightly build)
    Scala 2.7.1 Final (scala-library.jar, scala-compiler.jar)

    Beispiel:

    In dem Beispiel erstelle ich einen Kontext(UserContext), der bestimmte Informationen oder Dienste enthält, auf die ich aus meinem Skript zugreifen will.

    Danach definiere ich meinen Prozess(UseCase), der einen Anwendungfall repräsentiert. In meinem Bespiel kann jeder Prozess eine Liste(subProcesses) mit Teilprozessen(auch vom Typ IProcess) beinhalten.
    Ein Teilprozess ist mein Skript(UserSpecificScript) was zusätzlich zu dem Code den ich für meinen Anwendungfall(UseCase) in der run Methode implementiere ausgeführt werden soll. Das Skript ist einfach gehalten und weist der Variable text, in dem Kontext, den Wert "Hello Bubu!" zu.

    In dem trait (interface) IScript ist eine Variable scriptsDirectory definiert. Diese verwende ich um die Pfade zu den Skripten dynamisch zusammen zu bauen. Der Pfad setzt sich aus scriptsDirectory + ClassName + .scala zusammen, wobei der ClassName der Name der Klasse ohne dem Package ist.

    Das Skript(UserSpecificScript.scala) selbst hab ich bei mir unter einem Pfad(/home/.../scripts abgelegt und einen Datei-Link(File>New>File>Advanced>Link to file in the file system) in Eclipse definiert. Somit kann das Skript aus Eclipse mit Syntax-Highlighter editieren werden.

    Hier ist die Ausgabe nach dem ich das Skript, während die Anwendung schläft(Thread.sleep), bearbeitet habe.
    Code (Text):
    1.  
    2. performs script: userSpecificScript
    3. loaded script: context.text = "Hello Bubu!"
    4. context: de.kde.tutorials.scalaInterpreter.UserContext = [UserContext text=]
    5. [UserContext text=Hello Bubu!]
    6.  
    7. performs script: userSpecificScript
    8. loaded script: context.text = "Hello Bambi!"
    9. context: de.kde.tutorials.scalaInterpreter.UserContext = [UserContext text=Hello Bubu!]
    10. [UserContext text=Hello Bambi!]
    11.  
    Eine mögliche Verbesserung von dem Bespiel wäre: das Cachen der Classfiles, die der Scala-Compiler beim Interpretieren in einem temporären Verzeichnis(/tmp/scalaint*****/) ablegt. Damit man ein Skript NUR neu interpretiert, sobald es sich ändert und nicht ständig den Interpreter bei größeren Skripten quälen muss.


    Skript:

    Code (Java):
    1.  
    2. context.text = "Hello Bubu!"
    3.  
    Geändertes Skript:

    Code (Java):
    1.  
    2. context.text = "Hello Bambi!"
    3.  
    Code:

    Code (Java):
    1.  
    2. package de.kde.tutorials.scalaInterpreter
    3.  
    4. import scala.tools.nsc._
    5. import java.io._
    6.  
    7. /*
    8.  * [USER=136855]@author[/USER] konstantin.denerz
    9.  */
    10. object InterpreterExample {
    11.  
    12.   def main(args : Array[String]) : Unit = {
    13.  
    14.     // define user context
    15.     var context = new UserContext
    16.    
    17.     // define use case
    18.     var useCase = new UseCase
    19.    
    20.     // run the process
    21.     useCase.run(context)
    22.    
    23.     // show the changed context
    24.     println(context)
    25.    
    26.     // sleep during script manipulation
    27.     Thread sleep 5000
    28.     useCase.run(context)
    29.     println(context)
    30.    
    31.    
    32.   }
    33.  
    34. }
    35.  
    36. /*
    37.  * the context used to run a process
    38.  */
    39. trait IContext {}
    40.  
    41. /*
    42.  * User context
    43.  */
    44. class UserContext extends IContext{
    45.   var _text = ""
    46.   // getter
    47.   def text = {_text}
    48.   // setter
    49.   def text_=(theText: String) {
    50.     _text = theText
    51.   }
    52.  
    53.   override def toString() : String = { "[UserContext text=" + text + "]" }
    54.  
    55. }
    56.  
    57.  
    58. trait IProcess {
    59.   var subProcesses = Map[String, IProcess]()
    60.  
    61.   def run(context: IContext) {
    62.     // run all scripts for this use case
    63.     subProcesses.keys.foreach(key =>
    64.       {
    65.         println("performs script: " + key)
    66.         subProcesses(key).run(context)
    67.       }
    68.     )
    69.   }
    70.  
    71. }
    72.  
    73. /*
    74.  * Interface for all scripts
    75.  */
    76. trait IScript extends IProcess {
    77.   /*
    78.    * path to directory which contains the scala script files
    79.    */
    80.   val scriptsDirectory = "/home/kde/private/projects/example/scripts"
    81.   // settings for the scala interpreter & compiler
    82.   val settings = new Settings(null)
    83.   var interpreter : Interpreter = _
    84.  
    85.   /*
    86.    * create a new instance of scala interpreter
    87.    */
    88.   def createInterpreter() {
    89.     settings.classpath.value = List(settings.classpath.value,"")
    90.       .mkString("",java.io.File.separator,"")
    91.     interpreter = new Interpreter(settings)
    92.   }
    93.  
    94.   /*
    95.    * read the script from file
    96.    */
    97.   def getScript() : String = {
    98.     var result = ""
    99.     // get the class name for instance :  Bubu
    100.     var className = this.getClass.getSimpleName
    101.     // read scala file
    102.     var file = new File(List(scriptsDirectory, className)
    103.                           .mkString("", "/", ".scala"))
    104.     var reader = new BufferedReader(new FileReader(file))
    105.     try{
    106.       var line = ""
    107.       do{
    108.         line = reader.readLine
    109.         if(null != line){
    110.           result+=line
    111.         }
    112.       }while(line != null)
    113.     }catch{
    114.       case e : Throwable => e.printStackTrace
    115.       case _=>
    116.     }
    117.    
    118.     println("loaded script: " + result)
    119.     result
    120.   }
    121.  
    122.   override def run(context: IContext) {
    123.     var script = getScript
    124.     // make the context reachable for the script
    125.     interpreter.bind("context", context.getClass.getName, context)
    126.     // interpret the script
    127.     interpreter.interpret(script)
    128.    
    129.     super.run(context)
    130.   }
    131.  
    132. }
    133.  
    134. class UseCase extends IProcess {
    135.  
    136.   subProcesses = Map("userSpecificScript" -> new UserSpecificScript)
    137.  
    138.   override def run(context: IContext) {
    139.     // ...
    140.    
    141.     super.run(context)
    142.   }
    143.  
    144. }
    145.  
    146.  
    147.  
    148. class UserSpecificScript extends IScript{
    149.   createInterpreter()
    150.  
    151.   override def run(context: IContext) {
    152.     // ...
    153.    
    154.     // run the script
    155.     super.run(context)
    156.   }
    157.  
    158. }
    159.  
    160.  
    Gruß Konstantin
    Zuletzt von einem Moderator bearbeitet: 23. Mai 2014
  2. Thomas Darimont

    Thomas Darimont Administrator

    Hallo,

    schickes Beispiel ;-) Im Prinzip ist hier der springende Punkt das man beliebige Objekte an den Scala Interpreter binden und somit eine "Kommunikation" zwischen Script Umgebung und "äußerer" Laufzeit möglich ist.

    Man siehe auch:
    http://www.jugsaar.de/?p=131

    Hier noch ein Beispiel wie man von Java aus ein Objekt an ein Scala Script übergeben kann:

    Hier unser scala Script in einem separeten Scala Projekt:
    Code (Text):
    1.  
    2. package de.tutorials.scala2
    3.  
    4. object Test {
    5.   def main(args:Array[String])={
    6.     println(args.toList)
    7.     args(0)="Scala"
    8.   }
    9. }
    10.  
    11.  
    Hier rufen wir das Scala Script aus einer java Anwendung aus auf:
    (Hier rufe ich eine Methode einer schon kompilierten Scala Klasse auf. Die entsprechenden .class Files müssen natürlich auch im Classpath liegen, bei mir habe ich dazu den Class Folder (bin) des entsprechenden Scalaprojektes im Java Buildpath des Javaprojektes eingebunden)
    Code (Java):
    1.  
    2. /**
    3.  *
    4.  */
    5. package de.tutorials;
    6.  
    7. import scala.tools.nsc.Interpreter;
    8. import scala.tools.nsc.Settings;
    9.  
    10. /**
    11.  * @author Thomas.Darimont
    12.  *
    13.  */
    14. public class ScalaInterpreterExample {
    15.  
    16.     /**
    17.      * @param args
    18.      */
    19.     public static void main(String[] args) throws Exception {
    20.         Settings settings = new Settings();
    21.         System.out.println(settings.classpath());
    22.  
    23.  
    24.         Interpreter interpreter = new Interpreter(settings);
    25.  
    26.         String[] context = { "FOO" };
    27.         interpreter.bind("context", "Array[String]", context);
    28.         interpreter
    29.                 .interpret("de.tutorials.scala2.Test.main(context)");
    30.        
    31.         context[0] = "BAR";
    32.        
    33.         interpreter
    34.                 .interpret("de.tutorials.scala2.Test.main(context)");
    35.         interpreter
    36.                 .interpret("binder0.getClass.getProtectionDomain.getCodeSource.getLocation");
    37.        
    38.         System.out.println(context[0]);
    39.     }
    40. }
    41.  
    42.  
    Ausgabe:
    Code (Text):
    1.  
    2. StringSetting(-classpath,path,Specify where to find user class files,D:\eclipse\workspaces\workspace-3.4\de.tutorials.training\bin;D:\stuff\jtds\jtds-1.2.2.jar;D:\stuff\neo4j\neo-1.0-b6\neo-1.0-b6.jar;D:\stuff\neo4j\neo-1.0-b6\jta-spec1_0_1.jar;D:\eclipse\3.4\eclipse\plugins\org.eclipse.team.svn_0.7.1.I20080612-1500.jar;D:\eclipse\3.4\eclipse\plugins\org.eclipse.team.svn.core_0.7.1.I20080612-1500.jar;D:\eclipse\3.3M7\eclipse\plugins\org.polarion.team.client.javasvn.standard.core_1.1.2\lib\svnkit.jar;D:\eclipse\3.3M7\eclipse\plugins\org.polarion.team.client.javasvn.standard.core_1.1.2\lib\svnkit-javahl.jar;D:\stuff\log4j\1.2\apache-log4j-1.2.15\log4j-1.2.15.jar;D:\stuff\scala\2.7.1\scala-2.7.1.final\lib\scala-library.jar;D:\stuff\scala\2.7.1\scala-2.7.1.final\lib\sbaz.jar;D:\stuff\scala\2.7.1\scala-2.7.1.final\lib\sbaz-tests.jar;D:\stuff\scala\2.7.1\scala-2.7.1.final\lib\scala-compiler.jar;D:\stuff\scala\2.7.1\scala-2.7.1.final\lib\scala-dbc.jar;D:\stuff\scala\2.7.1\scala-2.7.1.final\lib\scala-decoder.jar;D:\eclipse\workspaces\workspace-3.4\de.tutorials.scala2\bin)
    3. context: Array[String] = Array(FOO)
    4. List(FOO)
    5. List(BAR)
    6. res2: java.net.URL = file:/C:/DOKUME~1/THOMAS~1.DAR/LOKALE~1/Temp/scalaint47770/
    7. Scala
    8.  
    Gruß Tom
  3. Hallo,

    hier eine kleine Erweiterung des Beispiels. Dabei werden zwar nicht die Classfiles gecachet, jedoch wird verhindert, dass ein Skript jedes mal neu interpretiert/kompiliert wird, bis man es wünscht.

    hier das geänderte Trait:
    Code (Java):
    1.  
    2. /*
    3.  * Interface for all scripts
    4.  */
    5. trait IScript extends IProcess {
    6.  
    7.   // ....
    8.  
    9.   /*
    10.    * read the script
    11.    */
    12.   def getScript() : String = {
    13.     var result = ""
    14.     // get the class name for instance :  Bubu
    15.     var className = this.getClass.getSimpleName
    16.     // read scala file
    17.     var file = new File(List(scriptsDirectory, className)
    18.                           .mkString("", "/", ".scala"))
    19.    
    20.     if(isDirty){
    21.       result = getScriptRunner + System.getProperty("line.separator") + getScriptFrom(file)
    22.     }else{
    23.     // run the runner only
    24.       result = getScriptRunner()
    25.     }
    26.    
    27.     println("loaded script: " + result)
    28.     result
    29.   }
    30.  
    31.  
    32.   override def run(context: IContext) {
    33.     var script = getScript
    34.     // make the context reachable for the script
    35.     interpreter.bind("context", context.getClass.getName, context)
    36.     // interpret the script
    37.     interpreter.interpret(script)
    38.    
    39.     super.run(context)
    40.    
    41.     isDirty = false
    42.    
    43.   }
    44.  
    45.   var isDirty = true
    46.  
    47.   def getScriptRunner() = {
    48.     "Runner.run"
    49.   }
    50.  
    51.   def getScriptFrom(file: File) = {
    52.     var result = ""
    53.     var reader = new BufferedReader(new FileReader(file))
    54.     try{
    55.      
    56.       var line = ""
    57.       do{
    58.         line = reader.readLine
    59.         if(null != line){
    60.           result += line + System.getProperty("line.separator")
    61.         }
    62.       }while(line != null)
    63.        
    64.     }catch{
    65.       case e : Throwable => e.printStackTrace
    66.       case _=>
    67.     }
    68.     result
    69.   }
    70.  
    71. }
    72.  

    Und hier das neue Skript. Dabei ist eine Klasse(MyScript) zu sehen, die eine Methode zum Manipulieren des Kontexts hat. Wenn der Interpreter dieses Skript einmal interpretiert (kompiliert), dann werden die Classfiles dafür generiert. Solange das Skript sich nicht ändert, kann man den Runner zum Ausführen des Skript verwenden. In run Methode des IScript traits wird dann statt dem ganzen Skript, einfach nur der Aufruf Runner.run interpretiert. Somit ruft immer das gleiche Skript, ohne es neu zu kompilieren, auf.

    UserSpecificScript.scala:
    Code (Java):
    1.  
    2. object Runner{
    3.   def run = {
    4.     var script = new MyScript
    5.     script.changeContext(context)
    6.   }
    7. }
    8.  
    9. import de.kde.tutorials.scalaInterpreter._
    10.  
    11. class MyScript{
    12.  
    13.   def changeContext(theContext : IContext) = {
    14.      theContext.asInstanceOf[UserContext].text = "Hello Bambi!"
    15.   }
    16.  
    17. }
    18.  
    Hier die Ausgabe von dem Ganzen, woran man erkennt, dass beim zweiten Aufruf nur der Context nochmal an den Interpreter gebunden und der Runner ausgeführt wird, ohne das module Runner und MySkript neu definiert (kompiliert) werden.

    Code (Text):
    1.  
    2. performs script: userSpecificScript
    3. loaded script: Runner.run
    4. object Runner{
    5.   def run = {
    6.     var script = new MyScript
    7.     script.changeContext(context)
    8.   }
    9. }
    10.  
    11. import de.kde.tutorials.scalaInterpreter._
    12.  
    13. class MyScript{
    14.  
    15.   def changeContext(theContext : IContext) = {
    16.      theContext.asInstanceOf[UserContext].text = "Hello Bambi!"
    17.   }
    18.  
    19. }
    20.  
    21. context: de.kde.tutorials.scalaInterpreter.UserContext = [UserContext text=]
    22. defined module Runner
    23. import de.kde.tutorials.scalaInterpreter._
    24. defined class MyScript
    25. [UserContext text=Hello Bambi!]
    26.  
    27.  
    28.  
    29.  
    30.  
    31. performs script: userSpecificScript
    32. loaded script: Runner.run
    33. context: de.kde.tutorials.scalaInterpreter.UserContext = [UserContext text=Hello Bambi!]
    34. [UserContext text=Hello Bambi!]
    35.  

    Gruß Konstantin
    Zuletzt von einem Moderator bearbeitet: 23. Mai 2014

Diese Seite empfehlen