77
88package org .scalaexercises .evaluator
99
10- import java .io .{ByteArrayOutputStream , File }
10+ import java .io .{ByteArrayOutputStream , Closeable , File }
1111import java .math .BigInteger
12- import java .net .URLClassLoader
1312import java .security .MessageDigest
1413import java .util .jar .JarFile
1514
@@ -24,10 +23,11 @@ import org.scalaexercises.evaluator.{Dependency => EvaluatorDependency}
2423
2524import scala .concurrent .duration ._
2625import scala .reflect .internal .util .{AbstractFileClassLoader , BatchSourceFile , Position }
26+ import scala .reflect .internal .util .ScalaClassLoader .URLClassLoader
2727import scala .tools .nsc .io .{AbstractFile , VirtualDirectory }
2828import scala .tools .nsc .reporters ._
2929import scala .tools .nsc .{Global , Settings }
30- import scala .util .Try
30+ import scala .util .{ Failure , Success , Try }
3131import scala .util .control .NonFatal
3232
3333class Evaluator [F [_]: Sync ](timeout : FiniteDuration = 20 .seconds)(
@@ -83,7 +83,15 @@ class Evaluator[F[_]: Sync](timeout: FiniteDuration = 20.seconds)(
8383 }
8484 } yield artifacts
8585
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 ]) =
8795 new Eval (jars = jars.toList) {
8896 @ volatile var errors : Map [String , List [CompilationInfo ]] = Map .empty
8997
@@ -110,34 +118,32 @@ class Evaluator[F[_]: Sync](timeout: FiniteDuration = 20.seconds)(
110118 errors = Map .empty
111119 }
112120 })
121+
113122 }
114- }
115123
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
118127
119- val outCapture = new ByteArrayOutputStream
128+ F .delay[ EvalResult [ T ]]( Console .withOut( outCapture) {
120129
121- Console .withOut(outCapture) {
130+ val result = Try (evalInstance.execute[ T ](code, resetState = true , jars = jars))
122131
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
127133
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()))
129146
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- }
141147 }
142148
143149 def eval [T ](
@@ -149,7 +155,7 @@ class Evaluator[F[_]: Sync](timeout: FiniteDuration = 20.seconds)(
149155 allJars <- fetchArtifacts(remotes, dependencies)
150156 result <- allJars match {
151157 case Right (jars) =>
152- F .delay[ EvalResult [ T ]]( evaluate(code, jars) )
158+ evaluate(code, jars)
153159 .timeoutTo(timeout, Timeout [T ](timeout).asInstanceOf [EvalResult [T ]].pure[F ])
154160 case Left (fileError) => F .pure(UnresolvedDependency [T ](fileError.describe))
155161 }
@@ -173,9 +179,9 @@ private class StringCompiler(
173179 output : AbstractFile ,
174180 settings : Settings ,
175181 messageHandler : Option [Reporter ]
176- ) {
182+ ) extends Closeable {
177183
178- val cache = new scala.collection.mutable. HashMap [String , Class [_]]()
184+ // val cache = new java.util.concurrent.ConcurrentHashMap [String, Class[_]]
179185
180186 trait MessageCollector {
181187 val messages : scala.collection.mutable.ListBuffer [List [String ]]
@@ -229,23 +235,28 @@ private class StringCompiler(
229235 }
230236 }
231237 }
232- cache.clear()
238+ global.cleanup
239+ // cache.clear()
233240 reporter.reset()
234241 }
235242
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
247247 }
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+ // }
249260
250261 /**
251262 * Compile scala code. It can be found using the above class loader.
@@ -257,7 +268,7 @@ private class StringCompiler(
257268 // ...and 1/2 this line:
258269 compiler.compileSources(sourceFiles)
259270
260- if (reporter.hasErrors || reporter.hasWarnings) {
271+ if (reporter.hasErrors /* || reporter.hasWarnings*/ ) {
261272 val msgs : List [List [String ]] = reporter match {
262273 case collector : MessageCollector =>
263274 collector.messages.toList
@@ -277,12 +288,19 @@ private class StringCompiler(
277288 resetState : Boolean = true ,
278289 classLoader : ClassLoader ): Class [_] = {
279290 synchronized {
280- if (resetState) reset()
281-
282291 apply(code)
283- findClass(className, classLoader).get // fixme
292+ val clazz = findClass(className, classLoader).get // fixme
293+
294+ if (resetState) reset()
295+ clazz
284296 }
285297 }
298+
299+ override def close (): Unit = {
300+ global.cleanup
301+ global.close()
302+ reporter.reset()
303+ }
286304}
287305
288306/**
@@ -351,37 +369,32 @@ class Eval(target: Option[File] = None, jars: List[File] = Nil) {
351369 }
352370
353371 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)
361375
362376 val cls = compiler(
363377 wrapCodeInClass(className, code),
364378 className,
365379 resetState,
366380 classLoader
367381 )
368- cls
382+
383+ val res = cls
369384 .getConstructor()
370385 .newInstance()
371386 .asInstanceOf [() => T ]
372387 .apply()
373388 .asInstanceOf [T ]
389+
390+ urlClassLoader.close()
391+
392+ res
374393 }
375394
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())
385398 }
386399
387400 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 {
459472 val jarFile = currentClassPath(0 )
460473 val relativeRoot =
461474 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+ }
465485 if (nestedClassPath eq null ) {
466486 Nil
467487 } else {
@@ -496,4 +516,5 @@ object Eval {
496516
497517 class CompilerException (val messages : List [List [String ]])
498518 extends Exception (" Compiler exception " + messages.map(_.mkString(" \n " )).mkString(" \n " ))
519+
499520}
0 commit comments