├── .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
--------------------------------------------------------------------------------