Skip to content

Commit 5867619

Browse files
Merge pull request #2 from cquiroz/java-util-logging-implementation
Implement parts of java.util.logging
2 parents f05e169 + 1069234 commit 5867619

File tree

17 files changed

+1469
-7
lines changed

17 files changed

+1469
-7
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package java.util.logging
2+
3+
// Unfortunately the spec require the use of System.err
4+
class ConsoleHandler extends StreamHandler(System.err, new SimpleFormatter) {
5+
6+
// Overriden on the javadocs but there are no comments about differences
7+
override def publish(record: LogRecord): Unit =
8+
super.publish(record)
9+
10+
override def close(): Unit = {
11+
// Required by javadocs not to close the stream
12+
writeHeader()
13+
writeTail()
14+
flush()
15+
}
16+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package java.util.logging
2+
3+
object ErrorManager {
4+
val GENERIC_FAILURE = 0
5+
val WRITE_FAILURE = 1
6+
val FLUSH_FAILURE = 2
7+
val CLOSE_FAILURE = 3
8+
val OPEN_FAILURE = 4
9+
val FORMAT_FAILURE = 5
10+
}
11+
12+
class ErrorManager() {
13+
14+
// The spec is a bit vague. This will implement the most
15+
// obvious interpretation of outputting only once
16+
private[this] var called = false
17+
18+
def error(msg: String, ex: Exception, code: Int): Unit = {
19+
if (!called) {
20+
// The format is undocumented, this is learned by runtime experimentation
21+
if (msg == null && ex == null) {
22+
System.err.println(code)
23+
} else {
24+
System.err.println(s"$code: $msg")
25+
if (ex == null) ex.printStackTrace(System.err)
26+
}
27+
called = true
28+
}
29+
}
30+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package java.util.logging
2+
3+
trait Filter {
4+
def isLoggable(record: LogRecord): Boolean
5+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package java.util.logging
2+
3+
abstract class Formatter protected () {
4+
5+
def format(record: LogRecord): String
6+
7+
def getHead(h: Handler): String = ""
8+
9+
def getTail(h: Handler): String = ""
10+
11+
def formatMessage(record: LogRecord): String = {
12+
val msg = record.getMessage
13+
val params = record.getParameters
14+
15+
if (params != null && params.length > 0) {
16+
// The Java spec uses java.text formatting not available in Scala.js
17+
// Instead we'll do simple text replacement, very imperative
18+
var msgAccumulator = new StringBuilder()
19+
var inParam = false
20+
var paramInFlight:StringBuilder = null
21+
var substitutionFailure = false // track failure to break the loop
22+
var i = 0
23+
24+
// Do one run over msg keeping track if a param needs replacement
25+
while (i < msg.length && !substitutionFailure) {
26+
val currentChar = msg.charAt(i)
27+
i = i + 1
28+
if (currentChar == '{' && !inParam) {
29+
// Beginning of param
30+
inParam = true
31+
paramInFlight = new StringBuilder()
32+
} else if (inParam && currentChar != '}') {
33+
// accumulate the param
34+
paramInFlight += currentChar
35+
} else if (currentChar == '}') {
36+
// end of param, replace placeholder by value if possible
37+
inParam = false
38+
val (failed, replacement) = {
39+
try {
40+
val index = paramInFlight.toInt
41+
if (index >= 0 && index < params.length) {
42+
(false, params(index).toString)
43+
} else if (index > 0) {
44+
(false, "{" + index + "}")
45+
} else {
46+
// Negative indexes break substitution on the JVM
47+
(true, "")
48+
}
49+
} catch {
50+
case e: Exception =>
51+
// The JVM will halt replacing if it cannot parse one param
52+
(true, "")
53+
}
54+
}
55+
56+
// The JVM will fail if e.g. there are bogus params and would not replace
57+
// any parameter
58+
if (failed) substitutionFailure = failed
59+
else msgAccumulator ++= replacement
60+
} else {
61+
msgAccumulator += currentChar
62+
}
63+
}
64+
65+
if (substitutionFailure || inParam) msg
66+
else msgAccumulator.result()
67+
} else {
68+
msg
69+
}
70+
}
71+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package java.util.logging
2+
3+
import java.nio.charset.{Charset, UnsupportedCharsetException}
4+
5+
abstract class Handler protected () {
6+
7+
private[this] var level: Level = Level.ALL
8+
private[this] var filter: Filter = null
9+
private[this] var formatter: Formatter = null
10+
private[this] var encoding: String = null
11+
private[this] var errorManager: ErrorManager = new ErrorManager()
12+
13+
def publish(record:LogRecord): Unit
14+
15+
def flush(): Unit
16+
17+
def close(): Unit
18+
19+
def setFormatter(formatter: Formatter): Unit = this.formatter = formatter
20+
21+
def getFormatter(): Formatter = formatter
22+
23+
def setEncoding(encoding: String): Unit = {
24+
if (encoding == null) this.encoding = null
25+
else if (Charset.isSupported(encoding)) this.encoding = encoding
26+
else throw new UnsupportedCharsetException(s"$encoding not supported")
27+
}
28+
29+
def getEncoding(): String = encoding
30+
31+
def setFilter(filter: Filter): Unit = this.filter = filter
32+
33+
def getFilter(): Filter = filter
34+
35+
def setErrorManager(errorManager: ErrorManager): Unit =
36+
if (errorManager == null) throw new NullPointerException()
37+
else this.errorManager = errorManager
38+
39+
def getErrorManager(): ErrorManager = errorManager
40+
41+
protected def reportError(msg: String, ex: Exception, code: Int): Unit =
42+
errorManager.error(msg, ex, code)
43+
44+
def setLevel(level: Level): Unit =
45+
if (level == null) throw new NullPointerException()
46+
else this.level = level
47+
48+
def getLevel(): Level = level
49+
50+
def isLoggable(record:LogRecord): Boolean = {
51+
level.intValue() <= record.getLevel.intValue() &&
52+
(filter == null || filter.isLoggable(record))
53+
}
54+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package java.util.logging
2+
3+
object Level {
4+
5+
val OFF: Level = new Level("OFF", Int.MaxValue)
6+
val SEVERE: Level = new Level("SEVERE", 1000)
7+
val WARNING: Level = new Level("WARNING", 900)
8+
val INFO: Level = new Level("INFO", 800)
9+
val CONFIG: Level = new Level("CONFIG", 700)
10+
val FINE: Level = new Level("FINE", 500)
11+
val FINER: Level = new Level("FINER", 400)
12+
val FINEST: Level = new Level("FINEST", 300)
13+
val ALL: Level = new Level("ALL", Int.MinValue)
14+
15+
private lazy val knownLevels = Map[String, Level](OFF.getName -> OFF,
16+
SEVERE.getName -> SEVERE, WARNING.getName -> WARNING,
17+
INFO.getName -> INFO, CONFIG.getName -> CONFIG, FINE.getName -> FINE,
18+
FINER.getName -> FINER, FINEST.getName -> FINEST, ALL.getName -> ALL)
19+
20+
def parse(name: String): Level =
21+
if (name == null) throw new NullPointerException("Name cannot be null")
22+
else knownLevels.getOrElse(name, throw new IllegalArgumentException(""))
23+
}
24+
25+
class Level protected (private[this] val name: String,
26+
private[this] val value: Int,
27+
private[this] val resourceBundleName: String) {
28+
29+
if (name == null)
30+
throw new NullPointerException("Name cannot be null")
31+
32+
protected def this(name: String, value: Int) = this(name, value, null)
33+
34+
def getResourceBundleName(): String = resourceBundleName
35+
36+
def getName(): String = name
37+
38+
// Not implemented, no locale in Scala.js
39+
//def getLocalizedName():String
40+
41+
override def toString(): String = name
42+
43+
def intValue(): Int = value
44+
45+
override def equals(obj: Any): Boolean = obj match {
46+
case l: Level => intValue() == l.intValue()
47+
case _ => false
48+
}
49+
50+
override def hashCode(): Int = value
51+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package java.util.logging
2+
3+
object LogRecord {
4+
protected[logging] var sequence: Long = 0L
5+
}
6+
7+
class LogRecord(private[this] var level: Level, private[this] var msg: String) {
8+
9+
private[this] var sourceClassName: String = null
10+
private[this] var sourceMethodName: String = null
11+
private[this] var params: Array[AnyRef] = null
12+
private[this] var thrown: Throwable = null
13+
private[this] var loggerName: String = null
14+
private[this] var millis: Long = System.currentTimeMillis()
15+
private[this] var threadId: Long = Thread.currentThread().getId
16+
17+
private[this] var sequenceNumber: Long = {
18+
LogRecord.sequence = LogRecord.sequence + 1
19+
LogRecord.sequence
20+
}
21+
22+
def getLoggerName(): String = loggerName
23+
24+
def setLoggerName(loggerName: String): Unit =
25+
this.loggerName = loggerName
26+
27+
// Not implemented, no locale in Scala.js
28+
//def getResourceBundle():ResourceBundle = ???
29+
30+
// Not implemented, no locale in Scala.js
31+
//def setResourceBundle(bundle: ResourceBundle):Unit = ???
32+
33+
// Message is not localizable, return null
34+
def getResourceBundleName(): String = null
35+
36+
// Message is not localizable, no-op
37+
def setResourceBundleName(name: String): Unit = {}
38+
39+
def getLevel(): Level = level
40+
41+
def setLevel(level: Level): Unit = this.level = level
42+
43+
def getSequenceNumber(): Long = sequenceNumber
44+
45+
def setSequenceNumber(seq: Long): Unit = sequenceNumber = seq
46+
47+
def getSourceClassName(): String = sourceClassName
48+
49+
def setSourceClassName(sourceClassName: String): Unit =
50+
this.sourceClassName = sourceClassName
51+
52+
def getSourceMethodName(): String = sourceMethodName
53+
54+
def setSourceMethodName(sourceClassName: String): Unit =
55+
this.sourceMethodName = sourceClassName
56+
57+
def getMessage(): String = msg
58+
59+
def setMessage(message: String): Unit = msg = message
60+
61+
def getParameters(): Array[AnyRef] = params
62+
63+
def setParameters(parameters: Array[AnyRef]): Unit = this.params = parameters
64+
65+
def getThreadID(): Int = threadId.toInt
66+
67+
def setThreadID(threadID: Int): Unit = this.threadId = threadID
68+
69+
def getMillis(): Long = millis
70+
71+
def setMillis(millis: Long): Unit = this.millis = millis
72+
73+
def getThrown(): Throwable = thrown
74+
75+
def setThrown(thrown: Throwable): Unit = this.thrown = thrown
76+
}

0 commit comments

Comments
 (0)