7
7
8
8
package org .scalaexercises .evaluator
9
9
10
- import java .io .{ByteArrayOutputStream , File }
10
+ import java .io .{ByteArrayOutputStream , Closeable , File }
11
11
import java .math .BigInteger
12
- import java .net .URLClassLoader
13
12
import java .security .MessageDigest
14
13
import java .util .jar .JarFile
15
14
@@ -24,10 +23,11 @@ import org.scalaexercises.evaluator.{Dependency => EvaluatorDependency}
24
23
25
24
import scala .concurrent .duration ._
26
25
import scala .reflect .internal .util .{AbstractFileClassLoader , BatchSourceFile , Position }
26
+ import scala .reflect .internal .util .ScalaClassLoader .URLClassLoader
27
27
import scala .tools .nsc .io .{AbstractFile , VirtualDirectory }
28
28
import scala .tools .nsc .reporters ._
29
29
import scala .tools .nsc .{Global , Settings }
30
- import scala .util .Try
30
+ import scala .util .{ Failure , Success , Try }
31
31
import scala .util .control .NonFatal
32
32
33
33
class Evaluator [F [_]: Sync ](timeout : FiniteDuration = 20 .seconds)(
@@ -83,7 +83,15 @@ class Evaluator[F[_]: Sync](timeout: FiniteDuration = 20.seconds)(
83
83
}
84
84
} yield artifacts
85
85
86
- def createEval (jars : Seq [File ]) = {
86
+ def fetch (remotes : Seq [Remote ], dependencies : Seq [EvaluatorDependency ]) =
87
+ Fetch [F ](cache)
88
+ .addDependencies(dependencies.map(dependencyToModule): _* )
89
+ .addRepositories(remotes.map(remoteToRepository): _* )
90
+ .addRepositories(coursier.LocalRepositories .ivy2Local)
91
+ .io
92
+ .attempt
93
+
94
+ def createEval (jars : Seq [File ]) =
87
95
new Eval (jars = jars.toList) {
88
96
@ volatile var errors : Map [String , List [CompilationInfo ]] = Map .empty
89
97
@@ -110,34 +118,32 @@ class Evaluator[F[_]: Sync](timeout: FiniteDuration = 20.seconds)(
110
118
errors = Map .empty
111
119
}
112
120
})
121
+
113
122
}
114
- }
115
123
116
- private [this ] def evaluate [T ](code : String , jars : Seq [File ]): EvalResult [T ] = {
117
- val eval = createEval(jars)
124
+ private [this ] def evaluate [T ](code : String , jars : Seq [File ]): F [EvalResult [T ]] = {
125
+ F .bracket(F .delay(createEval(jars))) { evalInstance =>
126
+ val outCapture = new ByteArrayOutputStream
118
127
119
- val outCapture = new ByteArrayOutputStream
128
+ F .delay[ EvalResult [ T ]]( Console .withOut( outCapture) {
120
129
121
- Console .withOut(outCapture) {
130
+ val result = Try (evalInstance.execute[ T ](code, resetState = true , jars = jars))
122
131
123
- val result = for {
124
- _ <- Try (eval.check(code))
125
- result <- Try (eval.execute[T ](code, resetState = true , jars = jars))
126
- } yield result
132
+ val errors = evalInstance.errors
127
133
128
- val errors = eval.errors
134
+ result match {
135
+ case scala.util.Success (r) => EvalSuccess [T ](errors, r, outCapture.toString)
136
+ case scala.util.Failure (t) =>
137
+ t match {
138
+ case e : CompilerException => CompilationError (errors)
139
+ case NonFatal (e) =>
140
+ EvalRuntimeError (errors, Option (RuntimeError (e, None )))
141
+ case e => GeneralError (e)
142
+ }
143
+ }
144
+ })
145
+ }(EI => F .delay(EI .clean()))
129
146
130
- result match {
131
- case scala.util.Success (r) => EvalSuccess [T ](errors, r, outCapture.toString)
132
- case scala.util.Failure (t) =>
133
- t match {
134
- case e : CompilerException => CompilationError (errors)
135
- case NonFatal (e) =>
136
- EvalRuntimeError (errors, Option (RuntimeError (e, None )))
137
- case e => GeneralError (e)
138
- }
139
- }
140
- }
141
147
}
142
148
143
149
def eval [T ](
@@ -149,7 +155,7 @@ class Evaluator[F[_]: Sync](timeout: FiniteDuration = 20.seconds)(
149
155
allJars <- fetchArtifacts(remotes, dependencies)
150
156
result <- allJars match {
151
157
case Right (jars) =>
152
- F .delay[ EvalResult [ T ]]( evaluate(code, jars) )
158
+ evaluate(code, jars)
153
159
.timeoutTo(timeout, Timeout [T ](timeout).asInstanceOf [EvalResult [T ]].pure[F ])
154
160
case Left (fileError) => F .pure(UnresolvedDependency [T ](fileError.describe))
155
161
}
@@ -173,9 +179,9 @@ private class StringCompiler(
173
179
output : AbstractFile ,
174
180
settings : Settings ,
175
181
messageHandler : Option [Reporter ]
176
- ) {
182
+ ) extends Closeable {
177
183
178
- val cache = new scala.collection.mutable. HashMap [String , Class [_]]()
184
+ // val cache = new java.util.concurrent.ConcurrentHashMap [String, Class[_]]
179
185
180
186
trait MessageCollector {
181
187
val messages : scala.collection.mutable.ListBuffer [List [String ]]
@@ -229,23 +235,28 @@ private class StringCompiler(
229
235
}
230
236
}
231
237
}
232
- cache.clear()
238
+ global.cleanup
239
+ // cache.clear()
233
240
reporter.reset()
234
241
}
235
242
236
- def findClass (className : String , classLoader : ClassLoader ): Option [Class [_]] = {
237
- synchronized {
238
- cache.get(className).orElse {
239
- try {
240
- val cls = classLoader.loadClass(className)
241
- cache(className) = cls
242
- Some (cls)
243
- } catch {
244
- case e : ClassNotFoundException => None
245
- }
246
- }
243
+ def findClass (className : String , classLoader : ClassLoader ): Option [Class [_]] =
244
+ Try ( /* cache.getOrElseUpdate(className, */ classLoader.loadClass(className) /* )*/ ) match {
245
+ case Success (cls) => Some (cls)
246
+ case Failure (_) => None
247
247
}
248
- }
248
+ // synchronized {
249
+ // cache.get(className).orElse {
250
+ // try {
251
+ // val cls = classLoader.loadClass(className)
252
+ // cache(className) = cls
253
+ // Some(cls)
254
+ // } catch {
255
+ // case e: ClassNotFoundException => None
256
+ // }
257
+ // }
258
+ // }
259
+ // }
249
260
250
261
/**
251
262
* Compile scala code. It can be found using the above class loader.
@@ -257,7 +268,7 @@ private class StringCompiler(
257
268
// ...and 1/2 this line:
258
269
compiler.compileSources(sourceFiles)
259
270
260
- if (reporter.hasErrors || reporter.hasWarnings) {
271
+ if (reporter.hasErrors /* || reporter.hasWarnings*/ ) {
261
272
val msgs : List [List [String ]] = reporter match {
262
273
case collector : MessageCollector =>
263
274
collector.messages.toList
@@ -277,12 +288,19 @@ private class StringCompiler(
277
288
resetState : Boolean = true ,
278
289
classLoader : ClassLoader ): Class [_] = {
279
290
synchronized {
280
- if (resetState) reset()
281
-
282
291
apply(code)
283
- findClass(className, classLoader).get // fixme
292
+ val clazz = findClass(className, classLoader).get // fixme
293
+
294
+ if (resetState) reset()
295
+ clazz
284
296
}
285
297
}
298
+
299
+ override def close (): Unit = {
300
+ global.cleanup
301
+ global.close()
302
+ reporter.reset()
303
+ }
286
304
}
287
305
288
306
/**
@@ -351,37 +369,32 @@ class Eval(target: Option[File] = None, jars: List[File] = Nil) {
351
369
}
352
370
353
371
def execute [T ](className : String , code : String , resetState : Boolean , jars : Seq [File ]): T = {
354
- val jarUrls = jars
355
- .map(jar => new java.net.URL (s " file:// ${jar.getAbsolutePath}" ))
356
- .toArray
357
- val urlClassLoader =
358
- new URLClassLoader (jarUrls, compiler.getClass.getClassLoader)
359
- val classLoader =
360
- new AbstractFileClassLoader (compilerOutputDir, urlClassLoader)
372
+ val jarUrls = jars.map(jar => new java.net.URL (s " file:// ${jar.getAbsolutePath}" ))
373
+ val urlClassLoader = new URLClassLoader (jarUrls, compiler.getClass.getClassLoader)
374
+ val classLoader = new AbstractFileClassLoader (compilerOutputDir, urlClassLoader)
361
375
362
376
val cls = compiler(
363
377
wrapCodeInClass(className, code),
364
378
className,
365
379
resetState,
366
380
classLoader
367
381
)
368
- cls
382
+
383
+ val res = cls
369
384
.getConstructor()
370
385
.newInstance()
371
386
.asInstanceOf [() => T ]
372
387
.apply()
373
388
.asInstanceOf [T ]
389
+
390
+ urlClassLoader.close()
391
+
392
+ res
374
393
}
375
394
376
- /**
377
- * Check if code is Eval-able.
378
- * @throws CompilerException if not Eval-able.
379
- */
380
- def check (code : String ) = {
381
- val id = uniqueId(code)
382
- val className = " Evaluator__" + id
383
- val wrappedCode = wrapCodeInClass(className, code)
384
- compiler(wrappedCode)
395
+ def clean (): Unit = {
396
+ compiler.close()
397
+ compilerMessageHandler.foreach(_.reset())
385
398
}
386
399
387
400
private [this ] def uniqueId (code : String , idOpt : Option [Int ] = Some (Eval .jvmId)): String = {
@@ -459,9 +472,16 @@ class ${className} extends (() => Any) with java.io.Serializable {
459
472
val jarFile = currentClassPath(0 )
460
473
val relativeRoot =
461
474
new File (jarFile).getParentFile()
462
- val nestedClassPath =
463
- new JarFile (jarFile).getManifest.getMainAttributes
464
- .getValue(" Class-Path" )
475
+ val nestedClassPath = Try {
476
+ val jar = new JarFile (jarFile)
477
+ val CP = jar.getManifest.getMainAttributes.getValue(" Class-Path" )
478
+ jar.close()
479
+ CP
480
+ } match {
481
+ case Success (classPath) => classPath
482
+ case Failure (throwable) =>
483
+ throw new CompilerException (List (List (throwable.getMessage)))
484
+ }
465
485
if (nestedClassPath eq null ) {
466
486
Nil
467
487
} else {
@@ -496,4 +516,5 @@ object Eval {
496
516
497
517
class CompilerException (val messages : List [List [String ]])
498
518
extends Exception (" Compiler exception " + messages.map(_.mkString(" \n " )).mkString(" \n " ))
519
+
499
520
}
0 commit comments