├── .gitignore ├── Licence.txt ├── README ├── Readme.md ├── build.sbt ├── examplefiles ├── constructors │ └── my │ │ └── User.scala └── simple │ └── my │ └── TryMe.scala ├── project └── plugins.sbt ├── src ├── main │ ├── java │ │ └── .gitignore │ └── scala │ │ └── com │ │ └── googlecode │ │ ├── concurrent │ │ ├── ExecutorServiceManager.scala │ │ └── LockManager.scala │ │ └── scalascriptengine │ │ ├── CodeVersion.scala │ │ ├── CompilationStatus.scala │ │ ├── Config.scala │ │ ├── Constructors.scala │ │ ├── DevUseIDECompiledClassesOnly.scala │ │ ├── EvalCode.scala │ │ ├── FromClasspathFirst.scala │ │ ├── Logging.scala │ │ ├── RefreshPolicies.scala │ │ ├── SSESecurityManager.scala │ │ ├── ScalaScriptEngine.scala │ │ ├── classloading │ │ ├── ClassLoaderConfig.scala │ │ ├── ClassRegistry.scala │ │ └── ScalaClassLoader.scala │ │ └── internals │ │ ├── CompilerManager.scala │ │ ├── LastModMap.scala │ │ └── Utils.scala └── test │ └── scala │ ├── Test.scala │ ├── com │ └── googlecode │ │ └── scalascriptengine │ │ ├── ClassRegistrySuite.scala │ │ ├── CompilationSuite.scala │ │ ├── ConstructorsSuite.scala │ │ ├── DevUseIDECompiledClassesOnlySuite.scala │ │ ├── EndToEndSuite.scala │ │ ├── EnhancersSuite.scala │ │ ├── EvalCodeSuite.scala │ │ ├── OnChangeRefreshPolicySuite.scala │ │ ├── ResourcesSuite.scala │ │ ├── SandboxAllowOnlySuite.scala │ │ ├── SandboxSuite.scala │ │ ├── StressTestPermGen.scala │ │ ├── TestClassTrait.scala │ │ ├── TimedRefreshPolicySuite.scala │ │ ├── internals │ │ └── ScalaClassLoaderSuite.scala │ │ └── package.scala │ ├── examples │ ├── CompileChangesButLoadThemWhenReady.scala │ ├── FullBlown.scala │ ├── InstantiatingWithConstructorArgs.scala │ ├── LoadsChangesImmediatelly.scala │ ├── TimedRefresh.scala │ └── TryMeTrait.scala │ └── test │ ├── FromClasspathFirst.scala │ └── Test.scala ├── testfiles ├── CompilationSuite │ └── test │ │ ├── Dep1.scala │ │ ├── Dep2.scala │ │ └── MyClass.scala ├── CompilationSuite1 │ └── A.scala ├── CompilationSuite2 │ └── B.scala ├── FromClasspathFirst │ └── test │ │ ├── FCF.scala │ │ └── FromClasspathFirst.scala ├── ResourcesSuite │ ├── v1 │ │ └── reload │ │ │ ├── Main.scala │ │ │ └── version.txt │ └── v2 │ │ └── reload │ │ ├── Main.scala │ │ └── version.txt ├── SandboxAllowOnlySuite │ └── test │ │ ├── TryPackage.scala │ │ └── TryPackageAllow.scala ├── SandboxSuite │ ├── test.policy │ └── test │ │ ├── A.scala │ │ ├── TryFile.scala │ │ ├── TryHome.scala │ │ ├── TryPackage.scala │ │ └── TryThread.scala ├── ScalaClassLoaderSuite │ ├── default │ │ └── Test.class │ ├── v1 │ │ └── test │ │ │ ├── Test.class │ │ │ ├── TestDep.class │ │ │ └── TestParam.class │ └── v2 │ │ └── test │ │ ├── Test.class │ │ ├── TestDep.class │ │ └── TestParam.class ├── erroneous │ └── ve │ │ └── reload │ │ └── Reload.scala ├── src1 │ └── test │ │ └── A.scala ├── src2 │ └── test │ │ └── B.scala ├── src3 │ └── test │ │ └── A.scala └── versions │ ├── v1 │ └── reload │ │ └── Reload.scala │ └── v2 │ └── reload │ └── Reload.scala └── wiki ├── Downloads.md └── News.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.cache 2 | /target 3 | .idea 4 | *.iml 5 | tmp.pom 6 | *~ 7 | -------------------------------------------------------------------------------- /Licence.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018 Konstantinos Kougios. Licensed under the Apache License, Version 2.0 2 | (the "License"); you may not use this file except in compliance with the License. 3 | You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | Unless required by applicable law or agreed to in writing, software distributed under 5 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 6 | ANY KIND, either express or implied. See the License for the specific language governing 7 | permissions and limitations under the License. 8 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | For queries/bug reports, please contact kostas.kougios@googlemail.com 2 | 3 | --- Only for maintainers --- 4 | 5 | to release: 6 | 7 | sbt 8 | publishSigned 9 | (and then close & publish the artifact @ oss.sonatype.org) 10 | 11 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ### Description ### 2 | This library dynamically compiles scala source files and loads them as classes. Changed scala files will be recompiled and the changed class with be loaded. Multiple source paths are supported as well as compilation class path and class loading class paths (so that the scripts can load extra libraries). 3 | 4 | Classpath detection can be automatic (effectively using the classpath of the caller) or manual. 5 | 6 | Different compilation and refreshing strategies are provided to suit various purposes. 7 | 8 | ### News ### 9 | * 14/06/2017 : v1.3.11 for scala 2.12 is now available 10 | * 08/06/2014 : v1.3.10 for scala 2.11 is now available, fixing the artifactId for sbt and scala version 11 | * 15/05/2014 : v1.3.9 for scala 2.11 is now available. 12 | * 05/03/2014 : v1.3.9 is available, bug fixes for Eval, and now eval is capable of taking arguments with generics. Also source paths can now be a set of files instead of directories. 13 | * 26/01/2014 : v1.3.8 is available, dropped dependency to time\_2.9.1 and fixed compilation issue within eclipse. 14 | * 21/10/2013 : v1.3.7 is available with optimizations during class loading 15 | * 06/10/2013 : v1.3.6 is available with class registry and for scala 2.10.3 16 | * 23/07/2013 : v1.3.5 is available with error reporting fixes and only for scala 2.10.2 17 | * 30/06/2013 : v1.3.4 is available, with error reporting fixes and only for scala 2.10.2 18 | * 24/04/2013 : v1.3.2 is available with class loading listeners 19 | * 02/03/2013 : v1.3.1 is available with better error reporting 20 | * 20/02/2013 : v1.3.0 is available with better support for using ScalaScriptEngine within an IDE and multiple target class folders. 21 | * 09/02/2013 : v1.2.1 is now available with better compilation error reporting and sbt compatibility fix. 22 | * 23/01/2013 : snapshots of v1.2.1 are available in sonatype snapshot repo. Those fix sbt issues. 23 | * 12/01/2013 : v1.2.0 for scala 2.10.0 is now available 24 | * 16/10/2012 : v1.2.0 : Sandbox, better eval() and compilation for scala 2.9.2 and 2.10.0-M7 . For Sandbox please look at the end of this page. 25 | * 25/08/2012 : v1.1.0 : this has support for evaluating scala code from a String. 26 | * 22/07/2012 : migrated to git 27 | * 19/07/2012 : v1.0.0 : v0.6.4 is promoted to v1.0.0 28 | [more...](wiki/News.md) 29 | 30 | ### Examples ### 31 | 32 | [Please click to view examples](https://github.com/kostaskougios/scalascriptengine/tree/master/src/test/scala/examples) 33 | 34 | [eval(): Evaluating scala code from a String](https://github.com/kostaskougios/scalascriptengine/blob/master/src/test/scala/com/googlecode/scalascriptengine/EvalCodeSuite.scala) 35 | 36 | ### Discuss ### 37 | 38 | at [http://groups.google.com/group/scala-script-engine](http://groups.google.com/group/scala-script-engine) 39 | 40 | ### Download ### 41 | 42 | Please use the maven repository to download the required jar, sources and javadocs : 43 | [Download](https://oss.sonatype.org/content/repositories/releases/com/googlecode/scalascriptengine/scalascriptengine/) 44 | 45 | ### sbt ### 46 | 47 | ``` 48 | "com.googlecode.scalascriptengine" %% "scalascriptengine" % "1.3.11", 49 | "org.scala-lang" % "scala-compiler" % "2.12.2" 50 | 51 | ``` 52 | 53 | NOTE: add 54 | 55 | fork:=true 56 | 57 | to your build.sbt because sbt seems to create issues with the scala compiler. 58 | 59 | ### Maven ### 60 | 61 | Both scalascriptengine, scala-reflect and scala-compiler must be added as dependencies : 62 | 63 | ``` 64 | 65 | com.googlecode.scalascriptengine 66 | scalascriptengine_${scala.version} 67 | 1.3.10 68 | 69 | 70 | org.scala-lang 71 | scala-compiler 72 | ${scala.version} 73 | 74 | 75 | org.scala-lang 76 | scala-reflect 77 | ${scala.version} 78 | 79 | ``` 80 | 81 | Please add the sonatype releases repository to your repositories: 82 | 83 | ``` 84 | 85 | 86 | sonatype.releases 87 | https://oss.sonatype.org/content/repositories/releases/ 88 | 89 | 90 | ``` 91 | 92 | ### Usage ### 93 | 94 | This is not the most efficient usage of the library, but is the one with the most expected behavior: 95 | 96 | ``` 97 | // sourceDir is the folder with the scala source files 98 | val sse = ScalaScriptEngine.onChangeRefresh(sourceDir) 99 | // get the class which should extend statically 100 | // compiled trait ClzTrait 101 | val clzTraitClass=sse.get[ClzTrait]("my.dynamic.Clz") 102 | // or get a new instance 103 | val clzTrait=sse.newInstance[ClzTrait]("my.dynamic.Clz") 104 | ``` 105 | 106 | Please note that scala classes that are going to be requested from ScalaScriptEngine, should be declared in a synonymous scala file, i.e. my.Foo should be under my/Foo.scala in order for change detection to work. 107 | 108 | ### Avoiding compilation during development ### 109 | 110 | The engine can be configured to use classes as they are compiled by an IDE. 111 | 112 | ``` 113 | val sse=if(is running for production) 114 | ...normal script engine initialization for production env 115 | else 116 | new ScalaScriptEngine(Config(sourcePaths = List( 117 | SourcePath(...source folder, i.e. src/main/scala..., ... existing class folder, i.e. target/classes) 118 | ))) with DevUseIDECompiledClassesOnly 119 | ``` 120 | 121 | Now scripts can be recompiled within the IDE and, without restarting your java app, the compiled classes will be reloaded on every sse.get or sse.newInstance. 122 | 123 | NOTE: this frequently throws away a classloader and it is not recommended for production as it is slow and will cause a PermGen issue. But it is very handy during development. 124 | 125 | ### Examples ### 126 | 127 | [Please click to view examples](https://github.com/kostaskougios/scalascriptengine/tree/master/src/test/scala/examples) 128 | 129 | ### How does it work ### 130 | 131 | The ScalaScriptEngine class works by keeping versions of compiled source directories. Version 1 can be loaded during initialization of the engine or during the request for the first script. After that, there are different policies to refresh the changed source files: 132 | 133 | * **manual** : the client of the engine manually calls ScalaScriptEngine.refresh to check & recompile changed classes in the source directories. 134 | 135 | * **on-change-refresh** : as soon as the src file for a requested class changes, the source dirs are recompiled (only changed files). The code requesting for the changed class blocks till compilation completes 136 | 137 | * **on-change-refresh-async**: as soon as the src file for a requested class changes, the source dirs are recompiled (only changed files). The code requesting for the changed class resumes execution but uses an old version of the class till compilation completes. This method scales up better for i.e. servers that need to process hundreds of requests per second and blocking till compilation completes is not an option. 138 | 139 | * **timed refresh**: a background thread periodically scans the source folders for changes and recompiles them. During recompilation, old version classes are returned by the engine but as soon as compilation completes the new version classes are used. 140 | 141 | In case of compilation errors, the previous version remains in use. 142 | 143 | ### Sandbox ### 144 | 145 | Please view the test suites: 146 | 147 | * [policy file](https://github.com/kostaskougios/scalascriptengine/blob/master/src/test/scala/com/googlecode/scalascriptengine/SandboxSuite.scala) 148 | * [Example 2](https://github.com/kostaskougios/scalascriptengine/blob/master/src/test/scala/com/googlecode/scalascriptengine/SandboxAllowOnlySuite.scala) 149 | 150 | 151 | ScalaScriptEngine can be configured to work with a Java sandbox and in addition offers extra help in terms of SecureManager and limited classloading for scripts. 152 | 153 | ### Sandbox and SecureManager ### 154 | 155 | create a policy file: 156 | ``` 157 | grant codeBase "file:${user.home}/-" { 158 | permission java.security.AllPermission; 159 | }; 160 | 161 | grant codeBase "${script.classes}/-" { 162 | permission java.io.FilePermission "/home","read"; 163 | }; 164 | ``` 165 | 166 | Register a SecurityManager with the help of SSESecurityManager: 167 | ``` 168 | import com.googlecode.scalascriptengine._ 169 | 170 | // create the default config with a Source Dir. The temp directory where 171 | // the compiled classes are stored is in the OS tmp folder. 172 | val config = ScalaScriptEngine.defaultConfig(sourceDir) 173 | // We are now going to create a security manager with the test.policy 174 | // file. We need to fill the placeholders of test.policy 175 | System.setProperty("script.classes", config.outputDir.toURI.toString) 176 | System.setProperty("java.security.policy", new File("test.policy").toURI.toString) 177 | val sseSM = new SSESecurityManager(new SecurityManager) 178 | System.setSecurityManager(sseSM) 179 | 180 | ``` 181 | 182 | The SSESecurityManager is by default not active. So the rest of the java code will run like if not under a security manager. The SSESecurityManager activates the delegate SecurityManager as follows: 183 | 184 | ``` 185 | 186 | val sse = ScalaScriptEngine.onChangeRefresh(config, 5) 187 | sse.deleteAllClassesInOutputDirectory 188 | sse.refresh 189 | 190 | sseSM.secured { 191 | // now the delegated SecurityManager is active and hence test.policy is active 192 | val tct = sse.newInstance[TestClassTrait]("test.TryFile") 193 | tct.result should be === "directory" 194 | } 195 | ``` 196 | 197 | Please note: SSESecurityManager can be bypassed and a global SecurityManager can be installed for both the main scala app and the scripts. SSESecurityManager is provided as a helper to avoid running all code under a security manager. 198 | 199 | ### Configuring limited access to loaded classes ### 200 | 201 | Scripts can be limited to i.e. not be able to load classes from specific packages. The decision is just a function (packageName,fullClassName)=>Boolean. If true, access to `fullClassName` class is allowed otherwise `AccessControlException` is thrown. 202 | 203 | The following example allows access only to certain packages and i.e. Threads can't be created by the scripts (except ofcourse if one of the allowed packages contains a class that creates threads): 204 | 205 | ``` 206 | val allowedPackages = Set( 207 | "java.lang", 208 | "scala", 209 | "com.googlecode.scalascriptengine") 210 | val config = ScalaScriptEngine.defaultConfig(sourceDir).copy( 211 | classLoaderConfig = ClassLoaderConfig.default.copy( 212 | allowed = { (pckg, name) => 213 | allowedPackages(pckg) || pckg == "test" 214 | } 215 | ) 216 | ) 217 | val sse = ScalaScriptEngine.onChangeRefresh(config, 5) 218 | sse.deleteAllClassesInOutputDirectory 219 | sse.refresh 220 | 221 | val t = sse.newInstance[TestClassTrait]("test.TryPackage") 222 | // if test.TryPackage tries to use a class not in the allowed 223 | // packages, an AccessControlException will be thrown 224 | t.result 225 | 226 | ``` 227 | 228 | Please note this mechanism works independently of a security manager. No security manager is required as this mechanism works during classloading. -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import sbt.Tests.{ Group, SubProcess } 2 | 3 | name := "scalascriptengine" 4 | 5 | organization := "com.googlecode.scalascriptengine" 6 | 7 | version := "1.3.11" 8 | 9 | pomIncludeRepository := { _ => false } 10 | 11 | licenses := Seq("Apache License, Version 2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")) 12 | 13 | homepage := Some(url("https://github.com/kostaskougios/scalascriptengine")) 14 | 15 | scmInfo := Some( 16 | ScmInfo( 17 | url("https://github.com/kostaskougios/scalascriptengine"), 18 | "scm:https://github.com/kostaskougios/scalascriptengine.git" 19 | ) 20 | ) 21 | 22 | developers := List( 23 | Developer( 24 | id = "kostas.kougios@googlemail.com", 25 | name = "Konstantinos Kougios", 26 | email = "kostas.kougios@googlemail.com", 27 | url = url("https://github.com/kostaskougios") 28 | ) 29 | ) 30 | 31 | publishMavenStyle := true 32 | 33 | publishTo := { 34 | val nexus = "https://oss.sonatype.org/" 35 | if (isSnapshot.value) 36 | Some("snapshots" at nexus + "content/repositories/snapshots") 37 | else 38 | Some("releases" at nexus + "service/local/staging/deploy/maven2") 39 | } 40 | 41 | scalaVersion := "2.12.2" 42 | 43 | libraryDependencies ++= Seq( 44 | "commons-io" % "commons-io" % "2.1" % Test, 45 | "org.slf4j" % "slf4j-api" % "1.6.4", 46 | "ch.qos.logback" % "logback-classic" % "1.0.0", 47 | "org.scala-lang" % "scala-reflect" % "2.12.2", 48 | "org.scalatest" %% "scalatest" % "3.0.3", 49 | "org.scala-lang" % "scala-compiler" % "2.12.2", 50 | "joda-time" % "joda-time" % "2.9.9" 51 | ) 52 | 53 | // fork in test cause there are conflicts with sbt classpath 54 | def forkedJvmPerTest(testDefs: Seq[TestDefinition]) = testDefs.groupBy( 55 | test => test.name match { 56 | case "com.googlecode.scalascriptengine.SandboxSuite" => 57 | test.name 58 | case _ => "global" 59 | } 60 | ).map { case (name, tests) => 61 | Group( 62 | name = name, 63 | tests = tests, 64 | runPolicy = SubProcess(ForkOptions()) 65 | ) 66 | }.toSeq 67 | 68 | //definedTests in Test returns all of the tests (that are by default under src/test/scala). 69 | testGrouping in Test <<= (definedTests in Test) map forkedJvmPerTest 70 | 71 | testOptions in Test += Tests.Argument("-oF") 72 | -------------------------------------------------------------------------------- /examplefiles/constructors/my/User.scala: -------------------------------------------------------------------------------- 1 | package my 2 | 3 | case class User(val name: String, val age: Int) extends UserTrait 4 | -------------------------------------------------------------------------------- /examplefiles/simple/my/TryMe.scala: -------------------------------------------------------------------------------- 1 | package my 2 | 3 | class TryMe extends TryMeTrait 4 | { 5 | val r = "%d : change me while example runs!" 6 | 7 | override def result = { 8 | import TryMe._ 9 | counter += 1 10 | r.format(counter) 11 | } 12 | } 13 | 14 | object TryMe 15 | { 16 | var counter = 0 17 | } -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") 2 | 3 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "1.1") 4 | 5 | -------------------------------------------------------------------------------- /src/main/java/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kostaskougios/scalascriptengine/1e2202b601eb2d44096af78f47aa4f4670f168f7/src/main/java/.gitignore -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/concurrent/ExecutorServiceManager.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.concurrent 2 | 3 | import java.util.concurrent._ 4 | 5 | import org.joda.time.DateTime 6 | 7 | /** 8 | * manages executor instantiation, provides factory methods 9 | * for various executors 10 | * 11 | * @author kostantinos.kougios 12 | * 13 | * 15 Nov 2011 14 | */ 15 | object ExecutorServiceManager 16 | { 17 | 18 | def wrap(executor: ExecutorService) = new Executor with Shutdown 19 | { 20 | protected val executorService = executor 21 | } 22 | 23 | def newSingleThreadExecutor = new Executor with Shutdown 24 | { 25 | protected val executorService = Executors.newSingleThreadExecutor 26 | } 27 | 28 | def newCachedThreadPool( 29 | corePoolSize: Int, 30 | maximumPoolSize: Int, 31 | keepAliveTimeInSeconds: Int = 60, 32 | workQueue: BlockingQueue[Runnable] = new SynchronousQueue 33 | ) = 34 | new Executor with Shutdown 35 | { 36 | override protected val executorService = new ThreadPoolExecutor( 37 | corePoolSize, 38 | maximumPoolSize, 39 | keepAliveTimeInSeconds, 40 | TimeUnit.SECONDS, 41 | workQueue) 42 | } 43 | 44 | def newCachedThreadPoolCompletionService[V]( 45 | corePoolSize: Int, 46 | maximumPoolSize: Int, 47 | keepAliveTimeInSeconds: Int = 60, 48 | workQueue: BlockingQueue[Runnable] = new SynchronousQueue 49 | ) = 50 | new CompletionExecutor[V]( 51 | new ThreadPoolExecutor( 52 | corePoolSize, 53 | maximumPoolSize, 54 | keepAliveTimeInSeconds, 55 | TimeUnit.SECONDS, 56 | workQueue) 57 | ) 58 | 59 | def newScheduledThreadPool(corePoolSize: Int, errorLogger: Throwable => Unit) = 60 | new Executor with Shutdown with Scheduling 61 | { 62 | override protected val executorService = new ScheduledThreadPoolExecutor(corePoolSize) 63 | override val onError = errorLogger 64 | } 65 | 66 | def newScheduledThreadPool(corePoolSize: Int) = 67 | new Executor with Shutdown with Scheduling 68 | { 69 | override protected val executorService = new ScheduledThreadPoolExecutor(corePoolSize) 70 | override val onError = (t: Throwable) => t.printStackTrace 71 | } 72 | 73 | def newFixedThreadPool(nThreads: Int) = 74 | new Executor with Shutdown 75 | { 76 | override protected val executorService = Executors.newFixedThreadPool(nThreads) 77 | } 78 | 79 | def newFixedThreadPoolCompletionService[V](nThreads: Int) = 80 | new CompletionExecutor[V](Executors.newFixedThreadPool(nThreads)) 81 | 82 | /** 83 | * creates an executor of nThread, submits f() x times and returns V x times 84 | * as returned by f(). It then shutsdown the executor. 85 | * 86 | * f: Int => V , where Int is the i-th execution, i is between [1..times] 87 | * inclusive. 88 | * 89 | * If any of the invocation of f() fails, the executor will be shut down 90 | * and no further threads will be submitted to it. The exception will propagate 91 | * to the caller. 92 | */ 93 | def lifecycle[V](nThreads: Int, times: Int)(f: Int => V): Seq[V] = { 94 | val pool = newFixedThreadPool(nThreads) 95 | try { 96 | val seq = for (i <- 1 to times) yield pool.submit(f(i)) 97 | seq.map(_.get) 98 | } finally { 99 | pool.shutdown 100 | } 101 | } 102 | 103 | /** 104 | * creates an executor of nThread, submits f() x params.size and returns V x params.size 105 | * as returned by f(). It then shutsdown the executor. 106 | * 107 | * f: T => V , each thread getting a different parameter from the traversable 108 | * 109 | * If any of the invocation of f() fails, the executor will be shut down 110 | * and no further threads will be submitted to it. The exception will propagate 111 | * to the caller. 112 | */ 113 | def lifecycle[T, V](nThreads: Int, params: Traversable[T])(f: T => V): Traversable[V] = { 114 | val pool = newFixedThreadPool(nThreads) 115 | try { 116 | val results = params.map(param => pool.submit(f(param))) 117 | results.map(_.get) 118 | } finally { 119 | pool.shutdown 120 | } 121 | } 122 | } 123 | 124 | /** 125 | * wrapper for the ExecutorService 126 | * 127 | * new Executor with Shutdown 128 | */ 129 | abstract class Executor 130 | { 131 | // ideally the underlying executor should not be accessible 132 | protected val executorService: ExecutorService 133 | 134 | /** 135 | * submits a task for execution and returns a Future. 136 | * 137 | * example: 138 | * 139 | * val future=executor.submit { 140 | * // will run on a separate thread soon 141 | * 25 142 | * } 143 | * ... 144 | * val result = future.get // result=25 145 | * 146 | */ 147 | def submit[R](f: => R) = executorService.submit(new Callable[R] 148 | { 149 | def call = f 150 | }) 151 | 152 | def submit[V](task: Callable[V]) = executorService.submit(task) 153 | 154 | def submit(task: Runnable) = executorService.submit(task) 155 | } 156 | 157 | /* 158 | * new Executor with Scheduling with Shutdown 159 | */ 160 | trait Scheduling 161 | { 162 | protected val executorService: ScheduledExecutorService 163 | val onError: Throwable => Unit 164 | 165 | /** 166 | * schedules a task to run in the future 167 | * 168 | * example: 169 | * 170 | * 171 | * val future=schedule(100,TimeUnit.MILLISECONDS) { 172 | * // to do in 100 millis from now 173 | * } 174 | * ... 175 | * val result=future.get 176 | * 177 | */ 178 | def schedule[R](delay: Long, unit: TimeUnit)(f: => R): ScheduledFuture[R] = executorService.schedule(new Callable[R] 179 | { 180 | def call = f 181 | }, delay, unit) 182 | 183 | /** 184 | * schedule a task to run in the future. 185 | * 186 | * example: 187 | * 188 | * usage: val future=schedule(DateTime.now + 2.days) { 189 | * // to do in 2 days from now 190 | * } 191 | * 192 | */ 193 | def schedule[R](runAt: DateTime): (=> R) => ScheduledFuture[R] = { 194 | val dt = runAt.getMillis - System.currentTimeMillis 195 | if (dt < 0) throw new IllegalArgumentException("next run time is in the past : %s".format(runAt)) 196 | schedule(dt, TimeUnit.MILLISECONDS) _ 197 | } 198 | 199 | /** 200 | * runs a task periodically. The task initially runs on firstTime. The result R is then 201 | * used to call process(R) and if that returns a new DateTime, the task will be executed 202 | * again on that time. If process(R) returns None, the task won't be executed again. 203 | * 204 | * This method returns straight away, any processing occurs on separate threads using 205 | * the executor. 206 | * 207 | * If the task throws an exception, the onError function will be called to log 208 | * the error (by default it prints the stacktrace to the console) 209 | * 210 | * If the process throws an exception, the scheduling of the task will stop. 211 | * 212 | * @param firstRun DateTime of the first run, i.e. DateTime.now + 2.seconds 213 | * @param process a function to process the result and specify the next 214 | * time the task should run. The value is calculated after f is 215 | * executed and if None the task will not be executed anymore. 216 | * @param f the task 217 | */ 218 | def runPeriodically[R](firstRun: DateTime, process: Option[R] => Option[DateTime])(f: => R): Unit = 219 | schedule(firstRun) { 220 | val r = try { 221 | Some(f) 222 | } catch { 223 | case e: Throwable => 224 | onError(e) 225 | None 226 | } 227 | process(r) match { 228 | case Some(nextRun) => runPeriodically(nextRun, process)(f) 229 | case None => 230 | } 231 | } 232 | 233 | /** 234 | * periodically runs f, starting on firstRun and repeating according to 235 | * the calculated "process" value. 236 | * 237 | * example: 238 | * 239 | * 240 | * import org.scala_tools.time.Imports._ 241 | * 242 | * val executorService = ExecutorServiceManager.newScheduledThreadPool(5) 243 | * 244 | * val start = System.currentTimeMillis 245 | * executorService.runPeriodically(DateTime.now + 50.millis, Some(DateTime.now + 1.second)) { 246 | * // should print dt 6 times, once per second 247 | * println("dt:%d".format(System.currentTimeMillis - start)) 248 | * } 249 | * 250 | * Thread.sleep(5500) 251 | * executorService.shutdownAndAwaitTermination(DateTime.now + 100.millis) 252 | * 253 | * 254 | * 255 | * If the task throws an exception, the onError function will be called to log 256 | * the error (by default it prints the stacktrace to the console) 257 | * 258 | * If the process throws an exception, the scheduling of the task will stop. 259 | * 260 | * @param firstRun DateTime of the first run, i.e. DateTime.now + 2.seconds 261 | * @param whenToReRun a by-value parameter specifying the next time the task should 262 | * run. The value is calculated after f is executed and if None 263 | * the task will not be executed anymore. 264 | * @param f the task 265 | */ 266 | def runPeriodically[R](firstRun: DateTime, whenToReRun: => Option[DateTime])(f: => R): Unit = 267 | schedule(firstRun) { 268 | try { 269 | f 270 | } catch { 271 | case e: Throwable => onError(e) 272 | } 273 | whenToReRun match { 274 | case Some(nextRun) => runPeriodically(nextRun, whenToReRun)(f) 275 | case None => 276 | } 277 | } 278 | } 279 | 280 | /** 281 | * provides shutdown services to Executor 282 | */ 283 | trait Shutdown 284 | { 285 | protected val executorService: ExecutorService 286 | 287 | def shutdown = executorService.shutdown 288 | 289 | def shutdownNow = executorService.shutdownNow 290 | 291 | def awaitTermination(timeout: Long, unit: TimeUnit): Unit = executorService.awaitTermination(timeout, unit) 292 | 293 | def awaitTermination(timeoutWhen: DateTime): Unit = awaitTermination(timeoutWhen.getMillis - System.currentTimeMillis, TimeUnit.MILLISECONDS) 294 | 295 | def shutdownAndAwaitTermination(waitTimeInSeconds: Int) { 296 | shutdown 297 | awaitTermination(waitTimeInSeconds, TimeUnit.SECONDS) 298 | } 299 | 300 | def shutdownAndAwaitTermination(timeoutWhen: DateTime) { 301 | shutdown 302 | awaitTermination(timeoutWhen) 303 | } 304 | } 305 | 306 | /* 307 | * @see CompletionService 308 | */ 309 | class CompletionExecutor[V](protected val executorService: ExecutorService) extends Shutdown 310 | { 311 | private val completionService = new ExecutorCompletionService[V](executorService) 312 | 313 | def submit(f: => V): Future[V] = completionService.submit(new Callable[V] 314 | { 315 | def call = f 316 | }) 317 | 318 | def submit(task: Callable[V]) = completionService.submit(task) 319 | 320 | def submit(task: Runnable, result: V) = completionService.submit(task, result) 321 | 322 | /** 323 | * Retrieves and removes the Future representing the next 324 | * completed task, waiting if none are yet present. 325 | * 326 | * @return the Future representing the next completed task 327 | * @throws InterruptedException if interrupted while waiting 328 | */ 329 | def take: Future[V] = completionService.take 330 | 331 | /** 332 | * Retrieves and removes the Future representing the next 333 | * completed task or None if none are present. 334 | * 335 | * @return the Future representing the next completed task, or 336 | * None if none are present 337 | */ 338 | def poll: Option[Future[V]] = { 339 | val t = completionService.poll 340 | if (t == null) None else Some(t) 341 | } 342 | 343 | /** 344 | * Retrieves and removes the Future representing the next 345 | * completed task, waiting if necessary up to the specified wait 346 | * time if none are yet present. 347 | * 348 | * @param timeout how long to wait before giving up, in units of 349 | * unit 350 | * @param unit a TimeUnit determining how to interpret the 351 | * timeout parameter 352 | * @return the Future representing the next completed task or 353 | * None if the specified waiting time elapses 354 | * before one is present 355 | * @throws InterruptedException if interrupted while waiting 356 | */ 357 | def poll(timeout: Long, unit: TimeUnit): Option[Future[V]] = { 358 | val t = completionService.poll(timeout, unit) 359 | if (t == null) None else Some(t) 360 | } 361 | 362 | /** 363 | * polls, waiting max until the provided DateTime. 364 | */ 365 | def poll(till: DateTime): Option[Future[V]] = pollWaitInMillis(till.getMillis - System.currentTimeMillis) 366 | 367 | def pollWaitInMillis(timeoutMs: Long): Option[Future[V]] = poll(timeoutMs, TimeUnit.MILLISECONDS) 368 | } 369 | -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/concurrent/LockManager.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.concurrent 2 | 3 | import java.util.concurrent.TimeUnit 4 | import java.util.concurrent.locks.{Lock, ReadWriteLock, ReentrantLock, ReentrantReadWriteLock} 5 | 6 | import org.joda.time.DateTime 7 | 8 | /** 9 | * manages locks. 10 | * 11 | * @author kostantinos.kougios 12 | * 13 | * 7 Nov 2011 14 | */ 15 | object LockManager 16 | { 17 | def reentrantLock = new LockEx(new ReentrantLock) 18 | 19 | def readWriteLock = new ReadWriteLockEx(new ReentrantReadWriteLock) 20 | } 21 | 22 | protected class ReadWriteLockEx(val lock: ReadWriteLock) 23 | { 24 | private val readLock = new LockEx(lock.readLock()) 25 | private val writeLock = new LockEx(lock.writeLock()) 26 | 27 | def readLockAndDo[T](f: => T): T = readLock.lockAndDo(f) 28 | 29 | def readLockInterruptiblyAndDo[T](f: => T): T = readLock.lockInterruptiblyAndDo(f) 30 | 31 | def tryReadLockAndDo[T](f: => T): Option[T] = readLock.tryLockAndDo(f) 32 | 33 | def tryReadLockAndDo[T](when: DateTime)(f: => T): Option[T] = 34 | tryReadLockAndDo(when.getMillis - System.currentTimeMillis, TimeUnit.MILLISECONDS)(f) 35 | 36 | def tryReadLockAndDo[T](time: Long, unit: TimeUnit)(f: => T): Option[T] = readLock.tryLockAndDo(time, unit)(f) 37 | 38 | def writeLockAndDo[T](f: => T): T = writeLock.lockAndDo(f) 39 | 40 | def writeLockInterruptiblyAndDo[T](f: => T): T = writeLock.lockInterruptiblyAndDo(f) 41 | 42 | def tryWriteLockAndDo[T](f: => T): Option[T] = writeLock.tryLockAndDo(f) 43 | 44 | def tryWriteLockAndDo[T](when: DateTime)(f: => T): Option[T] = 45 | tryWriteLockAndDo(when.getMillis - System.currentTimeMillis, TimeUnit.MILLISECONDS)(f) 46 | 47 | def tryWriteLockAndDo[T](time: Long, unit: TimeUnit)(f: => T): Option[T] = writeLock.tryLockAndDo(time, unit)(f) 48 | } 49 | 50 | protected class LockEx(val lock: Lock) 51 | { 52 | 53 | def lockAndDo[T](f: => T): T = { 54 | lock.lock 55 | try { 56 | f 57 | } finally { 58 | lock.unlock 59 | } 60 | } 61 | 62 | def lockInterruptiblyAndDo[T](f: => T): T = { 63 | lock.lockInterruptibly 64 | try { 65 | f 66 | } finally { 67 | lock.unlock 68 | } 69 | } 70 | 71 | def tryLockAndDo[T](f: => T): Option[T] = 72 | if (lock.tryLock) 73 | try { 74 | Some(f) 75 | } finally { 76 | lock.unlock 77 | } 78 | else None 79 | 80 | def tryLockAndDo[T](when: DateTime)(f: => T): Option[T] = 81 | tryLockAndDo(when.getMillis - System.currentTimeMillis, TimeUnit.MILLISECONDS)(f) 82 | 83 | def tryLockAndDo[T](time: Long, unit: TimeUnit)(f: => T): Option[T] = 84 | if (lock.tryLock(time, unit)) 85 | try { 86 | Some(f) 87 | } finally { 88 | lock.unlock 89 | } 90 | else None 91 | } -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/CodeVersion.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.File 4 | 5 | import com.googlecode.scalascriptengine.classloading.ScalaClassLoader 6 | 7 | /** 8 | * the script engine works by keeping 1 version of the compiled source directories. 9 | * Every time a script changes, a new compilation is triggered (called a "refresh") 10 | * and done the current version is replaced by the new version. 11 | * 12 | * @author kostantinos.kougios 13 | * 14 | * 24 Dec 2011 15 | */ 16 | trait CodeVersion 17 | { 18 | def version: Int 19 | 20 | def classLoader: ScalaClassLoader 21 | 22 | def files: List[SourceFile] 23 | 24 | def sourceFiles: Map[File, SourceFile] 25 | 26 | def get[T](className: String): Class[T] 27 | 28 | def newInstance[T](className: String): T 29 | 30 | def constructors[T](className: String): Constructors[T] 31 | } 32 | 33 | protected case class CodeVersionImpl( 34 | val version: Int, 35 | val files: List[SourceFile], 36 | classLoader: ScalaClassLoader, 37 | val sourceFiles: Map[File, SourceFile] 38 | ) extends CodeVersion 39 | { 40 | override def get[T](className: String): Class[T] = classLoader.get(className) 41 | 42 | override def newInstance[T](className: String): T = classLoader.newInstance(className) 43 | 44 | override def constructors[T](className: String) = new Constructors(get(className)) 45 | } 46 | 47 | case class SourceFile(file: File) -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/CompilationStatus.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.util.concurrent.atomic.AtomicBoolean 4 | 5 | import org.joda.time.DateTime 6 | 7 | /** 8 | * @author kostantinos.kougios 9 | * 10 | * 11 Jan 2012 11 | */ 12 | class CompilationStatus private(val startTime: DateTime, val stopTime: Option[DateTime], val step: CompilationStatus.Status) 13 | { 14 | 15 | import CompilationStatus._ 16 | 17 | private val stopTrigger = new AtomicBoolean(false) 18 | 19 | def stop: Unit = step match { 20 | case ScanningSources | Compiling => stopTrigger.set(true) 21 | } 22 | 23 | def stopIfCompiling: Unit = stopTrigger.set(true) 24 | 25 | private[scalascriptengine] def checkStop: Unit = if (stopTrigger.get) throw new CompilationStopped 26 | } 27 | 28 | object CompilationStatus 29 | { 30 | 31 | abstract class Status 32 | 33 | object NotYetReady extends Status 34 | 35 | object ScanningSources extends Status 36 | 37 | object Compiling extends Status 38 | 39 | object Complete extends Status 40 | 41 | object Failed extends Status 42 | 43 | def notYetReady = new CompilationStatus(DateTime.now, None, NotYetReady) 44 | 45 | def started = new CompilationStatus(DateTime.now, None, ScanningSources) 46 | 47 | def failed(currentStatus: CompilationStatus) = new CompilationStatus(currentStatus.startTime, Some(DateTime.now), Failed) 48 | 49 | def completed(currentStatus: CompilationStatus) = new CompilationStatus(currentStatus.startTime, Some(DateTime.now), Complete) 50 | } 51 | 52 | class CompilationStopped extends RuntimeException 53 | { 54 | val time = DateTime.now 55 | 56 | override def getMessage = "compilation stopped at %s".format(time) 57 | 58 | override def toString = "CompilationStopped(%s)".format(time) 59 | } -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/Config.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.File 4 | 5 | import com.googlecode.scalascriptengine.classloading.ClassLoaderConfig 6 | 7 | /** 8 | * this holds the configuration for the script engine. Source and output paths, 9 | * compilation class paths, classloading classpaths . In the future it will hold scala compiler 10 | * settings, error reporters and so on. 11 | */ 12 | case class Config( 13 | // this is where the source files and target class directories are located 14 | // each source folder is compiled and in turn is used as classpath for the next source folder. 15 | // hence source folders must be in order of dependency, with the root classes been the 16 | // first element of the list 17 | sourcePaths: List[SourcePath], 18 | // this is the classpath for compilation and must be provided. i.e. 19 | // ScalaScriptEngine.currentClassPath 20 | compilationClassPaths: Set[File] = ScalaScriptEngine.currentClassPath, 21 | // this is an extra class loading classpath. I.e. the script folder might 22 | // utilize extra jars. Also the parent classloader will be used 23 | // to find any unresolved classes. This means that all classes visible to 24 | // your application will also be visible to the scripts even if the 25 | // classLoadingClassPaths is empty 26 | classLoadingClassPaths: Set[File] = Set.empty, 27 | 28 | classLoaderConfig: ClassLoaderConfig = ClassLoaderConfig.Default, 29 | compilationListeners: List[CodeVersion => Unit] = Nil, 30 | parentClassLoader: ClassLoader = getClass.getClassLoader 31 | ) 32 | { 33 | 34 | if (sourcePaths.flatMap(_.sources).toSet.size < sourcePaths.size) throw new IllegalArgumentException("duplicate source directories for " + sourcePaths) 35 | if (sourcePaths.map(_.targetDir).toSet.size < sourcePaths.size) throw new IllegalArgumentException("duplicate target directories for " + sourcePaths) 36 | 37 | // a convenient constructor to create a config with the default options 38 | // and one only source folder. 39 | def this(sourcePath: File) = this(List(SourcePath(Set(sourcePath)))) 40 | 41 | val scalaSourceDirs = sourcePaths.flatMap(_.sources) 42 | val targetDirs = sourcePaths.map(_.targetDir) 43 | } 44 | 45 | /** 46 | * scala source folder along with the destination class folder 47 | * 48 | * @param sources root folders of scala sources or scala files 49 | * @param targetDir root folder of generated class files 50 | */ 51 | case class SourcePath( 52 | sources: Set[File], 53 | // the outputDir, this is where all compiled classes will be stored. Please 54 | // use with care! A folder in the temp directory will usually do. 55 | targetDir: File = ScalaScriptEngine.tmpOutputFolder 56 | ) 57 | { 58 | if (!targetDir.isDirectory) throw new IllegalArgumentException(targetDir + " is not a directory") 59 | } 60 | 61 | object SourcePath 62 | { 63 | def apply(source: File): SourcePath = SourcePath(Set(source)) 64 | } -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/Constructors.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import scala.reflect.ClassTag 4 | 5 | /** 6 | * provides easy access to instance construction 7 | * 8 | * @author kostantinos.kougios 9 | * 10 | * 5 Jan 2012 11 | */ 12 | class Constructors[T](clz: Class[T]) 13 | { 14 | 15 | def newInstance = clz.newInstance 16 | 17 | def newInstance[P1](p1: P1)(implicit p1m: ClassTag[P1]): T = { 18 | val c = clz.getConstructor(p1m.runtimeClass) 19 | c.newInstance(p1.asInstanceOf[Object]) 20 | } 21 | 22 | def newInstance[P1, P2](p1: P1, p2: P2)(implicit p1m: ClassTag[P1], p2m: ClassTag[P2]): T = { 23 | val c = clz.getConstructor(p1m.runtimeClass, p2m.runtimeClass) 24 | c.newInstance(p1.asInstanceOf[Object], p2.asInstanceOf[Object]) 25 | } 26 | 27 | def newInstance[P1, P2, P3](p1: P1, p2: P2, p3: P3)(implicit p1m: ClassTag[P1], p2m: ClassTag[P2], p3m: ClassTag[P3]): T = { 28 | val c = clz.getConstructor(p1m.runtimeClass, p2m.runtimeClass, p3m.runtimeClass) 29 | c.newInstance(p1.asInstanceOf[Object], p2.asInstanceOf[Object], p3.asInstanceOf[Object]) 30 | } 31 | 32 | def newInstance[P1, P2, P3, P4](p1: P1, p2: P2, p3: P3, p4: P4)(implicit p1m: ClassTag[P1], p2m: ClassTag[P2], p3m: ClassTag[P3], p4m: ClassTag[P4]): T = { 33 | val c = clz.getConstructor(p1m.runtimeClass, p2m.runtimeClass, p3m.runtimeClass, p4m.runtimeClass) 34 | c.newInstance(p1.asInstanceOf[Object], p2.asInstanceOf[Object], p3.asInstanceOf[Object], p4.asInstanceOf[Object]) 35 | } 36 | 37 | /** 38 | * returns a function that can be used to instantiate the class using a no-arg constructor 39 | */ 40 | def constructor = newInstance _ 41 | 42 | /** 43 | * returns a function that can be used to instantiate the class using 1 arg constructor 44 | */ 45 | def constructorWith1Arg[P1](implicit p1m: ClassTag[P1]): P1 => T = { 46 | val c = clz.getConstructor(p1m.runtimeClass) 47 | (p1: P1) => c.newInstance(p1.asInstanceOf[Object]) 48 | } 49 | 50 | /** 51 | * returns a function that can be used to instantiate the class using 2 arg constructor 52 | */ 53 | def constructorWith2Args[P1, P2](implicit p1m: ClassTag[P1], p2m: ClassTag[P2]): (P1, P2) => T = { 54 | val c = clz.getConstructor(p1m.runtimeClass, p2m.runtimeClass) 55 | (p1: P1, p2: P2) => c.newInstance(p1.asInstanceOf[Object], p2.asInstanceOf[Object]) 56 | } 57 | 58 | /** 59 | * returns a function that can be used to instantiate the class using 3 arg constructor 60 | */ 61 | def constructorWith3Args[P1, P2, P3](implicit p1m: ClassTag[P1], p2m: ClassTag[P2], p3m: ClassTag[P3]): (P1, P2, P3) => T = { 62 | val c = clz.getConstructor(p1m.runtimeClass, p2m.runtimeClass, p3m.runtimeClass) 63 | (p1: P1, p2: P2, p3: P3) => c.newInstance(p1.asInstanceOf[Object], p2.asInstanceOf[Object], p3.asInstanceOf[Object]) 64 | } 65 | 66 | /** 67 | * returns a function that can be used to instantiate the class using 3 arg constructor 68 | */ 69 | def constructorWith4Args[P1, P2, P3, P4](implicit p1m: ClassTag[P1], p2m: ClassTag[P2], p3m: ClassTag[P3], p4m: ClassTag[P4]): (P1, P2, P3, P4) => T = { 70 | val c = clz.getConstructor(p1m.runtimeClass, p2m.runtimeClass, p3m.runtimeClass, p4m.runtimeClass) 71 | (p1: P1, p2: P2, p3: P3, p4: P4) => c.newInstance(p1.asInstanceOf[Object], p2.asInstanceOf[Object], p3.asInstanceOf[Object], p4.asInstanceOf[Object]) 72 | } 73 | } -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/DevUseIDECompiledClassesOnly.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import com.googlecode.scalascriptengine.classloading.ScalaClassLoader 4 | 5 | /** 6 | * this is useful during development. If your IDE compiles the classes (and recompiles them), 7 | * then there is no need for the script engine to recompile those. Just mixin this trait 8 | * and don't call refresh (if refresh is called then it falls back to normal operation) 9 | * 10 | * Note: don't use this on production or stress tests as it will reload the classes over 11 | * and over again until java runs out of PermGen space. 12 | * 13 | * @author kostas.kougios 14 | * Date: 18/02/13 15 | */ 16 | trait DevUseIDECompiledClassesOnly extends ScalaScriptEngine 17 | { 18 | 19 | @volatile 20 | private var cl: ScalaClassLoader = createClassLoader 21 | @volatile 22 | private var lastRefresh = System.currentTimeMillis 23 | 24 | @volatile 25 | var classVersion: Int = 0 26 | 27 | abstract override def get[T](className: String): Class[T] = 28 | if (currentVersion.version == 0) { 29 | if (System.currentTimeMillis - lastRefresh > 100) { 30 | cl = createClassLoader 31 | lastRefresh = System.currentTimeMillis 32 | classVersion += 1 33 | } 34 | cl.get(className) 35 | } else super.get(className) 36 | } 37 | -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/EvalCode.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.{File, FileWriter} 4 | import java.util.UUID 5 | 6 | import com.googlecode.scalascriptengine.classloading.ClassLoaderConfig 7 | 8 | import scala.reflect.runtime.universe._ 9 | 10 | /** 11 | * @author kostantinos.kougios 12 | * 13 | * 20 Aug 2012 14 | */ 15 | 16 | private class EvalCodeImpl[T]( 17 | clz: Class[T], 18 | typeArgs: List[TypeTag[_]], 19 | argNames: Iterable[String], 20 | body: String, 21 | classLoaderConfig: ClassLoaderConfig 22 | ) 23 | extends EvalCode[T] 24 | { 25 | private val id = UUID.randomUUID.toString.replace("-", "_") 26 | private val srcFolder = new File(System.getProperty("java.io.tmpdir"), id) 27 | if (!srcFolder.mkdir) throw new IllegalStateException("can't create temp folder %s".format(srcFolder)) 28 | 29 | private val config = ScalaScriptEngine.defaultConfig(srcFolder).copy(classLoaderConfig = classLoaderConfig) 30 | private val sse = ScalaScriptEngine.onChangeRefresh(config, 0) 31 | 32 | private val className = s"Eval_$id" 33 | 34 | private val templateTop = { 35 | def ttString(tt: TypeTag[_]) = tt.tpe.toString 36 | 37 | val typeArgsString = 38 | if (typeArgs.isEmpty) "" 39 | else typeArgs.map(ttString).mkString("[", ",", "]") 40 | 41 | val params = (argNames zip typeArgs).map { 42 | case (pName, tt) => pName + ": " + ttString(tt) 43 | }.mkString(",") 44 | 45 | val retType = ttString(typeArgs.last) 46 | 47 | s""" 48 | class $className extends ${clz.getName}$typeArgsString { 49 | override def apply($params): $retType = { 50 | $body 51 | } 52 | } 53 | """ 54 | } 55 | private val srcFile = new File(srcFolder, s"$className.scala") 56 | private val src = new FileWriter(srcFile) 57 | try { 58 | src.write(templateTop) 59 | } finally { 60 | src.close() 61 | } 62 | 63 | // the Class[T] 64 | val generatedClass: Class[T] = 65 | try sse.get[T](className) 66 | finally { 67 | // clean up 68 | srcFile.delete() 69 | srcFolder.delete() 70 | } 71 | 72 | // creates a new instance of the evaluated class 73 | def newInstance: T = generatedClass.newInstance 74 | } 75 | 76 | /** 77 | * a scala-code evaluator 78 | */ 79 | trait EvalCode[T] 80 | { 81 | // the Class[T] 82 | val generatedClass: Class[T] 83 | 84 | // creates a new instance of the evaluated class 85 | def newInstance: T 86 | } 87 | 88 | object EvalCode 89 | { 90 | def apply[T](clz: Class[T], typeArgs: List[TypeTag[_]], argNames: Iterable[String], body: String, classLoaderConfig: ClassLoaderConfig): EvalCode[T] = 91 | new EvalCodeImpl(clz, typeArgs, argNames, body, classLoaderConfig) 92 | 93 | def withoutArgs[R](body: String, classLoaderConfig: ClassLoaderConfig = ClassLoaderConfig.Default)(implicit retTag: TypeTag[R]) = 94 | apply(classOf[() => R], List(retTag), Nil, body, classLoaderConfig) 95 | 96 | def with1Arg[A1, R]( 97 | arg1Var: String, 98 | body: String, 99 | classLoaderConfig: ClassLoaderConfig = ClassLoaderConfig.Default 100 | )( 101 | implicit arg1Tag: TypeTag[A1], 102 | retTag: TypeTag[R] 103 | ) = 104 | apply(classOf[A1 => R], List(arg1Tag, retTag), List(arg1Var), body, classLoaderConfig) 105 | 106 | def with2Args[A1, A2, R]( 107 | arg1Var: String, 108 | arg2Var: String, 109 | body: String, 110 | classLoaderConfig: ClassLoaderConfig = ClassLoaderConfig.Default 111 | )( 112 | implicit arg1Tag: TypeTag[A1], 113 | arg2Tag: TypeTag[A2], 114 | retTag: TypeTag[R] 115 | ) = 116 | apply(classOf[(A1, A2) => R], List(arg1Tag, arg2Tag, retTag), List(arg1Var, arg2Var), body, classLoaderConfig) 117 | 118 | def with3Args[A1, A2, A3, R]( 119 | arg1Var: String, 120 | arg2Var: String, 121 | arg3Var: String, 122 | body: String, 123 | classLoaderConfig: ClassLoaderConfig = ClassLoaderConfig.Default 124 | )( 125 | implicit arg1Tag: TypeTag[A1], 126 | arg2Tag: TypeTag[A2], 127 | arg3Tag: TypeTag[A3], 128 | retTag: TypeTag[R] 129 | ) = 130 | apply(classOf[(A1, A2, A3) => R], List(arg1Tag, arg2Tag, arg3Tag, retTag), List(arg1Var, arg2Var, arg3Var), body, classLoaderConfig) 131 | } -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/FromClasspathFirst.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | /** 4 | * if no code version is ready, 5 | * tries to find the classes from the classpath initially. If that 6 | * fails, it then goes the route of finding/compiling a scala class 7 | * from the source classpaths. 8 | * 9 | * If a codeversion is ready, it just uses the codeversion 10 | * 11 | * This can come handy i.e. during development if your IDE compiles 12 | * the dynamic scala classes and you wouldn't like to wait for the 13 | * classes to be recompiled. 14 | * 15 | * @author kostantinos.kougios 16 | * 17 | * 2 Jan 2012 18 | */ 19 | trait FromClasspathFirst extends ScalaScriptEngine 20 | { 21 | abstract override def get[T](className: String): Class[T] = 22 | if (currentVersion.version == 0) { 23 | try { 24 | Class.forName(className).asInstanceOf[Class[T]] 25 | } catch { 26 | case _: ClassNotFoundException => super.get(className) 27 | } 28 | } else super.get(className) 29 | } -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/Logging.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import org.slf4j.{Logger, LoggerFactory} 4 | 5 | /** 6 | * logging is done via slf4j 7 | * 8 | * @author kostantinos.kougios 9 | * 10 | * 25 Dec 2011 11 | */ 12 | protected trait Logging 13 | { 14 | private val logger: Logger = LoggerFactory.getLogger(getClass) 15 | 16 | protected def debug(msg: => String) = if (logger.isDebugEnabled) logger.debug(msg) 17 | 18 | protected def info(msg: => String) = if (logger.isInfoEnabled) logger.info(msg) 19 | 20 | protected def warn(msg: String) = logger.warn(msg) 21 | 22 | protected def error(msg: String) = logger.error(msg) 23 | 24 | protected def error(msg: String, e: Throwable) = logger.error(msg, e) 25 | } -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/RefreshPolicies.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.File 4 | import java.util.concurrent.ConcurrentHashMap 5 | import java.util.concurrent.atomic.{AtomicBoolean, AtomicLong} 6 | 7 | import com.googlecode.concurrent.ExecutorServiceManager 8 | import org.joda.time.DateTime 9 | 10 | /** 11 | * periodically scans the source directories and if a file changed, it recompiles 12 | * and creates a new CodeVersion (changes will be visible as soon as compilation 13 | * completes) 14 | * 15 | * @author kostantinos.kougios 16 | * 17 | * 25 Dec 2011 18 | */ 19 | trait TimedRefresh 20 | { 21 | this: ScalaScriptEngine => 22 | def rescheduleAt: DateTime 23 | 24 | private val executor = ExecutorServiceManager.newScheduledThreadPool(1, e => error("error during recompilation of a source file", e)) 25 | executor.runPeriodically(rescheduleAt, Some(rescheduleAt)) { 26 | refresh 27 | } 28 | 29 | def shutdown = executor.shutdown 30 | } 31 | 32 | /** 33 | * checks scala files for modification and if yes it recompiles 34 | * the changed sources. This is not to be used by client code but 35 | * rather be used by the rest of the refresh policy traits. 36 | * 37 | * recheckEveryMillis should be provided. If <=0 then for every 38 | * request for a class, the source file of the class is checked 39 | * for modifications. If >0 then maximum 1 check will be performed 40 | * every recheckEveryMillis milliseconds. A sensible value might be 41 | * 1000 millis if code changes frequently (i.e. during dev) and 42 | * 30000 millis if code doesn't change that often (i.e. production) 43 | */ 44 | protected trait OnChangeRefresh extends ScalaScriptEngine 45 | { 46 | val recheckEveryMillis: Long 47 | private val lastChecked = new ConcurrentHashMap[String, java.lang.Long] 48 | private val timesTested = new AtomicLong 49 | 50 | def numberOfTimesSourcesTestedForModifications = timesTested.get 51 | 52 | abstract override def get[T](className: String): Class[T] = { 53 | val l = lastChecked.get(className) 54 | val now = System.currentTimeMillis 55 | if (l == null || recheckEveryMillis <= 0 || now - l > recheckEveryMillis) { 56 | lastChecked.put(className, now) 57 | val fileName = className.replace('.', '/') + ".scala" 58 | val isModO = config.sourcePaths.find { 59 | paths => 60 | paths.sources.exists { 61 | source => 62 | new File(source, fileName).exists 63 | } 64 | }.map { 65 | paths => 66 | isModified(paths, className) 67 | } 68 | timesTested.incrementAndGet 69 | if (isModO.isDefined && isModO.get) doRefresh 70 | } 71 | super.get(className) 72 | } 73 | 74 | def doRefresh: Unit 75 | } 76 | 77 | /** 78 | * refresh as soon as a modification is detected. The first thread that actually 79 | * does the refresh will do the compilation and the rest of the threads will 80 | * wait. All threads will get an up to date compiled version of the source code. 81 | * 82 | * This is blocking during compilation and is not recommended to be used by 83 | * web servers. RefreshAsynchronously offers a much better alternative. 84 | */ 85 | trait RefreshSynchronously extends ScalaScriptEngine with OnChangeRefresh 86 | { 87 | private var lastCompiled: Long = 0 88 | 89 | override def doRefresh: Unit = { 90 | // refresh only if not already refreshing 91 | val time = System.currentTimeMillis 92 | synchronized { 93 | if (time > lastCompiled) try { 94 | refresh 95 | } finally { 96 | // set lastCompile even in case of compilation errors 97 | lastCompiled = time 98 | } 99 | } 100 | } 101 | } 102 | 103 | /** 104 | * makes sure the refresh is run only once at a time. All calls 105 | * to refresh return straight away with the current code version but 106 | * a compilation will be triggered if the source code changed. The 107 | * compilation will occur in the background and when done, the new 108 | * compiled version of the code will be used. 109 | */ 110 | trait RefreshAsynchronously extends ScalaScriptEngine with OnChangeRefresh 111 | { 112 | private val isCompiling = new AtomicBoolean(false) 113 | private val executor = ExecutorServiceManager.newSingleThreadExecutor 114 | 115 | override def doRefresh: Unit = { 116 | // refresh only if not already refreshing 117 | val c = isCompiling.getAndSet(true) 118 | if (!c) executor.submit { 119 | try { 120 | refresh 121 | } catch { 122 | case e: Throwable => 123 | error("error during refresh", e) 124 | } finally { 125 | isCompiling.set(false) 126 | } 127 | } 128 | } 129 | 130 | def shutdown = executor.shutdown 131 | } -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/SSESecurityManager.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.security.Permission 4 | 5 | /** 6 | * @author kostantinos.kougios 7 | * 8 | * 7 Oct 2012 9 | */ 10 | class SSESecurityManager(securityManager: SecurityManager) extends SecurityManager 11 | { 12 | if (securityManager == null) throw new NullPointerException("securityManager shouldn't be null") 13 | 14 | private val enabled = new InheritableThreadLocal[Boolean] 15 | 16 | override def checkPermission(perm: Permission) { 17 | if (enabled.get) { 18 | securityManager.checkPermission(perm) 19 | } 20 | } 21 | 22 | def secured[R](f: => R) = { 23 | enabled.set(true) 24 | try { 25 | f 26 | } finally { 27 | enabled.set(false) 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/ScalaScriptEngine.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.File 4 | import java.net.URLClassLoader 5 | 6 | import com.googlecode.scalascriptengine.classloading.ScalaClassLoader 7 | import com.googlecode.scalascriptengine.internals.{CompilerManager, LastModMap} 8 | import org.joda.time.DateTime 9 | 10 | /** 11 | * The implementation of the script engine. 12 | * 13 | * The engine works by refreshing the codeVersion. This means that, when the 14 | * refresh() function is called and provided that the source files have changed, 15 | * a compilation will be triggered. When the compilation is complete, a new 16 | * codeVersion will be created and will be used till the next refresh. 17 | * 18 | * This can be initialized standalone or by mixing in a refresh policy trait. If 19 | * standalone, then refresh() should be manually invoked every time a change 20 | * occurs in the source code. 21 | * 22 | * If mixed in with a refresh policy, then the policy takes care of scanning the 23 | * source code for changes and refreshing. Please check RefreshPolicies.scala 24 | * 25 | * Typically this class will not be instantiated using 'new' but rather using one 26 | * of the factory methods of the companion object. Instantiation offers the full 27 | * amount of options that can be used by mixing in the various refresh policies 28 | * and enhancers. 29 | * 30 | * val sse = new ScalaScriptEngine(Config( 31 | * Set(sourceDir), 32 | * compilationClassPath, 33 | * runtimeClasspath, 34 | * outputDir)) with RefreshAsynchronously with FromClasspathFirst { 35 | * val recheckEveryMillis: Long = 1000 // each file will only be checked maximum once per second 36 | * })) 37 | * 38 | * 39 | * @author kostantinos.kougios 40 | * 41 | * 22 Dec 2011 42 | */ 43 | class ScalaScriptEngine(val config: Config) extends Logging 44 | { 45 | 46 | private def compileManager = new CompilerManager(config.sourcePaths, config.compilationClassPaths, this) 47 | 48 | // codeversion is initialy to version 0 which is not usable. 49 | @volatile private var codeVersion: CodeVersion = new CodeVersion 50 | { 51 | override def version: Int = 0 52 | 53 | override def classLoader: ScalaClassLoader = throw new IllegalStateException("CodeVersion not yet ready.") 54 | 55 | override def files: List[SourceFile] = Nil 56 | 57 | override def sourceFiles = Map[File, SourceFile]() 58 | 59 | override def get[T](className: String): Class[T] = throw new IllegalStateException("CodeVersion not yet ready.") 60 | 61 | override def newInstance[T](className: String): T = throw new IllegalStateException("CodeVersion not yet ready.") 62 | 63 | override def constructors[T](className: String) = throw new IllegalStateException("CodeVersion not yet ready.") 64 | } 65 | 66 | @volatile private var _compilationStatus = CompilationStatus.notYetReady 67 | 68 | def currentVersion = codeVersion 69 | 70 | def versionNumber = codeVersion.version 71 | 72 | def compilationStatus = _compilationStatus 73 | 74 | /* 75 | * refreshes the codeversion by scanning the source folders for changed source files. If any are 76 | * found, then a compilation is triggered. 77 | * 78 | * This method is not thread safe (but refresh policies ensure calling this only once at each time) 79 | */ 80 | def refresh: CodeVersion = { 81 | 82 | _compilationStatus = CompilationStatus.started 83 | 84 | val allChangedFiles = config.sourcePaths.flatMap(paths => allChanged(paths)) 85 | _compilationStatus.checkStop 86 | val result = if (allChangedFiles.isEmpty) 87 | codeVersion 88 | else { 89 | debug("refreshing changed files %s".format(allChangedFiles)) 90 | val sourceFilesSet = allChangedFiles.map(f => SourceFile(f)) 91 | _compilationStatus.checkStop 92 | 93 | def sourceFiles = sourceFilesSet.map(s => (s.file, s)).toMap 94 | 95 | try { 96 | _compilationStatus.checkStop 97 | compileManager.compile(allChangedFiles.map(_.getAbsolutePath)) 98 | } catch { 99 | case e: Throwable => 100 | if (versionNumber > 0) { 101 | // update fileset to this codeversion to avoid 102 | // continuously compiling problematic code 103 | codeVersion = CodeVersionImpl( 104 | codeVersion.version, 105 | sourceFilesSet, 106 | codeVersion.classLoader, 107 | sourceFiles) 108 | } 109 | _compilationStatus = CompilationStatus.failed(_compilationStatus) 110 | throw e 111 | } 112 | _compilationStatus.checkStop 113 | val classLoader = createClassLoader 114 | debug("done refreshing") 115 | codeVersion = CodeVersionImpl( 116 | codeVersion.version + 1, 117 | sourceFilesSet, 118 | classLoader, 119 | sourceFiles) 120 | codeVersion 121 | } 122 | 123 | _compilationStatus = CompilationStatus.completed(_compilationStatus) 124 | config.compilationListeners.foreach(_(result)) 125 | result 126 | } 127 | 128 | protected def createClassLoader = ScalaClassLoader( 129 | config.sourcePaths.map(_.targetDir).toSet, 130 | config.scalaSourceDirs.toSet ++ config.classLoadingClassPaths, 131 | config.parentClassLoader, 132 | config.classLoaderConfig) 133 | 134 | /** 135 | * returns the Class[T] for className 136 | * 137 | * Can throw ClassNotFoundException if the class is not present. 138 | * Can throw ClassCastException if the class is not of T 139 | * Can trigger a compilation in the background or foreground, 140 | * depending on the refresh policy. 141 | */ 142 | def get[T](className: String): Class[T] = codeVersion.get(className) 143 | 144 | /** 145 | * returns Constructors, this allows easy instantiation of the class 146 | * using up to 4 constructor arguments. 147 | * 148 | * Constructors returned by this method are linked to the current codeversion. 149 | * This means that, if codeversion is refreshed, a call to this will return 150 | * an up to date Constructors instance. But also it means that the returned 151 | * constructor will always create instances of that codeversion and will not 152 | * reflect updates to the codeversion. 153 | */ 154 | def constructors[T](className: String): Constructors[T] = new Constructors(get(className)) 155 | 156 | /** 157 | * returns a new instance of className. The new instance is always of the 158 | * latest codeversion. 159 | */ 160 | def newInstance[T](className: String): T = get[T](className).newInstance 161 | 162 | /** 163 | * please make sure outputDir is valid!!! If you used one of the factory 164 | * methods to create an instance of the script engine, the output dir will 165 | * be in the tmp directory. 166 | */ 167 | def deleteAllClassesInOutputDirectory() = { 168 | def deleteAllClassesInOutputDirectory(dir: File) { 169 | dir.listFiles.filter(_.getName.endsWith(".class")).foreach(_.delete) 170 | dir.listFiles.filter(_.isDirectory).foreach(d => deleteAllClassesInOutputDirectory(d)) 171 | } 172 | config.targetDirs.foreach { 173 | d => 174 | deleteAllClassesInOutputDirectory(d) 175 | } 176 | } 177 | 178 | private val modified = new LastModMap 179 | 180 | /** 181 | * @param clz the full class name 182 | * @return true if the scala file was modified since the last compilation 183 | */ 184 | def isModified(sourcePath: SourcePath, clz: String): Boolean = { 185 | val f = clz.replace('.', '/') 186 | val scalaName = f + ".scala" 187 | sourcePath.sources.exists { 188 | source => 189 | val scalaFile = new File(source, scalaName) 190 | modified.isMod(scalaFile) 191 | } 192 | } 193 | 194 | /** 195 | * marks all source files as modified, hence it will recompile all the source 196 | * files on the next call to refresh() 197 | */ 198 | def markAllAsModified() { 199 | modified.markAllAsModified() 200 | } 201 | 202 | /** 203 | * forces a clean build of all source files 204 | * 205 | * @return the new CodeVersion 206 | */ 207 | def cleanBuild: CodeVersion = { 208 | deleteAllClassesInOutputDirectory() 209 | markAllAsModified() 210 | refresh 211 | } 212 | 213 | /** 214 | * finds all changed sources in the this sourcePath 215 | * @param sourcePath SourcePath to scan 216 | * @return all changed files 217 | */ 218 | private def allChanged(sourcePath: SourcePath): Set[File] = { 219 | 220 | def scan(src: File, clzDir: File): Set[File] = { 221 | val srcIsDir = src.isDirectory 222 | val all = if (srcIsDir) src.listFiles else Array(src) 223 | val mod = if (srcIsDir) all.filter(_.getName.endsWith(".scala")) 224 | .filter { 225 | scalaFile => 226 | modified.isMod(scalaFile) 227 | }.toSet 228 | else Set(src) 229 | 230 | val sub = all.filter(_.isDirectory).flatMap { 231 | dir => 232 | scan(dir, new File(clzDir, dir.getName)) 233 | } 234 | 235 | mod ++ sub 236 | } 237 | 238 | val all = sourcePath.sources.flatMap(source => scan(source, sourcePath.targetDir)) 239 | all.foreach(modified.updated) 240 | all 241 | } 242 | } 243 | 244 | /** 245 | * the companion object provides a lot of useful factory methods to create a script engine 246 | * with sensible defaults. 247 | */ 248 | object ScalaScriptEngine 249 | { 250 | def tmpOutputFolder = { 251 | val dir = new File(System.getProperty("java.io.tmpdir"), "scala-script-engine-classes") 252 | dir.mkdir 253 | dir 254 | } 255 | 256 | def currentClassPath = { 257 | // this tries to detect the classpath, if it doesn't work 258 | // for you, please email me or open an issue explaining your 259 | // usecase. 260 | def cp(cl: ClassLoader): Set[File] = cl match { 261 | case ucl: URLClassLoader => ucl.getURLs.map(u => new File(u.getFile)).toSet ++ cp(ucl.getParent) 262 | case _: ClassLoader => Set() 263 | case null => Set() 264 | } 265 | cp(Thread.currentThread.getContextClassLoader) ++ System.getProperty("java.class.path").split(File.pathSeparator).map(p => new File(p)).toSet 266 | } 267 | 268 | def defaultConfig(sourcePath: File) = Config( 269 | List(SourcePath(Set(sourcePath))), 270 | currentClassPath, 271 | Set() 272 | ) 273 | 274 | /** 275 | * returns an instance of the engine. Refreshes must be done manually 276 | */ 277 | def withoutRefreshPolicy( 278 | sourcePaths: List[SourcePath], 279 | compilationClassPaths: Set[File], 280 | classLoadingClassPaths: Set[File] 281 | ): ScalaScriptEngine = 282 | new ScalaScriptEngine(Config(sourcePaths, compilationClassPaths, classLoadingClassPaths)) 283 | 284 | /** 285 | * returns an instance of the engine. Refreshes must be done manually 286 | */ 287 | def withoutRefreshPolicy(sourcePaths: List[SourcePath], compilationClassPaths: Set[File]): ScalaScriptEngine = 288 | new ScalaScriptEngine(Config(sourcePaths, compilationClassPaths, Set())) 289 | 290 | /** 291 | * returns an instance of the engine. Refreshes must be done manually 292 | */ 293 | def withoutRefreshPolicy(sourcePath: SourcePath, compilationClassPaths: Set[File]): ScalaScriptEngine = 294 | withoutRefreshPolicy(Config(List(sourcePath), compilationClassPaths, Set()), compilationClassPaths) 295 | 296 | def withoutRefreshPolicy(config: Config, compilationClassPaths: Set[File]): ScalaScriptEngine = 297 | new ScalaScriptEngine(config) 298 | 299 | /** 300 | * returns an instance of the engine. Refreshes must be done manually 301 | */ 302 | def withoutRefreshPolicy(sourcePaths: List[SourcePath]): ScalaScriptEngine = 303 | withoutRefreshPolicy(sourcePaths, currentClassPath) 304 | 305 | /** 306 | * returns an instance of the engine. Refreshes must be done manually 307 | */ 308 | def withoutRefreshPolicy(sourcePath: SourcePath): ScalaScriptEngine = withoutRefreshPolicy(List(sourcePath)) 309 | 310 | /** 311 | * periodically scans the source folders for changes. If a change is detected, a recompilation is 312 | * triggered. The new codeversion is used upon competion of the compilation. 313 | * 314 | * Please call refresh before using the engine for the first time. 315 | */ 316 | def timedRefresh(sourcePath: File, refreshEvery: () => DateTime): ScalaScriptEngine with TimedRefresh = 317 | timedRefresh(defaultConfig(sourcePath), refreshEvery) 318 | 319 | def timedRefresh(config: Config, refreshEvery: () => DateTime): ScalaScriptEngine with TimedRefresh = 320 | new ScalaScriptEngine(config) with TimedRefresh 321 | { 322 | def rescheduleAt = refreshEvery() 323 | } 324 | 325 | /** 326 | * creates a ScalaScriptEngine with the following behaviour: 327 | * 328 | * if a change in a requested class source file is detected, the source path 329 | * will be recompiled (this includes all changed files that changed). For each 330 | * call to ScalaScriptEngine.get(className), the filesystem is checked for 331 | * modifications in the relevant scala file. For a more efficient way of 332 | * doing the same in production environments, please look at 333 | * #onChangeRefresh(sourcePath, recheckEveryInMillis) 334 | * 335 | * @param sourcePath the path where the scala source files are. 336 | * @return the ScalaScriptEngine 337 | */ 338 | def onChangeRefresh(sourcePath: File): ScalaScriptEngine with OnChangeRefresh = 339 | onChangeRefresh(sourcePath, 0) 340 | 341 | /** 342 | * creates a ScalaScriptEngine with the following behaviour: 343 | * 344 | * if a change in a requested class source file is detected, the source path 345 | * will be recompiled (this includes all changed files that changed). For each 346 | * call to ScalaScriptEngine.get(className), provided that recheckEveryInMillis 347 | * has passed between the call and the previous filesystem check, 348 | * the filesystem is checked for 349 | * modifications in the relevant scala file. 350 | * 351 | * @param sourcePath the path where the scala source files are. 352 | * @param recheckSourceEveryDtInMillis each file will only be checked for changes 353 | * once per recheckEveryInMillis milliseconds. 354 | * @return the ScalaScriptEngine 355 | */ 356 | def onChangeRefresh(sourcePath: File, recheckSourceEveryDtInMillis: Long): ScalaScriptEngine with OnChangeRefresh = 357 | onChangeRefresh(defaultConfig(sourcePath), recheckSourceEveryDtInMillis) 358 | 359 | def onChangeRefresh(config: Config, recheckSourceEveryDtInMillis: Long): ScalaScriptEngine with OnChangeRefresh = 360 | new ScalaScriptEngine(config) with OnChangeRefresh with RefreshSynchronously 361 | { 362 | val recheckEveryMillis = recheckSourceEveryDtInMillis 363 | } 364 | 365 | /** 366 | * similar to onChangeRefresh, but the compilation occurs in the background. 367 | * While the compilation occurs, the ScalaScriptEngine.get(className) 368 | * returns the existing version of the class without blocking. 369 | * 370 | * Please call refresh before using the engine for the first time. 371 | * 372 | * Before exiting, please call shutdown to shutdown the compilation thread 373 | */ 374 | def onChangeRefreshAsynchronously(sourcePath: File): ScalaScriptEngine with OnChangeRefresh with RefreshAsynchronously = 375 | onChangeRefreshAsynchronously(sourcePath, 0) 376 | 377 | /** 378 | * similar to onChangeRefresh, but the compilation occurs in the background. 379 | * While the compilation occurs, the ScalaScriptEngine.get(className) 380 | * returns the existing version of the class without blocking. 381 | * 382 | * Please call refresh before using the engine for the first time. 383 | * 384 | * Before exiting, please call shutdown to shutdown the compilation thread 385 | */ 386 | def onChangeRefreshAsynchronously(sourcePath: File, recheckEveryInMillis: Long): ScalaScriptEngine with OnChangeRefresh with RefreshAsynchronously = 387 | onChangeRefreshAsynchronously(defaultConfig(sourcePath), recheckEveryInMillis) 388 | 389 | def onChangeRefreshAsynchronously(config: Config, recheckEveryInMillis: Long): ScalaScriptEngine with OnChangeRefresh with RefreshAsynchronously = 390 | new ScalaScriptEngine(config) with OnChangeRefresh with RefreshAsynchronously 391 | { 392 | val recheckEveryMillis: Long = recheckEveryInMillis 393 | } 394 | } -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/classloading/ClassLoaderConfig.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine.classloading 2 | 3 | /** 4 | * @author kostantinos.kougios 5 | * 6 | * 11 Oct 2012 7 | */ 8 | case class ClassLoaderConfig( 9 | protectPackages: Set[String] = Set(), 10 | protectClasses: Set[String] = Set(), 11 | // a function of (packageName , fullClassName)=> allow access? 12 | allowed: (String, String) => Boolean = (_, _) => true, 13 | // register listeners for class loading events, (className,class)=>Unit 14 | classLoadingListeners: List[(String, Class[_]) => Unit] = Nil, 15 | enableClassRegistry: Boolean = false 16 | ) 17 | { 18 | val protectPackagesSuffixed = protectPackages.map(_ + ".") 19 | } 20 | 21 | object ClassLoaderConfig 22 | { 23 | val Default = ClassLoaderConfig() 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/classloading/ClassRegistry.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine.classloading 2 | 3 | import java.io.{File, FileInputStream} 4 | 5 | import scala.language.reflectiveCalls 6 | import scala.reflect.ClassTag 7 | 8 | /** 9 | * finds all class names for a list of directories 10 | * 11 | * @author kostas.kougios 12 | * Date: 21/08/13 13 | */ 14 | case class ClassRegistry(parentClassLoader: ClassLoader, dirs: Set[File]) 15 | { 16 | val allClasses = { 17 | val classFiles = find(dirs.toList) 18 | val classLoader = new ClassLoader(parentClassLoader) 19 | { 20 | def scan = classFiles.map { 21 | f => 22 | val is = new FileInputStream(f) 23 | try { 24 | val cnt = is.available 25 | val bytes = Array.ofDim[Byte](cnt) 26 | is.read(bytes) 27 | defineClass(null, bytes, 0, bytes.length) 28 | } finally { 29 | is.close() 30 | } 31 | } 32 | } 33 | classLoader.scan 34 | } 35 | 36 | def withTypeOf[T](implicit ct: ClassTag[T]) = allClasses.filter(ct.runtimeClass.isAssignableFrom _) 37 | 38 | // find all class files 39 | private def find(dirs: List[File]): List[File] = dirs.map { 40 | dir => 41 | if (!dir.isDirectory) throw new IllegalArgumentException(s"not a directory: $dir") 42 | val files = dir.listFiles.toList 43 | val subDirs = find(files.filter(_.isDirectory)) 44 | files.filter(_.getName.endsWith(".class")) ::: subDirs 45 | }.flatten 46 | } 47 | -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/classloading/ScalaClassLoader.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine.classloading 2 | 3 | import java.io.File 4 | import java.net.URLClassLoader 5 | import java.security.AccessControlException 6 | import java.util.concurrent.ConcurrentHashMap 7 | 8 | import scala.reflect.ClassTag 9 | 10 | /** 11 | * a throwaway classloader that keeps one version of the source code. For every code change/refresh, 12 | * a new instance of this classloader is used. 13 | * 14 | * @author kostantinos.kougios 15 | * 16 | * 22 Dec 2011 17 | */ 18 | case class ScalaClassLoader( 19 | sourceDirs: Set[File], 20 | classPath: Set[File], 21 | parentClassLoader: ClassLoader, 22 | config: ClassLoaderConfig 23 | ) extends URLClassLoader( 24 | (classPath ++ sourceDirs).toArray.map(_.toURI.toURL), 25 | parentClassLoader) 26 | { 27 | private val cache = new ConcurrentHashMap[String, Class[_]] 28 | 29 | private val allClasses = if (config.enableClassRegistry) { 30 | ClassRegistry(parentClassLoader, sourceDirs).allClasses.map(c => loadClass(c.getName)) 31 | } else Nil 32 | 33 | /** 34 | * @return all classes, if ClassLoaderConfig.enableClassRegistry=true , throws an exception otherwise 35 | */ 36 | def all = if (config.enableClassRegistry) allClasses else throw new IllegalStateException("ClassLoaderConfig.enableClassRegistry not true, class registry not enabled") 37 | 38 | def withTypeOf[T](implicit ct: ClassTag[T]): List[Class[T]] = all.filter(ct.runtimeClass.isAssignableFrom _).asInstanceOf[List[Class[T]]] 39 | 40 | def get[T](className: String): Class[T] = loadClass(className).asInstanceOf[Class[T]] 41 | 42 | def newInstance[T](className: String): T = get[T](className).newInstance 43 | 44 | override def loadClass(name: String) = { 45 | 46 | def accessForbidden() = throw new AccessControlException("access to class " + name + " not allowed") 47 | 48 | if (!config.protectPackages.isEmpty) { 49 | config.protectPackagesSuffixed.find(name.startsWith(_)).foreach { 50 | _ => 51 | accessForbidden() 52 | } 53 | } 54 | if (!config.protectClasses.isEmpty) { 55 | config.protectClasses.find(_ == name).foreach { 56 | _ => 57 | accessForbidden() 58 | } 59 | } 60 | val pckg = name.lastIndexOf('.') match { 61 | case -1 => "" 62 | case n => name.substring(0, n) 63 | } 64 | 65 | if (!config.allowed(pckg, name)) 66 | accessForbidden() 67 | 68 | val clz = cache.get(name) match { 69 | case null => 70 | val clz = super.loadClass(name) 71 | cache.put(name, clz) 72 | clz 73 | case c => c 74 | } 75 | config.classLoadingListeners.foreach { 76 | cll => 77 | cll(name, clz) 78 | } 79 | clz 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/internals/CompilerManager.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine.internals 2 | 3 | import java.io.File 4 | 5 | import com.googlecode.scalascriptengine.{Logging, ScalaScriptEngine, SourcePath} 6 | 7 | import scala.reflect.internal.util.Position 8 | import scala.tools.nsc.reporters.AbstractReporter 9 | import scala.tools.nsc.{Global, Phase, Settings, SubComponent} 10 | 11 | /** 12 | * manages the scala compiler, taking care of setting the correct compiler parameters 13 | * and reporting errors. 14 | * 15 | * @author kostantinos.kougios 16 | * 17 | * 22 Dec 2011 18 | */ 19 | class CompilerManager(sourcePaths: List[SourcePath], classPaths: Set[File], sse: ScalaScriptEngine) extends Logging 20 | { 21 | 22 | private def acc(todo: List[SourcePath], done: List[SourcePath]): List[(SourcePath, (Global, Global#Run, CompilationReporter))] = todo match { 23 | case Nil => Nil 24 | case h :: t => 25 | val settings = new Settings(s => { 26 | error("errors report: " + s) 27 | }) 28 | settings.sourcepath.tryToSet(h.sources.map(_.getAbsolutePath).toList) 29 | val cp = done.map(_.targetDir) ++ classPaths 30 | settings.classpath.tryToSet(List(cp.map(_.getAbsolutePath).mkString(File.pathSeparator))) 31 | settings.outdir.tryToSet(h.targetDir.getAbsolutePath :: Nil) 32 | 33 | val reporter = new CompilationReporter(settings) 34 | 35 | class SSEGlobal extends Global(settings, reporter) 36 | { 37 | 38 | object SSEPhase extends SubComponent 39 | { 40 | val global: SSEGlobal.this.type = SSEGlobal.this 41 | val phaseName = "SSEPhase" 42 | val runsAfter = List[String]("typer") 43 | val runsRightAfter = None 44 | 45 | def newPhase(_prev: Phase): StdPhase = new StdPhase(_prev) 46 | { 47 | def apply(unit: CompilationUnit) { 48 | info("compiling unit " + unit) 49 | sse.compilationStatus.checkStop 50 | } 51 | } 52 | } 53 | 54 | override protected def computePhaseDescriptors = { 55 | addToPhasesSet(SSEPhase, "SSEPhase") 56 | super.computePhaseDescriptors 57 | } 58 | } 59 | 60 | val g = new SSEGlobal 61 | 62 | val run = new g.Run 63 | (h, (g, run, reporter)) :: acc(t, h :: done) 64 | 65 | } 66 | 67 | private val runMap = acc(sourcePaths, Nil).toMap 68 | 69 | def compile(allFiles: List[String]) = { 70 | 71 | def doCompile(sp: SourcePath, cp: Set[File]) { 72 | val (g, run, reporter) = runMap(sp) 73 | 74 | val rootPaths = sp.sources.map(_.getAbsolutePath) 75 | val files = allFiles.filter { 76 | f => 77 | rootPaths.exists(rp => f.startsWith(rp)) 78 | } 79 | run.compile(files) 80 | 81 | val errors = reporter.errors.result 82 | if (!errors.isEmpty) throw new CompilationError(s"${errors.size} error(s) occured :\n${errors.mkString("\n")}") 83 | } 84 | 85 | def all(todo: List[SourcePath], done: List[SourcePath]) { 86 | todo match { 87 | case Nil => 88 | // nop 89 | case h :: t => 90 | doCompile(h, classPaths ++ done.map(_.targetDir)) 91 | all(t, h :: done) 92 | } 93 | } 94 | 95 | all(sourcePaths, Nil) 96 | } 97 | 98 | } 99 | 100 | class CompilationError(msg: String) extends RuntimeException(msg) 101 | 102 | private class CompilationReporter(val settings: Settings) extends AbstractReporter with Logging 103 | { 104 | val errors = List.newBuilder[String] 105 | 106 | def display(pos: Position, msg: String, severity: Severity) { 107 | val m = Position.formatMessage(pos, msg, true) 108 | if (severity == ERROR) 109 | errors += m 110 | else warn(m) 111 | } 112 | 113 | def displayPrompt() { 114 | 115 | } 116 | } -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/internals/LastModMap.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine.internals 2 | 3 | import java.io.File 4 | import java.util.concurrent.ConcurrentHashMap 5 | 6 | /** 7 | * @author kostas.kougios 8 | * Date: 16/02/13 9 | */ 10 | class LastModMap 11 | { 12 | private val modified = new ConcurrentHashMap[File, java.lang.Long] 13 | 14 | def isMod(f: File) = { 15 | val r = modified.get(f) match { 16 | case null => true 17 | case l => l < f.lastModified 18 | } 19 | r 20 | } 21 | 22 | def updated(f: File) { 23 | modified.put(f, f.lastModified) 24 | } 25 | 26 | def markAllAsModified() { 27 | modified.clear() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/com/googlecode/scalascriptengine/internals/Utils.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine.internals 2 | 3 | import java.io.{File, FileInputStream} 4 | 5 | /** 6 | * @author kostantinos.kougios 7 | * 8 | * 23 Dec 2011 9 | */ 10 | object Utils 11 | { 12 | def toBytes(f: File) = { 13 | val fis = new FileInputStream(f) 14 | try { 15 | val l = f.length() 16 | val a = new Array[Byte](l.toInt) 17 | fis.read(a) 18 | a 19 | } finally { 20 | fis.close 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/scala/Test.scala: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * used to test the scala class loader. Please comment out after 4 | * copying the class file to the testfiles directory. 5 | * 6 | * @author kostantinos.kougios 7 | * 8 | * 23 Dec 2011 9 | */ 10 | //class Test extends TestClassTrait { 11 | // def result = "result" 12 | //} -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/ClassRegistrySuite.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.File 4 | 5 | import com.googlecode.scalascriptengine.classloading.ClassRegistry 6 | import org.scalatest.FunSuite 7 | import org.scalatest.Matchers._ 8 | 9 | /** 10 | * @author kkougios 11 | */ 12 | class ClassRegistrySuite extends FunSuite 13 | { 14 | val sourceDir = new File("testfiles/ScalaClassLoaderSuite/v1") 15 | 16 | test("loads classes") { 17 | val registry = new ClassRegistry(getClass.getClassLoader, Set(sourceDir)) 18 | registry.allClasses.map(_.getName).toSet should be(Set("test.TestDep", "test.TestParam", "test.Test")) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/CompilationSuite.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.File 4 | 5 | import com.googlecode.scalascriptengine.scalascriptengine._ 6 | import org.scalatest.FunSuite 7 | import org.scalatest.Matchers._ 8 | 9 | /** 10 | * @author kostantinos.kougios 11 | * 12 | * 22 Dec 2011 13 | */ 14 | class CompilationSuite extends FunSuite 15 | { 16 | val sourceDir = new File("testfiles/CompilationSuite") 17 | val versionsDir = new File("testfiles/versions") 18 | 19 | test("compile only 1 file") { 20 | val sse = ScalaScriptEngine.withoutRefreshPolicy(SourcePath(new File(sourceDir, "test/Dep1.scala"))) 21 | sse.deleteAllClassesInOutputDirectory() 22 | sse.refresh 23 | val tct = sse.newInstance[TestClassTrait]("test.Dep1") 24 | tct.result should be("Dep1R") 25 | 26 | an[ClassNotFoundException] should be thrownBy { 27 | sse.newInstance[Any]("test.MyClass") 28 | } 29 | } 30 | 31 | test("compile 2 files") { 32 | val sse = ScalaScriptEngine.withoutRefreshPolicy(SourcePath( 33 | Set( 34 | new File(sourceDir, "test/Dep1.scala"), 35 | new File(sourceDir, "test/Dep2.scala") 36 | )) 37 | ) 38 | sse.deleteAllClassesInOutputDirectory() 39 | sse.refresh 40 | sse.newInstance[TestClassTrait]("test.Dep1").result should be("Dep1R") 41 | sse.newInstance[TestClassTrait]("test.Dep2").result should be("Dep2R") 42 | } 43 | 44 | test("invoking compilation listeners") { 45 | val sourceDir1 = new File("testfiles/CompilationSuite1") 46 | var cnt = 0 47 | val config = ScalaScriptEngine.defaultConfig(sourceDir1).copy(compilationListeners = List( 48 | version => cnt += 1 49 | )) 50 | val sse = ScalaScriptEngine.withoutRefreshPolicy(config, Set[File]()) 51 | sse.deleteAllClassesInOutputDirectory() 52 | sse.refresh 53 | 54 | cnt should be(1) 55 | } 56 | 57 | test("multiple source and target dirs") { 58 | val sourceDir1 = new File("testfiles/CompilationSuite1") 59 | val sourceDir2 = new File("testfiles/CompilationSuite2") 60 | val target1 = tmpOutputFolder(1) 61 | val target2 = tmpOutputFolder(2) 62 | val sse = ScalaScriptEngine.withoutRefreshPolicy(List(SourcePath(Set(sourceDir1), target1), SourcePath(Set(sourceDir2), target2))) 63 | sse.deleteAllClassesInOutputDirectory() 64 | 65 | sse.refresh 66 | 67 | new File(target1, "A.class").exists should be(true) 68 | new File(target2, "B.class").exists should be(true) 69 | } 70 | 71 | test("code modifications are reloaded") { 72 | val destDir = newTmpDir("dynamicsrc") 73 | val sse = ScalaScriptEngine.withoutRefreshPolicy(SourcePath(destDir)) 74 | sse.deleteAllClassesInOutputDirectory() 75 | for (i <- 1 to 5) { 76 | copyFromSource(new File(versionsDir, "v1"), destDir) 77 | sse.refresh 78 | val v1 = sse.newInstance[TestClassTrait]("reload.Reload") 79 | v1.result should be("v1") 80 | copyFromSource(new File(versionsDir, "v2"), destDir) 81 | sse.refresh 82 | val v2 = sse.newInstance[TestClassTrait]("reload.Reload") 83 | v2.result should be("v2") 84 | } 85 | } 86 | 87 | test("scala files to compiled classes") { 88 | val sse = ScalaScriptEngine.withoutRefreshPolicy(SourcePath(sourceDir)) 89 | sse.deleteAllClassesInOutputDirectory() 90 | sse.refresh 91 | sse.newInstance[Any]("test.MyClass") 92 | new File(sse.config.targetDirs.head, "test/MyClass.class").exists should be(true) 93 | new File(sse.config.targetDirs.head, "test/Dep1.class").exists should be(true) 94 | } 95 | 96 | test("scala files correct") { 97 | val sse = ScalaScriptEngine.withoutRefreshPolicy(SourcePath(sourceDir)) 98 | sse.deleteAllClassesInOutputDirectory() 99 | sse.refresh 100 | val tct = sse.newInstance[TestClassTrait]("test.MyClass") 101 | tct.result should be("ok") 102 | } 103 | 104 | test("deleteAllClassesInOutputDirectory deletes all class files") { 105 | val sse = ScalaScriptEngine.withoutRefreshPolicy(SourcePath(sourceDir)) 106 | sse.deleteAllClassesInOutputDirectory() 107 | sse.refresh 108 | sse.newInstance[Any]("test.MyClass") 109 | sse.deleteAllClassesInOutputDirectory() 110 | new File(sse.config.targetDirs.head, "test/MyClass.class").exists should be(false) 111 | new File(sse.config.targetDirs.head, "test/Dep1.class").exists should be(false) 112 | } 113 | 114 | private def tmpOutputFolder(i: Int) = { 115 | val dir = new File(System.getProperty("java.io.tmpdir"), "scala-script-engine-classes" + i) 116 | dir.mkdir 117 | dir 118 | } 119 | 120 | } -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/ConstructorsSuite.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import org.scalatest.FunSuite 4 | import org.scalatest.Matchers._ 5 | 6 | /** 7 | * @author kostantinos.kougios 8 | * 9 | * 5 Jan 2012 10 | */ 11 | class ConstructorsSuite extends FunSuite 12 | { 13 | val constructors = new Constructors(classOf[ConstructorsSuiteTest]) 14 | 15 | test("no arg constructor") { 16 | val t = constructors.newInstance 17 | t.s should be("noarg") 18 | } 19 | 20 | test("no arg constructor function") { 21 | val t = constructors.constructor 22 | t().s should be("noarg") 23 | } 24 | 25 | test("one arg constructor") { 26 | val t = constructors.newInstance("one-arg") 27 | t.s should be("one-arg") 28 | } 29 | 30 | test("one arg constructor function") { 31 | val t = constructors.constructorWith1Arg[String] 32 | t("one-arg").s should be("one-arg") 33 | } 34 | 35 | test("two arg constructor") { 36 | val t = constructors.newInstance("two-arg", 2) 37 | t.s should be("two-arg") 38 | t.x should be(2) 39 | } 40 | 41 | test("two arg constructor function") { 42 | val t = constructors.constructorWith2Args[String, Int] 43 | t("two-arg", 2).s should be("two-arg") 44 | t("two-arg", 2).x should be(2) 45 | } 46 | 47 | test("three arg constructor") { 48 | val t = constructors.newInstance("three-arg", 3, 5.toDouble) 49 | t.s should be("three-arg") 50 | t.x should be(3) 51 | t.y should be(5.toDouble) 52 | } 53 | 54 | test("three arg constructor function") { 55 | val t = constructors.constructorWith3Args[String, Int, Double] 56 | val v = t("three-arg", 3, 5.toDouble) 57 | v.s should be("three-arg") 58 | v.x should be(3) 59 | v.y should be(5.toDouble) 60 | } 61 | 62 | test("four arg constructor") { 63 | val t = constructors.newInstance("four-arg", 3, 5.toDouble, true) 64 | t.s should be("four-arg") 65 | t.x should be(3) 66 | t.y should be(5.toDouble) 67 | t.z should be(true) 68 | } 69 | 70 | test("four arg constructor function") { 71 | val t = constructors.constructorWith4Args[String, Int, Double, Boolean] 72 | val v = t("four-arg", 3, 5.toDouble, true) 73 | v.s should be("four-arg") 74 | v.x should be(3) 75 | v.y should be(5.toDouble) 76 | v.z should be(true) 77 | } 78 | } 79 | 80 | class ConstructorsSuiteTest(val s: String, val x: Int, val y: Double, val z: Boolean) 81 | { 82 | def this() = this("noarg", 0, 0, false) 83 | 84 | def this(s: String) = this(s, 1, 0, false) 85 | 86 | def this(s: String, x: Int) = this(s, x, 0, false) 87 | 88 | def this(s: String, x: Int, y: Double) = this(s, x, y, false) 89 | } -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/DevUseIDECompiledClassesOnlySuite.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.File 4 | 5 | import com.googlecode.scalascriptengine.scalascriptengine._ 6 | import org.scalatest.FunSuite 7 | import org.scalatest.Matchers._ 8 | 9 | /** 10 | * @author kkougios 11 | */ 12 | class DevUseIDECompiledClassesOnlySuite extends FunSuite 13 | { 14 | val targetDir = new File("testfiles/ScalaClassLoaderSuite") 15 | // parent classloader will contain scala-lib and all test-compiled classes 16 | val classPath = Set[File]() 17 | 18 | test("using both v1 and v2 classes") { 19 | val destDir = newTmpDir("dynamicclass") 20 | 21 | val sse = new ScalaScriptEngine(Config(sourcePaths = List( 22 | SourcePath(Set(destDir), destDir) 23 | ))) with DevUseIDECompiledClassesOnly 24 | 25 | for (i <- 0 to 9) { 26 | if (i > 0) Thread.sleep(150) 27 | cleanDestinationAndCopyFromSource(new File(targetDir, "v1"), destDir) 28 | val tctV1 = sse.newInstance[TestClassTrait]("test.Test") 29 | val tcpV1 = sse.newInstance[TestParamTrait]("test.TestParam") 30 | tcpV1.result(tctV1) should be("TP:v1") 31 | 32 | sse.classVersion should be(i * 2) 33 | 34 | Thread.sleep(150) 35 | cleanDestinationAndCopyFromSource(new File(targetDir, "v2"), destDir) 36 | 37 | val tcpV2 = sse.newInstance[TestParamTrait]("test.TestParam") 38 | tcpV2.result(tctV1) should be("TP:v1") 39 | 40 | val tctV2 = sse.newInstance[TestClassTrait]("test.Test") 41 | tcpV2.result(tctV2) should be("TP:v2") 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/EndToEndSuite.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.{File, FileWriter} 4 | import java.util.concurrent.atomic.{AtomicBoolean, AtomicInteger, AtomicLong} 5 | 6 | import com.googlecode.concurrent.ExecutorServiceManager 7 | import com.googlecode.scalascriptengine.scalascriptengine._ 8 | import org.scalatest.FunSuite 9 | import org.scalatest.Matchers._ 10 | 11 | /** 12 | * @author kostantinos.kougios 13 | * 14 | * 27 Dec 2011 15 | */ 16 | class EndToEndSuite extends FunSuite 17 | { 18 | val sourceDir = new File("testfiles/EndToEndSuite") 19 | 20 | test("multi-threaded") { 21 | 22 | def write(f: File, version: Int) { 23 | val fw = new FileWriter(f) 24 | try { 25 | fw.write( 26 | """ 27 | package reload 28 | import com.googlecode.scalascriptengine.TestClassTrait 29 | 30 | class Main extends TestClassTrait 31 | { 32 | override def result="v%d" 33 | } 34 | """.format(version)) 35 | } finally { 36 | fw.close 37 | } 38 | } 39 | 40 | val destDir = newTmpDir("dynamicsrc/reload") 41 | val main = new File(destDir, "Main.scala") 42 | write(main, 1) 43 | val sse = ScalaScriptEngine.onChangeRefreshAsynchronously(destDir) 44 | sse.deleteAllClassesInOutputDirectory 45 | sse.refresh 46 | 47 | val errors = new AtomicInteger 48 | val done = new AtomicBoolean(false) 49 | val iterations = new AtomicLong 50 | val executor = ExecutorServiceManager.newSingleThreadExecutor 51 | try { 52 | executor.submit { 53 | ExecutorServiceManager.lifecycle(40, 40) { 54 | _ => 55 | while (!done.get) { 56 | try { 57 | val codeVersion = sse.currentVersion 58 | val t = codeVersion.newInstance[TestClassTrait]("reload.Main") 59 | if (t.result != "v" + codeVersion.version) { 60 | errors.incrementAndGet 61 | println(t.result) 62 | } 63 | 64 | // just to trigger a reload 65 | sse.newInstance[TestClassTrait]("reload.Main") 66 | Thread.sleep(1) 67 | iterations.incrementAndGet 68 | } catch { 69 | case e: Throwable => 70 | errors.incrementAndGet 71 | e.printStackTrace 72 | } 73 | } 74 | } 75 | println("executor finished") 76 | } 77 | var currentVersion = 0 78 | for (i <- 1 to 10) { 79 | if (currentVersion != sse.versionNumber) { 80 | println("version is %d , iterations so far : %d".format(sse.versionNumber, iterations.get)) 81 | currentVersion = sse.versionNumber 82 | Thread.sleep(1100) // make sure filesystem marks the file as modified 83 | write(main, currentVersion + 1) 84 | } 85 | Thread.sleep(500) 86 | errors.get should be(0) 87 | } 88 | done.set(true) 89 | errors.get should be(0) 90 | } finally { 91 | executor.shutdownAndAwaitTermination(1) 92 | sse.shutdown 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/EnhancersSuite.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.File 4 | 5 | import org.scalatest.FunSuite 6 | import org.scalatest.Matchers._ 7 | 8 | /** 9 | * @author kostantinos.kougios 10 | * 11 | * 2 Jan 2012 12 | */ 13 | class EnhancersSuite extends FunSuite 14 | { 15 | val sourceDir = new File("testfiles/FromClasspathFirst") 16 | 17 | test("from classpath first loads the already compiled version") { 18 | val sse = new ScalaScriptEngine(new Config(sourceDir)) with FromClasspathFirst 19 | sse.deleteAllClassesInOutputDirectory 20 | val t = sse.newInstance[TestClassTrait]("test.FromClasspathFirst") 21 | t.result should be("fcf:5") 22 | } 23 | 24 | test("from classpath first loads the script version") { 25 | val sse = new ScalaScriptEngine(new Config(sourceDir)) with RefreshSynchronously with FromClasspathFirst 26 | { 27 | val recheckEveryMillis: Long = 0 28 | } 29 | sse.deleteAllClassesInOutputDirectory 30 | val t = sse.newInstance[TestClassTrait]("test.FCF") 31 | t.result should be("not from classpath") 32 | } 33 | 34 | test("constructors use classpath class") { 35 | val sse = new ScalaScriptEngine(new Config(sourceDir)) with FromClasspathFirst 36 | sse.deleteAllClassesInOutputDirectory 37 | val constructors = sse.constructors[TestClassTrait]("test.FromClasspathFirst") 38 | val t = constructors.newInstance(8) 39 | t.result should be("fcf:8") 40 | } 41 | } -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/EvalCodeSuite.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.util.concurrent.Executors 4 | 5 | import com.googlecode.scalascriptengine.classloading.ClassLoaderConfig 6 | import org.scalatest.FunSuite 7 | import org.scalatest.Matchers._ 8 | import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures} 9 | 10 | import scala.concurrent.{ExecutionContext, Future} 11 | 12 | /** 13 | * @author kostantinos.kougios 14 | * 15 | * 20 Aug 2012 16 | */ 17 | class EvalCodeSuite extends FunSuite with ScalaFutures with IntegrationPatience 18 | { 19 | test("create list") { 20 | val ect = EvalCode.with1Arg[Int => Int, List[Int]]("f", "List(f(5),f(10))") 21 | val v = ect.newInstance 22 | v(x => x * 2) should be(List(10, 20)) 23 | } 24 | 25 | test("typed function") { 26 | val ect = EvalCode.with1Arg[Int => Int, Int]("f", "f(5)") 27 | val v = ect.newInstance 28 | v(x => x * 2) should be(10) 29 | } 30 | 31 | test("typed list") { 32 | val ect = EvalCode.with1Arg[List[Int], Int]("l", "l.sum") 33 | val v = ect.newInstance 34 | v(List(5, 10, 15)) should be(30) 35 | } 36 | 37 | test("typed map") { 38 | val ect = EvalCode.with1Arg[Map[Int, String], String]("m", "m(1)") 39 | val v = ect.newInstance 40 | v(Map(1 -> "x", 2 -> "y")) should be("x") 41 | } 42 | 43 | test("withNoArgs") { 44 | val ect = EvalCode.withoutArgs[Int]("22") 45 | val v = ect.newInstance 46 | v() should be(22) 47 | } 48 | 49 | test("using functions") { 50 | // create a factory that evaluates code that takes 1 string param and returns an Int. 51 | // The string param is named s and the evaluated code is s.toInt. 52 | // In other words, this creates a function: 53 | // (s:String)=>s.toInt 54 | val ect = EvalCode.with1Arg[String, Int]("s", "s.toInt") 55 | 56 | // Now create a new instance of this function 57 | val x = ect.newInstance 58 | 59 | // evaluates f("17") = "17".toInt 60 | x("17") should be(17) 61 | } 62 | 63 | test("constructs src code correctly, 2 args") { 64 | // creates a factory for instantiating a function (Float, Double) => Double 65 | // as (i1:Float,i2:Double)=>i1+i2 66 | val ect = EvalCode.with2Args[Float, Double, Double]("i1", "i2", "i1 + i2") 67 | 68 | // create a new instance of the function 69 | val x = ect.newInstance 70 | 71 | // and apply it 72 | x(12.5f, 2.5) should be(15.0) 73 | } 74 | 75 | test("constructs src code correctly, 3 args") { 76 | // creates a factory for instantiating a function (Float, Double) => Double 77 | // as (i1:Float,i2:Double)=>i1+i2 78 | val ect = EvalCode.with3Args[Int, Int, Int, Int]("i1", "i2", "i3", "i1 + 2*i2 + 3*i3") 79 | 80 | // create a new instance of the function 81 | val x = ect.newInstance 82 | 83 | // and apply it 84 | x(2, 5, 10) should be(42) 85 | } 86 | 87 | test("return type string") { 88 | val ect = EvalCode.with2Args[Float, Double, String]("i1", "i2", "(i1 + i2).toString") 89 | val x = ect.newInstance 90 | x(12.5f, 2.5) should be("15.0") 91 | } 92 | 93 | test("use case 1") { 94 | val sourceDir = new java.io.File("./src/main/scala") 95 | val config = ScalaScriptEngine.defaultConfig(sourceDir).copy( 96 | classLoaderConfig = ClassLoaderConfig.Default.copy( 97 | protectPackages = Set("javax.swing"), 98 | protectClasses = Set("java.lang.Thread") // note: still threads can be created via i.e. Executors 99 | ) 100 | ) 101 | 102 | def eval(code: String) { 103 | val ect = EvalCode.with1Arg[Int, AnyRef]("world", code, config.classLoaderConfig) 104 | val f = ect.newInstance 105 | f(1) 106 | } 107 | 108 | eval("world.asInstanceOf[Object]") 109 | } 110 | 111 | test("can keep classes for later use") { 112 | val num = 10 113 | 114 | val ects = for (i <- 1 to num) yield EvalCode.withoutArgs[Int](s"$i") 115 | 116 | ects.map { ect => ect.newInstance() } should be(1 to num) 117 | } 118 | 119 | test("compilation of multiple different scripts is thread safe") { 120 | val num = 10 121 | implicit val ec = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(num)) 122 | 123 | val results = for (i <- 1 to num) yield 124 | Future(EvalCode.withoutArgs[Int](s"$i")).map(_.newInstance()) 125 | 126 | Future.sequence(results).futureValue should be(1 to num) 127 | } 128 | } 129 | 130 | -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/OnChangeRefreshPolicySuite.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.File 4 | 5 | import com.googlecode.scalascriptengine.scalascriptengine._ 6 | import org.scalatest.FunSuite 7 | import org.scalatest.Matchers._ 8 | 9 | /** 10 | * @author kostantinos.kougios 11 | * 12 | * 25 Dec 2011 13 | */ 14 | class OnChangeRefreshPolicySuite extends FunSuite 15 | { 16 | 17 | val sourceDir = new File("testfiles/versions") 18 | 19 | test("onChangeRefresh: two source/output folders") { 20 | val src1 = newTmpDir("dynamicsrc1") 21 | val src2 = newTmpDir("dynamicsrc2") 22 | val destDir1 = newTmpDir("out1") 23 | val destDir2 = newTmpDir("out2") 24 | val sse = ScalaScriptEngine.onChangeRefresh( 25 | Config( 26 | sourcePaths = List( 27 | SourcePath(Set(src1), destDir1), 28 | SourcePath(Set(src2), destDir2) 29 | ) 30 | ) 31 | , 100 32 | ) 33 | 34 | sse.deleteAllClassesInOutputDirectory() 35 | 36 | copyFromSource(new File("testfiles/src1"), src1) 37 | copyFromSource(new File("testfiles/src2"), src2) 38 | 39 | sse.refresh 40 | 41 | sse.newInstance[TestClassTrait]("test.A").result should be("A") 42 | sse.versionNumber should be(1) 43 | sse.newInstance[TestClassTrait]("test.B").result should be("B") 44 | sse.versionNumber should be(1) 45 | 46 | Thread.sleep(1000) 47 | copyFromSource(new File("testfiles/src3"), src1) 48 | sse.newInstance[TestClassTrait]("test.A").result should be("AMod") 49 | sse.versionNumber should be(2) 50 | sse.newInstance[TestClassTrait]("test.B").result should be("B") 51 | sse.versionNumber should be(2) 52 | } 53 | 54 | test("onChangeRefreshAsynchronously: code modifications are refreshed but control returns immediatelly") { 55 | val destDir = newTmpDir("dynamicsrc") 56 | val sse = ScalaScriptEngine.onChangeRefreshAsynchronously(destDir) 57 | sse.deleteAllClassesInOutputDirectory() 58 | copyFromSource(new File(sourceDir, "v1"), destDir) 59 | sse.refresh 60 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 61 | sse.versionNumber should be(1) 62 | // this should trigger a refresh but on the background 63 | copyFromSource(new File(sourceDir, "v2"), destDir) 64 | // this will trigger the refresh which will occur on a different thread 65 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 66 | sse.versionNumber should be(1) 67 | Thread.sleep(2000) 68 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v2") 69 | sse.versionNumber should be(2) 70 | sse.shutdown 71 | } 72 | 73 | test("onChangeRefreshAsynchronously: code modifications are refreshed but control returns immediatelly even on errors") { 74 | val destDir = newTmpDir("dynamicsrc") 75 | val sse = ScalaScriptEngine.onChangeRefreshAsynchronously(destDir) 76 | sse.deleteAllClassesInOutputDirectory() 77 | copyFromSource(new File(sourceDir, "v1"), destDir) 78 | sse.refresh 79 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 80 | sse.versionNumber should be(1) 81 | copyFromSource(new File("testfiles/erroneous/ve"), destDir) 82 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 83 | Thread.sleep(2000) 84 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 85 | sse.versionNumber should be(1) 86 | 87 | copyFromSource(new File(sourceDir, "v2"), destDir) 88 | // this will trigger the refresh which will occur on a different thread 89 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 90 | sse.versionNumber should be(1) 91 | Thread.sleep(2000) 92 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v2") 93 | sse.versionNumber should be(2) 94 | sse.shutdown 95 | } 96 | 97 | test("onChangeRefresh: code modifications are reloaded immediatelly") { 98 | val destDir = newTmpDir("dynamicsrc") 99 | val sse = ScalaScriptEngine.onChangeRefresh(destDir) 100 | sse.deleteAllClassesInOutputDirectory() 101 | for (i <- 1 to 10) { 102 | copyFromSource(new File(sourceDir, "v1"), destDir) 103 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 104 | if (i == 1) sse.numberOfTimesSourcesTestedForModifications should be(1) 105 | sse.versionNumber should be(i * 2 - 1) 106 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 107 | if (i == 1) sse.numberOfTimesSourcesTestedForModifications should be(2) 108 | sse.versionNumber should be(i * 2 - 1) 109 | Thread.sleep(10) 110 | copyFromSource(new File(sourceDir, "v2"), destDir) 111 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v2") 112 | if (i == 1) sse.numberOfTimesSourcesTestedForModifications should be(3) 113 | sse.versionNumber should be(i * 2) 114 | } 115 | } 116 | 117 | test("onChangeRefresh: code modifications are reloaded according to recheckEveryMillis") { 118 | val destDir = newTmpDir("dynamicsrc") 119 | val sse = ScalaScriptEngine.onChangeRefresh(destDir, 2000) 120 | sse.deleteAllClassesInOutputDirectory() 121 | copyFromSource(new File(sourceDir, "v1"), destDir) 122 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 123 | sse.numberOfTimesSourcesTestedForModifications should be(1) 124 | sse.versionNumber should be(1) 125 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 126 | sse.numberOfTimesSourcesTestedForModifications should be(1) 127 | sse.versionNumber should be(1) 128 | copyFromSource(new File(sourceDir, "v2"), destDir) 129 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 130 | sse.numberOfTimesSourcesTestedForModifications should be(1) 131 | Thread.sleep(2100) 132 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v2") 133 | sse.versionNumber should be(2) 134 | sse.numberOfTimesSourcesTestedForModifications should be(2) 135 | } 136 | 137 | test("onChangeRefresh: code modifications are reloaded according to recheckEveryMillis even when errors") { 138 | val destDir = newTmpDir("dynamicsrc") 139 | val sse = ScalaScriptEngine.onChangeRefresh(destDir, 2000) 140 | sse.deleteAllClassesInOutputDirectory() 141 | copyFromSource(new File(sourceDir, "v1"), destDir) 142 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 143 | sse.versionNumber should be(1) 144 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 145 | sse.versionNumber should be(1) 146 | copyFromSource(new File("testfiles/erroneous/ve"), destDir) 147 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 148 | Thread.sleep(2100) 149 | try { 150 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 151 | } 152 | catch { 153 | case e: Throwable => 154 | } 155 | sse.versionNumber should be(1) 156 | copyFromSource(new File(sourceDir, "v2"), destDir) 157 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 158 | Thread.sleep(2100) 159 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v2") 160 | sse.versionNumber should be(2) 161 | } 162 | } -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/ResourcesSuite.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.File 4 | 5 | import com.googlecode.scalascriptengine.scalascriptengine._ 6 | import org.scalatest.FunSuite 7 | import org.scalatest.Matchers._ 8 | 9 | /** 10 | * @author kostantinos.kougios 11 | * 12 | * 29 Dec 2011 13 | */ 14 | class ResourcesSuite extends FunSuite 15 | { 16 | val sourceDir = new File("testfiles/ResourcesSuite") 17 | 18 | val in = getClass.getResourceAsStream("version.txt") 19 | val src = scala.io.Source.fromInputStream(in) 20 | 21 | test("loads resources from classpath") { 22 | val sse = ScalaScriptEngine.withoutRefreshPolicy(SourcePath(new File(sourceDir, "v1"))) 23 | sse.deleteAllClassesInOutputDirectory() 24 | sse.refresh 25 | 26 | val t: TestClassTrait = sse.newInstance[TestClassTrait]("reload.Main") 27 | t.result should be("v1") 28 | } 29 | 30 | test("loads changed resources from classpath without refreshing") { 31 | val destDir = newTmpDir("dynamicsrc") 32 | val sse = ScalaScriptEngine.withoutRefreshPolicy(SourcePath(destDir)) 33 | sse.deleteAllClassesInOutputDirectory() 34 | copyFromSource(new File(sourceDir, "v1"), destDir) 35 | sse.refresh 36 | 37 | val t1: TestClassTrait = sse.newInstance[TestClassTrait]("reload.Main") 38 | t1.result should be("v1") 39 | 40 | copyFromSource(new File(sourceDir, "v2"), destDir) 41 | val t2: TestClassTrait = sse.newInstance[TestClassTrait]("reload.Main") 42 | t2.result should be("v2") 43 | } 44 | 45 | test("loads changed resources from classpath with refreshing") { 46 | val destDir = newTmpDir("dynamicsrc") 47 | val sse = ScalaScriptEngine.withoutRefreshPolicy(SourcePath(destDir)) 48 | sse.deleteAllClassesInOutputDirectory() 49 | copyFromSource(new File(sourceDir, "v1"), destDir) 50 | sse.refresh 51 | 52 | val t1: TestClassTrait = sse.newInstance[TestClassTrait]("reload.Main") 53 | t1.result should be("v1") 54 | 55 | copyFromSource(new File(sourceDir, "v2"), destDir) 56 | sse.refresh 57 | val t2: TestClassTrait = sse.newInstance[TestClassTrait]("reload.Main") 58 | t2.result should be("v2") 59 | } 60 | } -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/SandboxAllowOnlySuite.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.File 4 | import java.security.AccessControlException 5 | 6 | import com.googlecode.scalascriptengine.classloading.ClassLoaderConfig 7 | import org.scalatest.FunSuite 8 | import org.scalatest.Matchers._ 9 | 10 | /** 11 | * @author kostantinos.kougios 12 | * 13 | * 12 Oct 2012 14 | */ 15 | class SandboxAllowOnlySuite extends FunSuite 16 | { 17 | val sourceDir = new File("testfiles/SandboxAllowOnlySuite") 18 | val allowedPackages = Set( 19 | "java.lang", 20 | "scala", 21 | "com.googlecode.scalascriptengine") 22 | val config = ScalaScriptEngine.defaultConfig(sourceDir).copy( 23 | classLoaderConfig = ClassLoaderConfig.Default.copy( 24 | allowed = { 25 | (pckg, _) => 26 | allowedPackages(pckg) || pckg == "test" 27 | } 28 | ) 29 | ) 30 | val sse = ScalaScriptEngine.onChangeRefresh(config, 5) 31 | sse.deleteAllClassesInOutputDirectory() 32 | sse.refresh 33 | 34 | test("allow only specific packages, positive") { 35 | val ex = intercept[AccessControlException] { 36 | val t = sse.newInstance[TestClassTrait]("test.TryPackage") 37 | t.result 38 | } 39 | ex.getMessage should be("access to class javax.swing.Icon not allowed") 40 | } 41 | 42 | test("allow only specific packages, negative") { 43 | val t = sse.newInstance[TestClassTrait]("test.TryPackageAllow") 44 | t.result should be("allowed") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/SandboxSuite.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.File 4 | import java.security.AccessControlException 5 | 6 | import com.googlecode.scalascriptengine.classloading.ClassLoaderConfig 7 | import org.scalatest.Matchers._ 8 | import org.scalatest.{ BeforeAndAfterAll, FunSuite } 9 | 10 | /** 11 | * @author konstantinos.kougios 12 | * 13 | * 7 Oct 2012 14 | */ 15 | class SandboxSuite extends FunSuite with BeforeAndAfterAll 16 | { 17 | val sourceDir = new File("testfiles/SandboxSuite") 18 | val config = ScalaScriptEngine.defaultConfig(sourceDir).copy( 19 | classLoaderConfig = ClassLoaderConfig.Default.copy( 20 | protectPackages = Set("javax.swing"), 21 | protectClasses = Set("java.lang.Thread") // note: still threads can be created via i.e. Executors 22 | ) 23 | ) 24 | System.setProperty("script.classes", config.targetDirs.head.toURI.toString) 25 | 26 | val policy = new File("testfiles/SandboxSuite/test.policy") 27 | System.setProperty("java.security.policy", policy.toURI.toString) 28 | val sseSM = new SSESecurityManager(new SecurityManager) 29 | System.setSecurityManager(sseSM) 30 | System.getSecurityManager should be theSameInstanceAs sseSM 31 | 32 | val sse = ScalaScriptEngine.onChangeRefresh(config, 5) 33 | sse.deleteAllClassesInOutputDirectory() 34 | sse.refresh 35 | 36 | test("will prevent access of a package") { 37 | val ex = intercept[AccessControlException] { 38 | sse.newInstance[TestClassTrait]("test.TryPackage").result 39 | } 40 | ex.getMessage should be("access to class javax.swing.Icon not allowed") 41 | } 42 | 43 | test("will prevent creating a thread") { 44 | val ex = intercept[AccessControlException] { 45 | sse.newInstance[TestClassTrait]("test.TryThread").result 46 | } 47 | ex.getMessage should be("access to class java.lang.Thread not allowed") 48 | } 49 | 50 | test("will prevent access to a file") { 51 | val ex = intercept[AccessControlException] { 52 | sseSM.secured { 53 | val tct = sse.newInstance[TestClassTrait]("test.TryFile") 54 | tct.result should be("directory") 55 | } 56 | } 57 | ex.getPermission match { 58 | case fp: java.io.FilePermission if fp.getActions == "read" && fp.getName == "/tmp" => 59 | // ok 60 | case _ => throw ex 61 | } 62 | } 63 | 64 | test("will allow access to a file") { 65 | sseSM.secured { 66 | val tct = sse.newInstance[TestClassTrait]("test.TryHome") 67 | tct.result should be("directory") 68 | } 69 | } 70 | 71 | test("sandbox eval") { 72 | intercept[AccessControlException] { 73 | val ect = EvalCode.with1Arg[String, String]("s", "s+classOf[java.lang.Thread].getName", config.classLoaderConfig) 74 | val f = ect.newInstance 75 | f("hi") 76 | } 77 | } 78 | 79 | override def afterAll = 80 | System.setSecurityManager(null) 81 | } 82 | -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/StressTestPermGen.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | /** 4 | * checks if we leak into perm-gen 5 | * 6 | * @author kostas.kougios 7 | * Date: 16/05/14 8 | */ 9 | object StressTestPermGen extends App 10 | { 11 | (1 to 1000000).par.foreach { 12 | i => 13 | val ect = EvalCode.with1Arg[String, Int]("s", s"s.toInt+${i}") 14 | 15 | // Now create a new instance of this function 16 | val x = ect.newInstance 17 | x("15") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/TestClassTrait.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | /** 4 | * inherited by compiled test classes 5 | * 6 | * @author kostantinos.kougios 7 | * 8 | * 22 Dec 2011 9 | */ 10 | trait TestClassTrait 11 | { 12 | def result: String 13 | } 14 | 15 | trait TestParamTrait 16 | { 17 | def result(tct: TestClassTrait): String 18 | } 19 | 20 | trait TestValTrait 21 | { 22 | val x: Int 23 | } -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/TimedRefreshPolicySuite.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.File 4 | 5 | import com.googlecode.scalascriptengine.scalascriptengine._ 6 | import org.joda.time.DateTime 7 | import org.scalatest.FunSuite 8 | import org.scalatest.Matchers._ 9 | 10 | /** 11 | * @author kostantinos.kougios 12 | * 13 | * 25 Dec 2011 14 | */ 15 | class TimedRefreshPolicySuite extends FunSuite 16 | { 17 | 18 | val sourceDir = new File("testfiles/versions") 19 | 20 | test("after compilation error, valid version is used") { 21 | val destDir = newTmpDir("dynamicsrc") 22 | val sse = ScalaScriptEngine.timedRefresh(destDir, () => DateTime.now.plusMillis(500)) 23 | sse.deleteAllClassesInOutputDirectory 24 | try { 25 | copyFromSource(new File(sourceDir, "v1/reload"), destDir) 26 | sse.refresh 27 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 28 | copyFromSource(new File("testfiles/erroneous/ve/reload"), destDir) 29 | Thread.sleep(3000) 30 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 31 | copyFromSource(new File(sourceDir, "v2/reload"), destDir) 32 | Thread.sleep(3000) 33 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v2") 34 | } finally { 35 | sse.shutdown 36 | } 37 | } 38 | test("code modifications are reloaded in time") { 39 | val destDir = newTmpDir("dynamicsrc") 40 | val sse = ScalaScriptEngine.timedRefresh(destDir, () => DateTime.now.plusMillis(500)) 41 | sse.deleteAllClassesInOutputDirectory 42 | try { 43 | copyFromSource(new File(sourceDir, "v1/reload"), destDir) 44 | sse.refresh 45 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 46 | copyFromSource(new File(sourceDir, "v2/reload"), destDir) 47 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 48 | Thread.sleep(3000) 49 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v2") 50 | copyFromSource(new File(sourceDir, "v1/reload"), destDir) 51 | Thread.sleep(3000) 52 | sse.newInstance[TestClassTrait]("reload.Reload").result should be("v1") 53 | } finally { 54 | sse.shutdown 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/internals/ScalaClassLoaderSuite.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine.internals 2 | 3 | import java.io.File 4 | 5 | import com.googlecode.scalascriptengine.classloading.{ClassLoaderConfig, ScalaClassLoader} 6 | import com.googlecode.scalascriptengine.scalascriptengine._ 7 | import com.googlecode.scalascriptengine.{TestClassTrait, TestParamTrait} 8 | import org.scalatest.{FunSuite, Matchers} 9 | 10 | /** 11 | * @author kostantinos.kougios 12 | * 13 | * 23 Dec 2011 14 | */ 15 | class ScalaClassLoaderSuite extends FunSuite with Matchers 16 | { 17 | val sourceDir = new File("testfiles/ScalaClassLoaderSuite") 18 | // parent classloader will contain scala-lib and all test-compiled classes 19 | val classPath = Set[File]() 20 | 21 | def classLoader(sourceDir: File, classPath: Set[File], config: ClassLoaderConfig = ClassLoaderConfig.Default) = 22 | new ScalaClassLoader(Set(sourceDir), classPath, Thread.currentThread.getContextClassLoader, config) 23 | 24 | test("class registry") { 25 | val cl = classLoader(new File(sourceDir, "v1"), Set(), config = ClassLoaderConfig.Default.copy(enableClassRegistry = true)) 26 | cl.all.map(_.getName).toSet should be(Set("test.TestDep", "test.TestParam", "test.Test")) 27 | } 28 | 29 | test("classes of type") { 30 | val cl = classLoader(new File(sourceDir, "v1"), Set(), config = ClassLoaderConfig.Default.copy(enableClassRegistry = true)) 31 | cl.withTypeOf[TestParamTrait] should be(List(cl.get("test.TestParam"))) 32 | } 33 | 34 | test("listeners are invoked") { 35 | val destDir = newTmpDir("defaultpackage") 36 | cleanDestinationAndCopyFromSource(new File(sourceDir, "default"), destDir) 37 | var count = 0 38 | val scl = new ScalaClassLoader( 39 | Set(destDir), 40 | classPath, 41 | Thread.currentThread.getContextClassLoader, 42 | ClassLoaderConfig.Default.copy(classLoadingListeners = ((className: String, clz: Class[_]) => { 43 | if (className == "Test" && classOf[TestClassTrait].isAssignableFrom(clz)) count += 1 44 | }) :: Nil)) 45 | scl.newInstance[TestClassTrait]("Test") 46 | count should be(1) 47 | } 48 | 49 | test("load a class on the default package") { 50 | val destDir = newTmpDir("defaultpackage") 51 | cleanDestinationAndCopyFromSource(new File(sourceDir, "default"), destDir) 52 | val scl = classLoader(destDir, classPath) 53 | val tct = scl.newInstance[TestClassTrait]("Test") 54 | tct.result should be("result") 55 | } 56 | 57 | test("will load a class") { 58 | val destDir = newTmpDir("dynamicclass") 59 | cleanDestinationAndCopyFromSource(new File(sourceDir, "v1"), destDir) 60 | val scl = classLoader(destDir, classPath) 61 | val tct = scl.newInstance[TestClassTrait]("test.Test") 62 | tct.result should be("v1") 63 | } 64 | 65 | test("loads dependent classes") { 66 | val destDir = newTmpDir("dynamicclass") 67 | cleanDestinationAndCopyFromSource(new File(sourceDir, "v1"), destDir) 68 | val scl = classLoader(destDir, classPath) 69 | val tctV1 = scl.newInstance[TestClassTrait]("test.TestDep") 70 | tctV1.result should be("TestDep:v1") 71 | } 72 | 73 | test("using both v1 and v2 classes") { 74 | val destDir = newTmpDir("dynamicclass") 75 | cleanDestinationAndCopyFromSource(new File(sourceDir, "v1"), destDir) 76 | val scl1 = classLoader(destDir, classPath) 77 | 78 | val tctV1 = scl1.newInstance[TestClassTrait]("test.Test") 79 | val tcpV1 = scl1.newInstance[TestParamTrait]("test.TestParam") 80 | tcpV1.result(tctV1) should be("TP:v1") 81 | 82 | cleanDestinationAndCopyFromSource(new File(sourceDir, "v2"), destDir) 83 | val scl2 = classLoader(destDir, classPath) 84 | 85 | val tcpV2 = scl2.newInstance[TestParamTrait]("test.TestParam") 86 | tcpV2.result(tctV1) should be("TP:v1") 87 | 88 | val tctV2 = scl2.newInstance[TestClassTrait]("test.Test") 89 | tcpV2.result(tctV2) should be("TP:v2") 90 | } 91 | 92 | // test("stress test loads the same class") { 93 | // val destDir = newTmpDir("dynamicclass") 94 | // cleanDestinationAndCopyFromSource(new File(sourceDir, "v1"), destDir) 95 | // val scl = classLoader(destDir, classPath) 96 | // 97 | // for (j <- 0 to 1000) { 98 | // println("go") 99 | // val start=System.currentTimeMillis 100 | // for (i <- 0 to 100000) { 101 | // scl.newInstance[TestClassTrait]("test.Test") 102 | // } 103 | // val dt=System.currentTimeMillis-start 104 | // println("dt="+dt) 105 | // Thread.sleep(1000) 106 | // } 107 | // } 108 | } -------------------------------------------------------------------------------- /src/test/scala/com/googlecode/scalascriptengine/package.scala: -------------------------------------------------------------------------------- 1 | package com.googlecode.scalascriptengine 2 | 3 | import java.io.File 4 | 5 | import org.apache.commons.io.FileUtils 6 | 7 | /** 8 | * @author kostantinos.kougios 9 | * 10 | * 22 Dec 2011 11 | */ 12 | package object scalascriptengine 13 | { 14 | def tmpDirStr = System.getProperty("java.io.tmpdir") 15 | 16 | def tmpDir = new File(tmpDirStr) 17 | 18 | def deleteDir(dir: File) = { 19 | if (!dir.getAbsolutePath.startsWith(tmpDirStr)) throw new IllegalStateException("only deleting from tmp folder allowed in order to avoid damage") 20 | FileUtils.deleteDirectory(dir) 21 | } 22 | 23 | def newTmpDir(name: String) = { 24 | val dir = new File(tmpDir, name) 25 | if (dir.isDirectory) deleteDir(dir) 26 | dir.mkdirs 27 | dir 28 | } 29 | 30 | def cleanDestinationAndCopyFromSource(src: File, dest: File) { 31 | deleteDir(dest) 32 | copyFromSource(src, dest) 33 | } 34 | 35 | var time = System.currentTimeMillis - 50000 36 | 37 | def copyFromSource(src: File, dest: File) = { 38 | if (!src.exists) throw new IllegalArgumentException(s"dir ${src.getAbsolutePath} doesn't exist") 39 | 40 | def replaceTime(dir: File) { 41 | val files = dir.listFiles 42 | files.filter(_.isDirectory).foreach(d => replaceTime(d)) 43 | files.filter(!_.isDirectory).foreach(_.setLastModified(time)) 44 | } 45 | 46 | FileUtils.copyDirectory(src, dest, false) 47 | replaceTime(dest) 48 | time += 2000 49 | } 50 | 51 | def makeDummyFile(dir: File, name: String, time: Option[Long] = None) = { 52 | val f = new File(dir, name) 53 | FileUtils.touch(f) 54 | time.foreach { 55 | t => 56 | f.setLastModified(t) 57 | } 58 | f 59 | } 60 | } -------------------------------------------------------------------------------- /src/test/scala/examples/CompileChangesButLoadThemWhenReady.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import java.io.File 4 | 5 | import com.googlecode.scalascriptengine.ScalaScriptEngine 6 | 7 | /** 8 | * This example dynamically compiles and reloads changes from the sourceDir. 9 | * When a change is detected, the script engine compiles the source files (non-blocking). 10 | * When compilation is complete, the new version of the class is returned but till then 11 | * any calls to get the class will return the previous version of my.TryMe. 12 | * If there is a compilation error, the script engine returns the previous 13 | * valid version of my.TryMe 14 | * 15 | * This example demonstrates the on-change-refresh-async policy of the script engine. 16 | * 17 | * @author kostantinos.kougios 18 | * 19 | * 26 Dec 2011 20 | */ 21 | object CompileChangesButLoadThemWhenReady extends App 22 | { 23 | val sourceDir = new File("examplefiles/simple") 24 | val sse = ScalaScriptEngine.onChangeRefreshAsynchronously(sourceDir) 25 | // because code changes are loaded async, we need to do a compilation 26 | // before we request a class for the first time otherwise exceptions 27 | // will be thrown till the 1st code version is compiled. 28 | try { 29 | sse.refresh 30 | 31 | while (true) { 32 | val t = sse.newInstance[TryMeTrait]("my.TryMe") 33 | println("code version %d, result : %s".format(sse.versionNumber, t.result)) 34 | Thread.sleep(500) 35 | } 36 | } finally { 37 | // since the async policy uses a background thread, we need to shut 38 | // it down when done. 39 | sse.shutdown 40 | } 41 | } -------------------------------------------------------------------------------- /src/test/scala/examples/FullBlown.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import java.io.File 4 | 5 | import com.googlecode.scalascriptengine.{Config, _} 6 | 7 | /** 8 | * This example shows how to instantiate the script engine without using the factory 9 | * methods. This allows for full customization as there are several traits that can 10 | * be mixed in to provide additional functionality. 11 | * 12 | * The traits mixed in are RefreshAsynchronously and FromClasspathFirst. RefreshAsynchronously 13 | * will do the compilation of changed files in the background and when ready will use the 14 | * new compiled classes. FromClasspathFirst will load the class from the classpath if it exists 15 | * and if not it will compile it. This is handy during dev if i.e. your IDE already compiled 16 | * the scala classes and those are in the classpath. 17 | * 18 | * This example will run continiously, allowing the user to change one of the used 19 | * scala classes. 20 | * 21 | * 22 | * @author kostantinos.kougios 23 | * 24 | * 27 Dec 2011 25 | */ 26 | object FullBlown extends App 27 | { 28 | // the source directory 29 | val sourceDir = new File("examplefiles/simple") 30 | // compilation classpath 31 | val compilationClassPath = ScalaScriptEngine.currentClassPath 32 | // runtime classpath (empty). All other classes are loaded by the parent classloader 33 | val runtimeClasspath = Set[File]() 34 | // the output dir for compiled classes 35 | val outputDir = new File(System.getProperty("java.io.tmpdir"), "scala-script-engine-classes") 36 | outputDir.mkdir 37 | 38 | val sse = new ScalaScriptEngine(Config( 39 | List(SourcePath(Set(sourceDir), outputDir)), 40 | compilationClassPath, 41 | runtimeClasspath 42 | )) with RefreshAsynchronously with FromClasspathFirst 43 | { 44 | val recheckEveryMillis: Long = 1000 // each file will only be checked maximum once per second 45 | } 46 | 47 | // delete all compiled classes (i.e. from previous runs) 48 | sse.deleteAllClassesInOutputDirectory 49 | // since the refresh occurs async, we need to do the 1st refresh otherwise initially my.TryMe 50 | // class will not be found 51 | sse.refresh 52 | 53 | while (true) { 54 | val t = sse.newInstance[TryMeTrait]("my.TryMe") 55 | println("code version %d, result : %s".format(sse.versionNumber, t.result)) 56 | Thread.sleep(500) 57 | } 58 | } -------------------------------------------------------------------------------- /src/test/scala/examples/InstantiatingWithConstructorArgs.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import java.io.File 4 | 5 | import com.googlecode.scalascriptengine.ScalaScriptEngine 6 | 7 | /** 8 | * This example instantiates a dynamically loaded class with 9 | * a 2 parameter constructor. 10 | * 11 | * @author kostantinos.kougios 12 | * 13 | * 5 Jan 2012 14 | */ 15 | object InstantiatingWithConstructorArgs extends App 16 | { 17 | val sourceDir = new File("examplefiles/constructors") 18 | val sse = ScalaScriptEngine.onChangeRefresh(sourceDir) 19 | sse.deleteAllClassesInOutputDirectory 20 | sse.refresh 21 | // get constructors for the current codeversion. 22 | // please note that in order to always get a new instance 23 | // of the latest version of the class you will need 24 | // to always get the constructor from sse before using 25 | // it to instantiate a class 26 | 27 | val constructors = sse.constructors[UserTrait]("my.User") 28 | val user = constructors.newInstance("Kostas Kougios", 10) 29 | println(user) 30 | // prints User(Kostas Kougios,10) 31 | 32 | // now assuming we want to construct multiple instances of 33 | // my.User, we can get a function that will create instances 34 | // every time it is called. This constructor always uses 35 | // the current codeversion and if my.User changes then 36 | // the change will *not* be reflected to the constructor. 37 | val constructor = constructors.constructorWith2Args[String, Int] 38 | println(constructor("Kostas", 5)) // prints User(Kostas,5) 39 | println(constructor("Joe", 15)) // prints User(Joe,15) 40 | } 41 | 42 | trait UserTrait 43 | { 44 | val name: String 45 | val age: Int 46 | } 47 | -------------------------------------------------------------------------------- /src/test/scala/examples/LoadsChangesImmediatelly.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import java.io.File 4 | 5 | import com.googlecode.scalascriptengine.ScalaScriptEngine 6 | 7 | /** 8 | * This example dynamically compiles and reloads changes from the sourceDir. 9 | * When a change is detected, the script engine compiles the source files (blocking). 10 | * When compilation is complete, the new version of the class is returned. 11 | * If there is a compilation error, the script engine returns the previous 12 | * valid version of my.TryMe 13 | * 14 | * This example demonstrates the on-change-refresh policy of the script engine. 15 | * 16 | * @author kostantinos.kougios 17 | * 18 | * 26 Dec 2011 19 | */ 20 | object LoadsChangesImmediatelly extends App 21 | { 22 | 23 | val sourceDir = new File("examplefiles/simple") 24 | val sse = ScalaScriptEngine.onChangeRefresh(sourceDir) 25 | 26 | while (true) { 27 | val t = sse.newInstance[TryMeTrait]("my.TryMe") 28 | println("code version %d, result : %s".format(sse.versionNumber, t.result)) 29 | Thread.sleep(1000) 30 | } 31 | } -------------------------------------------------------------------------------- /src/test/scala/examples/TimedRefresh.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import java.io.File 4 | 5 | import com.googlecode.scalascriptengine.ScalaScriptEngine 6 | import org.joda.time.DateTime 7 | 8 | /** 9 | * demonstrates the timed refresh policy. A background thread scans the 10 | * source folders for changes every second. If a change is detected, 11 | * it re-compiles the source folders and when done, the new code 12 | * version is used. 13 | * 14 | * @author kostantinos.kougios 15 | * 16 | * 27 Dec 2011 17 | */ 18 | object TimedRefresh extends App 19 | { 20 | val sourceDir = new File("examplefiles/simple") 21 | val sse = ScalaScriptEngine.timedRefresh(sourceDir, () => DateTime.now.plusSeconds(1)) 22 | sse.refresh 23 | while (true) { 24 | val t = sse.newInstance[TryMeTrait]("my.TryMe") 25 | println("code version %d, result : %s".format(sse.versionNumber, t.result)) 26 | Thread.sleep(500) 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/scala/examples/TryMeTrait.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | /** 4 | * @author kostantinos.kougios 5 | * 6 | * 26 Dec 2011 7 | */ 8 | trait TryMeTrait 9 | { 10 | def result: String 11 | } -------------------------------------------------------------------------------- /src/test/scala/test/FromClasspathFirst.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import com.googlecode.scalascriptengine.TestClassTrait 4 | 5 | /** 6 | * @author kostantinos.kougios 7 | * 8 | * 2 Jan 2012 9 | */ 10 | class FromClasspathFirst(v: Int) extends TestClassTrait 11 | { 12 | def this() = this(5) 13 | 14 | def result = "fcf:%d".format(v) 15 | } -------------------------------------------------------------------------------- /src/test/scala/test/Test.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | /** 4 | * used to test the scala class loader. Please comment out after 5 | * copying the class file to the testfiles directory. 6 | * 7 | * @author kostantinos.kougios 8 | * 9 | * 23 Dec 2011 10 | */ 11 | //class Test extends TestClassTrait { 12 | // def result = "v2" 13 | //} 14 | // 15 | //class TestDep extends TestClassTrait { 16 | // def result = "TestDep:" + new Test().result 17 | //} 18 | 19 | //class TestParam extends TestParamTrait { 20 | // override def result(tct: TestClassTrait) = "TP:" + tct.result 21 | //} 22 | 23 | //class TestVal extends TestValTrait { 24 | // val x = 2 25 | //} -------------------------------------------------------------------------------- /testfiles/CompilationSuite/test/Dep1.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | class Dep1 extends com.googlecode.scalascriptengine.TestClassTrait 4 | { 5 | def result = "Dep1R" 6 | } -------------------------------------------------------------------------------- /testfiles/CompilationSuite/test/Dep2.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | class Dep2 extends com.googlecode.scalascriptengine.TestClassTrait 4 | { 5 | def result = "Dep2R" 6 | } -------------------------------------------------------------------------------- /testfiles/CompilationSuite/test/MyClass.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | class MyClass extends com.googlecode.scalascriptengine.TestClassTrait 4 | { 5 | val d = new Dep1 6 | 7 | def result = "ok" 8 | } -------------------------------------------------------------------------------- /testfiles/CompilationSuite1/A.scala: -------------------------------------------------------------------------------- 1 | class A 2 | -------------------------------------------------------------------------------- /testfiles/CompilationSuite2/B.scala: -------------------------------------------------------------------------------- 1 | class B -------------------------------------------------------------------------------- /testfiles/FromClasspathFirst/test/FCF.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | /** 4 | * @author kostantinos.kougios 5 | * 6 | * 2 Jan 2012 7 | */ 8 | class FCF extends com.googlecode.scalascriptengine.TestClassTrait 9 | { 10 | def result = "not from classpath" 11 | } -------------------------------------------------------------------------------- /testfiles/FromClasspathFirst/test/FromClasspathFirst.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | /** 4 | * @author kostantinos.kougios 5 | * 6 | * 2 Jan 2012 7 | */ 8 | class FromClasspathFirst extends com.googlecode.scalascriptengine.TestClassTrait 9 | { 10 | def result = "not from classpath" 11 | } -------------------------------------------------------------------------------- /testfiles/ResourcesSuite/v1/reload/Main.scala: -------------------------------------------------------------------------------- 1 | package reload 2 | 3 | import scala.io.Source 4 | 5 | class Main extends com.googlecode.scalascriptengine.TestClassTrait 6 | { 7 | def result = { 8 | val in = getClass.getResourceAsStream("version.txt") 9 | val src = Source.fromInputStream(in) 10 | src.getLines().next 11 | } 12 | } -------------------------------------------------------------------------------- /testfiles/ResourcesSuite/v1/reload/version.txt: -------------------------------------------------------------------------------- 1 | v1 2 | -------------------------------------------------------------------------------- /testfiles/ResourcesSuite/v2/reload/Main.scala: -------------------------------------------------------------------------------- 1 | package reload 2 | 3 | import scala.io.Source 4 | 5 | class Main extends com.googlecode.scalascriptengine.TestClassTrait 6 | { 7 | def result = { 8 | val in = getClass.getResourceAsStream("version.txt") 9 | val src = Source.fromInputStream(in) 10 | src.getLines().next 11 | } 12 | } -------------------------------------------------------------------------------- /testfiles/ResourcesSuite/v2/reload/version.txt: -------------------------------------------------------------------------------- 1 | v2 2 | -------------------------------------------------------------------------------- /testfiles/SandboxAllowOnlySuite/test/TryPackage.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | class TryPackage extends com.googlecode.scalascriptengine.TestClassTrait 4 | { 5 | def result = { 6 | classOf[javax.swing.Icon].getName 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /testfiles/SandboxAllowOnlySuite/test/TryPackageAllow.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | class TryPackageAllow extends com.googlecode.scalascriptengine.TestClassTrait 4 | { 5 | def result = "allowed" 6 | } 7 | -------------------------------------------------------------------------------- /testfiles/SandboxSuite/test.policy: -------------------------------------------------------------------------------- 1 | grant codeBase "file:${user.home}/-" { 2 | permission java.security.AllPermission; 3 | }; 4 | 5 | grant codeBase "${script.classes}/-" { 6 | permission java.io.FilePermission "/home","read"; 7 | }; 8 | -------------------------------------------------------------------------------- /testfiles/SandboxSuite/test/A.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | case class A(name: String) -------------------------------------------------------------------------------- /testfiles/SandboxSuite/test/TryFile.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import java.io.File 4 | 5 | class TryFile extends com.googlecode.scalascriptengine.TestClassTrait 6 | { 7 | def result = { 8 | val a = A("hi") 9 | println(a.name) 10 | val f = new File("/tmp") 11 | if (f.isDirectory) "directory" else "file" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /testfiles/SandboxSuite/test/TryHome.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import java.io.File 4 | 5 | class TryHome extends com.googlecode.scalascriptengine.TestClassTrait 6 | { 7 | def result = { 8 | val f = new File("/home") 9 | if (f.isDirectory) "directory" else "file" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /testfiles/SandboxSuite/test/TryPackage.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | class TryPackage extends com.googlecode.scalascriptengine.TestClassTrait 4 | { 5 | def result = { 6 | classOf[javax.swing.Icon].getName 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /testfiles/SandboxSuite/test/TryThread.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | class TryThread extends com.googlecode.scalascriptengine.TestClassTrait 4 | { 5 | def result = { 6 | new Thread 7 | { 8 | override def run = { 9 | println("TryThread>thread>hacked!") 10 | } 11 | }.start() 12 | Thread.sleep(100) 13 | "TryThread>hacked!" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /testfiles/ScalaClassLoaderSuite/default/Test.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kostaskougios/scalascriptengine/1e2202b601eb2d44096af78f47aa4f4670f168f7/testfiles/ScalaClassLoaderSuite/default/Test.class -------------------------------------------------------------------------------- /testfiles/ScalaClassLoaderSuite/v1/test/Test.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kostaskougios/scalascriptengine/1e2202b601eb2d44096af78f47aa4f4670f168f7/testfiles/ScalaClassLoaderSuite/v1/test/Test.class -------------------------------------------------------------------------------- /testfiles/ScalaClassLoaderSuite/v1/test/TestDep.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kostaskougios/scalascriptengine/1e2202b601eb2d44096af78f47aa4f4670f168f7/testfiles/ScalaClassLoaderSuite/v1/test/TestDep.class -------------------------------------------------------------------------------- /testfiles/ScalaClassLoaderSuite/v1/test/TestParam.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kostaskougios/scalascriptengine/1e2202b601eb2d44096af78f47aa4f4670f168f7/testfiles/ScalaClassLoaderSuite/v1/test/TestParam.class -------------------------------------------------------------------------------- /testfiles/ScalaClassLoaderSuite/v2/test/Test.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kostaskougios/scalascriptengine/1e2202b601eb2d44096af78f47aa4f4670f168f7/testfiles/ScalaClassLoaderSuite/v2/test/Test.class -------------------------------------------------------------------------------- /testfiles/ScalaClassLoaderSuite/v2/test/TestDep.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kostaskougios/scalascriptengine/1e2202b601eb2d44096af78f47aa4f4670f168f7/testfiles/ScalaClassLoaderSuite/v2/test/TestDep.class -------------------------------------------------------------------------------- /testfiles/ScalaClassLoaderSuite/v2/test/TestParam.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kostaskougios/scalascriptengine/1e2202b601eb2d44096af78f47aa4f4670f168f7/testfiles/ScalaClassLoaderSuite/v2/test/TestParam.class -------------------------------------------------------------------------------- /testfiles/erroneous/ve/reload/Reload.scala: -------------------------------------------------------------------------------- 1 | package reload 2 | 3 | class Reload extends com.googlecode.scalascriptengine.TestClassTrait 4 | { 5 | println("ReloadV2x") 6 | 7 | def result = "v2" x 8 | } -------------------------------------------------------------------------------- /testfiles/src1/test/A.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | class A extends com.googlecode.scalascriptengine.TestClassTrait 4 | { 5 | def result = "A" 6 | } 7 | -------------------------------------------------------------------------------- /testfiles/src2/test/B.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | class B extends com.googlecode.scalascriptengine.TestClassTrait 4 | { 5 | def result = "B" 6 | } 7 | -------------------------------------------------------------------------------- /testfiles/src3/test/A.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | class A extends com.googlecode.scalascriptengine.TestClassTrait 4 | { 5 | def result = "AMod" 6 | } 7 | -------------------------------------------------------------------------------- /testfiles/versions/v1/reload/Reload.scala: -------------------------------------------------------------------------------- 1 | package reload 2 | 3 | class Reload extends com.googlecode.scalascriptengine.TestClassTrait 4 | { 5 | def result = "v1" 6 | } -------------------------------------------------------------------------------- /testfiles/versions/v2/reload/Reload.scala: -------------------------------------------------------------------------------- 1 | package reload 2 | 3 | class Reload extends com.googlecode.scalascriptengine.TestClassTrait 4 | { 5 | def result = "v2" 6 | } -------------------------------------------------------------------------------- /wiki/Downloads.md: -------------------------------------------------------------------------------- 1 | Please download from [http://oss.sonatype.org/content/repositories/releases/com/googlecode/scalascriptengine/scalascriptengine/](http://oss.sonatype.org/content/repositories/releases/com/googlecode/scalascriptengine/scalascriptengine/) -------------------------------------------------------------------------------- /wiki/News.md: -------------------------------------------------------------------------------- 1 | ... 2 | 3 | * 01/06/2012 : v0.6.4 is stable enough, maybe promote it to 1.0? 4 | * 19/02/2012 : v0.6.4 : synced with maven central 5 | * 12/01/2012 : v0.6.3 : compilation stopping, status and better error reporting 6 | * 11/01/2012 : v0.6.2 : simplified classloader, compilation files logging and bug fixes 7 | * 06/01/2012 : v0.6.1 : function that acts as constructors for up to 4 arg constructors 8 | * 05/01/2012 : v0.6.0 : easy instantiations of classes with up to 4 arg constructors. 9 | * 02/01/2012 : v0.5.3 : refactored configuration of the script engine and added FromClasspathFirst enhancer trait 10 | * 30/12/2011 : v0.5.2 : Better compilation classpath detection --------------------------------------------------------------------------------