diff --git a/projectDependencies.gradle b/projectDependencies.gradle
index 6ba628d..27aef41 100644
--- a/projectDependencies.gradle
+++ b/projectDependencies.gradle
@@ -100,6 +100,7 @@ project(':sulky-logging') {
description = 'This file is part of the sulky modules. It contains a LoggingPropertyChangeListener and some Swing logging.'
dependencies {
compile libraries.'slf4j-api'
+ testCompile libraries.'logback-classic'
}
}
diff --git a/sulky-formatting/src/main/java/de/huxhorn/sulky/formatting/SimpleXml.java b/sulky-formatting/src/main/java/de/huxhorn/sulky/formatting/SimpleXml.java
index 2106021..4207d5b 100644
--- a/sulky-formatting/src/main/java/de/huxhorn/sulky/formatting/SimpleXml.java
+++ b/sulky-formatting/src/main/java/de/huxhorn/sulky/formatting/SimpleXml.java
@@ -1,6 +1,6 @@
/*
* sulky-modules - several general-purpose modules.
- * Copyright (C) 2007-2011 Joern Huxhorn
+ * Copyright (C) 2007-2014 Joern Huxhorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -17,7 +17,7 @@
*/
/*
- * Copyright 2007-2011 Joern Huxhorn
+ * Copyright 2007-2014 Joern Huxhorn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -60,10 +60,15 @@ private SimpleXml()
*/
public static boolean isValidXMLCharacter(char character)
{
- return character == '\t' || character == '\r' || character == '\n' ||
- (character >= XML_CHAR_RANGE_A_START && character <= XML_CHAR_RANGE_A_END) ||
- (character >= XML_CHAR_RANGE_B_START && character <= XML_CHAR_RANGE_B_END) ||
- (character >= XML_CHAR_RANGE_C_START && character <= XML_CHAR_RANGE_C_END);
+ return isValidXMLCharacter(0xFFFF & character);
+ }
+
+ public static boolean isValidXMLCharacter(int codePoint)
+ {
+ return codePoint == '\t' || codePoint == '\r' || codePoint == '\n' ||
+ (codePoint >= XML_CHAR_RANGE_A_START && codePoint <= XML_CHAR_RANGE_A_END) ||
+ (codePoint >= XML_CHAR_RANGE_B_START && codePoint <= XML_CHAR_RANGE_B_END) ||
+ (codePoint >= XML_CHAR_RANGE_C_START && codePoint <= XML_CHAR_RANGE_C_END);
}
/**
@@ -135,12 +140,12 @@ public static String replaceNonValidXMLCharacters(String in, char replacementCha
if(!isValidXMLCharacter(replacementChar))
{
throw new IllegalArgumentException("Replacement character 0x"
- + Integer.toString(replacementChar, 16) + " is invalid itself!");
+ + Integer.toString(replacementChar, 16).toUpperCase() + " is invalid itself!");
}
for(int i = 0; i < in.length(); i++)
{
- char current = in.charAt(i);
+ int current = in.codePointAt(i);
if(isValidXMLCharacter(current))
{
diff --git a/sulky-formatting/src/test/groovy/de/huxhorn/sulky/formatting/SimpleXmlSpec.groovy b/sulky-formatting/src/test/groovy/de/huxhorn/sulky/formatting/SimpleXmlSpec.groovy
new file mode 100644
index 0000000..b7480ab
--- /dev/null
+++ b/sulky-formatting/src/test/groovy/de/huxhorn/sulky/formatting/SimpleXmlSpec.groovy
@@ -0,0 +1,203 @@
+/*
+ * sulky-modules - several general-purpose modules.
+ * Copyright (C) 2007-2014 Joern Huxhorn
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+/*
+ * Copyright 2007-2014 Joern Huxhorn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package de.huxhorn.sulky.formatting
+
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class SimpleXmlSpec extends Specification {
+
+ def escapedXmlCharacters() {
+ [
+ ['&' as char, '&'],
+ ['<' as char, '<'],
+ ['>' as char, '>'],
+ ['\"' as char, '"'],
+ ]
+ }
+
+ @Unroll
+ def "escape(character=#character) should return #escaped"(char character, String escaped) {
+ when:
+ String result = SimpleXml.escape(''+character)
+
+ then:
+ result == escaped
+
+ where:
+ [character, escaped] << escapedXmlCharacters()
+ }
+
+ def "special case escape(character=0) should return space"() {
+ expect:
+ ' ' == SimpleXml.escape(''+((char)0))
+ }
+
+ @Unroll
+ def "unescape(escaped=#escaped) should return #character"(char character, String escaped) {
+ when:
+ String result = SimpleXml.unescape(escaped)
+
+ then:
+ result == ''+character
+
+ where:
+ [character, escaped] << escapedXmlCharacters()
+ }
+
+
+ /**
+ * Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
+ * (any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.)
+ *
+ * See http://www.w3.org/TR/REC-xml#charsets
+ */
+ def validXmlEdgeCaseChars() {
+ [
+ // @formatter:off
+ [false, 0x8],
+ [true , 0x9],
+ [true , 0xA],
+ [false, 0xB],
+ [false, 0xC],
+ [true , 0xD],
+ [false, 0xE],
+ [false, 0x19],
+ [true, 0x20],
+ [true, 0xD7FF],
+ [false, 0xD800],
+ [false, 0xDFFF],
+ [true, 0xE000],
+ [true, 0xFFFD],
+ [false, 0xFFFE],
+ [false, 0xFFFF],
+ // @formatter:on
+ ]
+ }
+
+ /**
+ * Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
+ * (any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.)
+ *
+ * See http://www.w3.org/TR/REC-xml#charsets
+ */
+ def validXmlEdgeCaseIntegers() {
+ [
+ // @formatter:off
+ [true, 0x10000],
+ [true, 0x10FFFF],
+ [true, 0x10FFFF],
+ [false, 0x110000],
+ // @formatter:on
+ ]
+ }
+
+ @Unroll
+ def "char edge case: isValidXMLCharacter((int)codePoint=#codePoint) should return #valid"(boolean valid, int codePoint) {
+ when:
+ boolean result = SimpleXml.isValidXMLCharacter(codePoint)
+
+ then:
+ result == valid
+
+ where:
+ [valid, codePoint] << validXmlEdgeCaseChars()
+ }
+
+ @Unroll
+ def "integer edge case: isValidXMLCharacter((int)codePoint=#codePoint) should return #valid"(boolean valid, int codePoint) {
+ when:
+ boolean result = SimpleXml.isValidXMLCharacter(codePoint)
+
+ then:
+ result == valid
+
+ where:
+ [valid, codePoint] << validXmlEdgeCaseIntegers()
+ }
+
+ @Unroll
+ def "isValidXMLCharacter((char)codePoint=#codePoint) should return #valid"(boolean valid, int codePoint) {
+ when:
+ boolean result = SimpleXml.isValidXMLCharacter((char)codePoint)
+
+ then:
+ result == valid || codePoint != (codePoint & 0xFFFF)
+
+ where:
+ [valid, codePoint] << validXmlEdgeCaseChars()
+ }
+
+ @Unroll
+ def "replaceNonValidXMLCharacters((char)codePoint=#codePoint)"(boolean valid, int codePoint) {
+ when:
+ char replacement = '#'
+ String input = new StringBuilder().appendCodePoint(codePoint).toString();
+ String result = SimpleXml.replaceNonValidXMLCharacters(input, replacement)
+
+ then:
+ (valid && result == input) || (!valid && result.codePointAt(0) == (int)replacement)
+
+ where:
+ [valid, codePoint] << validXmlEdgeCaseChars()
+ }
+
+ def "replaceNonValidXMLCharacters with invalid replacement character"() {
+ when:
+ SimpleXml.replaceNonValidXMLCharacters('foo', (char)0xFFFF)
+
+ then:
+ IllegalArgumentException ex = thrown()
+ ex.message == 'Replacement character 0xFFFF is invalid itself!'
+ }
+
+ def "replaceNonValidXMLCharacters with valid input"() {
+ when:
+ String input = 'foo'
+ String result = SimpleXml.replaceNonValidXMLCharacters('foo', ' ' as char)
+
+ then:
+ input == result
+ input.is(result)
+ }
+
+ def "replaceNonValidXMLCharacters with some replacement"() {
+ when:
+ String result = SimpleXml.replaceNonValidXMLCharacters('foo\u0019\uD800bar', ' ' as char)
+
+ then:
+ 'foo bar' == result
+ }
+}
diff --git a/sulky-formatting/src/test/java/de/huxhorn/sulky/formatting/SafeStringTest.java b/sulky-formatting/src/test/java/de/huxhorn/sulky/formatting/SafeStringTest.java
index 9300149..d6a5d90 100644
--- a/sulky-formatting/src/test/java/de/huxhorn/sulky/formatting/SafeStringTest.java
+++ b/sulky-formatting/src/test/java/de/huxhorn/sulky/formatting/SafeStringTest.java
@@ -61,6 +61,7 @@ public void showMapRecursionProblem()
b.put("bar", a);
a.put("foo", b);
// the following line will throw an java.lang.StackOverflowError!
+ //noinspection ResultOfMethodCallIgnored
a.toString();
}
@@ -88,6 +89,7 @@ public void showCollectionRecursionProblem()
b.add(a);
a.add(b);
// the following line will throw an java.lang.StackOverflowError!
+ //noinspection ResultOfMethodCallIgnored
a.toString();
}
@@ -150,6 +152,12 @@ public void verifyExceptionWithMessageInToStringWorks()
evaluate(expected, o);
}
+ @Test
+ public void foo()
+ {
+ evaluate("UnproblematicToString", new UnproblematicToString());
+ }
+
@Test
public void verifyRecursiveObjectArray()
{
@@ -311,6 +319,14 @@ private void evaluate(String expected, Object o)
assertEquals(expected, result);
}
+ private static class UnproblematicToString
+ {
+ public String toString()
+ {
+ return "UnproblematicToString";
+ }
+ }
+
private static class ProblematicToString
{
private String message;
diff --git a/sulky-logging/src/main/java/de/huxhorn/sulky/logging/LoggingPropertyChangeListener.java b/sulky-logging/src/main/java/de/huxhorn/sulky/logging/LoggingPropertyChangeListener.java
index f7d8b69..0232773 100644
--- a/sulky-logging/src/main/java/de/huxhorn/sulky/logging/LoggingPropertyChangeListener.java
+++ b/sulky-logging/src/main/java/de/huxhorn/sulky/logging/LoggingPropertyChangeListener.java
@@ -43,7 +43,21 @@
public class LoggingPropertyChangeListener
implements PropertyChangeListener
{
- final Logger logger = LoggerFactory.getLogger(LoggingPropertyChangeListener.class);
+ private final Logger logger;
+
+ public LoggingPropertyChangeListener()
+ {
+ this(LoggerFactory.getLogger(LoggingPropertyChangeListener.class));
+ }
+
+ public LoggingPropertyChangeListener(Logger logger)
+ {
+ this.logger = logger;
+ if(logger == null)
+ {
+ throw new IllegalArgumentException("logger must not be null!");
+ }
+ }
public void propertyChange(PropertyChangeEvent event)
{
diff --git a/sulky-logging/src/test/groovy/de/huxhorn/sulky/logging/LoggingPropertyChangeListenerSpec.groovy b/sulky-logging/src/test/groovy/de/huxhorn/sulky/logging/LoggingPropertyChangeListenerSpec.groovy
new file mode 100644
index 0000000..f7a081c
--- /dev/null
+++ b/sulky-logging/src/test/groovy/de/huxhorn/sulky/logging/LoggingPropertyChangeListenerSpec.groovy
@@ -0,0 +1,94 @@
+/*
+ * sulky-modules - several general-purpose modules.
+ * Copyright (C) 2007-2014 Joern Huxhorn
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+/*
+ * Copyright 2007-2014 Joern Huxhorn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package de.huxhorn.sulky.logging
+
+import ch.qos.logback.core.Appender
+import org.slf4j.LoggerFactory
+import spock.lang.Specification
+
+import java.beans.PropertyChangeEvent;
+
+public class LoggingPropertyChangeListenerSpec extends Specification {
+ def "log a change with default logger"() {
+ setup:
+ ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
+
+ Appender appenderMock = Mock(Appender)
+ root.addAppender(appenderMock);
+
+ when:
+ LoggingPropertyChangeListener instance = new LoggingPropertyChangeListener()
+ def event = new PropertyChangeEvent(new Object(), 'valueName', 'oldValue', 'newValue')
+ instance.propertyChange(event)
+
+ then:
+ 1 * appenderMock.doAppend({
+ assert it.formattedMessage == 'PropertyChangeEvent:\n\tpropertyName=\'valueName\'\n\toldValue=oldValue\n\tnewValue=newValue'
+ assert it.loggerName == 'de.huxhorn.sulky.logging.LoggingPropertyChangeListener'
+ true
+ } )
+
+ }
+
+ def "log a change with custom logger"() {
+ setup:
+ ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
+
+ Appender appenderMock = Mock(Appender)
+ root.addAppender(appenderMock);
+
+ when:
+ LoggingPropertyChangeListener instance = new LoggingPropertyChangeListener(LoggerFactory.getLogger("foo"))
+ def event = new PropertyChangeEvent(new Object(), 'valueName', 'oldValue', 'newValue')
+ instance.propertyChange(event)
+
+ then:
+ 1 * appenderMock.doAppend({
+ assert it.formattedMessage == 'PropertyChangeEvent:\n\tpropertyName=\'valueName\'\n\toldValue=oldValue\n\tnewValue=newValue'
+ assert it.loggerName == 'foo'
+ true
+ } )
+
+ }
+
+ def "null logger in constructor"() {
+ when:
+ new LoggingPropertyChangeListener(null);
+
+ then:
+ IllegalArgumentException ex = thrown()
+ ex.message == 'logger must not be null!'
+ }
+}
diff --git a/sulky-logging/src/test/resources/logback-test.xml b/sulky-logging/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..23212af
--- /dev/null
+++ b/sulky-logging/src/test/resources/logback-test.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ %-5level - %d{HH:mm:ss.SSS} [%thread] - %file:%line - %msg%n%ex{full}
+
+
+
+
+
+
+
+
+