Skip to content

Commit 708f4a8

Browse files
committed
v1.6.1: stability fixes and improvements
1 parent c912c9c commit 708f4a8

File tree

9 files changed

+176
-39
lines changed

9 files changed

+176
-39
lines changed

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,21 +60,25 @@ for a higher control compared to the EasyML Facade.
6060

6161
### Release Notes
6262

63+
Release 1.6.1
64+
- bugfix: EasyML writer flush
65+
- bugfix: XMLWriter text driver attribute validation
66+
- bugfix: XMLWriter and XMLReader allow field names containing "$"
67+
- bugfix: only register AWT strategies if AWT available
68+
69+
6370
Release 1.6.0 (requires Java 9, recommended up to Java 17)
6471
- XMLWriter and XMLReader use getters and setters.
6572
- feature: support for Java 9 modules.
6673
- NON-BACKWARD COMPATIBLE refactor of ReflectionUtil.
67-
68-
69-
Release 1.5.3
7074
- refactor: remove deprecated Class.newInstance() usages.
7175
- refactor: limited reflection usage from Properties, EnumSet, EnumMap,
7276
SingletonList, SingletonSet, SingletonMap strategies.
7377
- feature: added java.util.concurrent.atomic strategies.
7478
- feature: added java.util.Collections emptyList, emptyMap, emptySet strategies.
7579

7680

77-
Release 1.5.2 (requires Java 8, recommended up to Java 9)
81+
Release 1.5.2
7882
- feature: added generic mechanism for cache clearing.
7983

8084

easyml/src/net/sourceforge/easyml/EasyML.java

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
* objects.<br/>
8585
*
8686
* @author Victor Cordis ( cordis.victor at gmail.com)
87-
* @version 1.5.3
87+
* @version 1.6.1
8888
* @see XMLReader
8989
* @see XMLWriter
9090
* @since 1.0
@@ -206,7 +206,9 @@ public static void defaultConfiguration(XMLWriter writer) {
206206
// simple.add(IntStrategy.INSTANCE);
207207
// simple.add(StringStrategy.INSTANCE);
208208
// awt:
209-
composite.add(ColorStrategy.INSTANCE);
209+
if (ColorStrategy.isAvailable()) {
210+
composite.add(ColorStrategy.INSTANCE);
211+
}
210212
// io:
211213
composite.add(ExternalizableStrategy.INSTANCE);
212214
simple.add(FileStrategy.INSTANCE);
@@ -299,7 +301,9 @@ public static void defaultConfiguration(XMLReader reader) {
299301
// simple.put(IntStrategy.NAME, IntStrategy.INSTANCE);
300302
// simple.put(StringStrategy.NAME, StringStrategy.INSTANCE);
301303
// awt:
302-
composite.put(ColorStrategy.NAME, ColorStrategy.INSTANCE);
304+
if (ColorStrategy.isAvailable()) {
305+
composite.put(ColorStrategy.NAME, ColorStrategy.INSTANCE);
306+
}
303307
// io:
304308
composite.put(ExternalizableStrategy.NAME, ExternalizableStrategy.INSTANCE);
305309
simple.put(FileStrategy.NAME, FileStrategy.INSTANCE);
@@ -619,8 +623,11 @@ public XMLReader newReader(Document in) {
619623
public void serialize(Object o, Writer out) {
620624
final XMLWriter writer = this.perThreadWriter.get();
621625
writer.reset(out);
622-
writer.write(o);
623-
writer.flush();
626+
try {
627+
writer.write(o);
628+
} finally {
629+
writer.flush();
630+
}
624631
}
625632

626633
/**
@@ -662,8 +669,11 @@ public String serialize(Object o) {
662669
public void serialize(Object o, Document out) {
663670
final XMLWriter writer = this.perThreadWriter.get();
664671
writer.reset(out);
665-
writer.write(o);
666-
writer.flush();
672+
try {
673+
writer.write(o);
674+
} finally {
675+
writer.flush();
676+
}
667677
}
668678

669679
/**
@@ -727,7 +737,7 @@ public Object deserialize(Document in) {
727737
/**
728738
* Releases the XML writer, if any, belonging to the current thread. If this
729739
* method isn't invoked, the XML writer will be released anyway at the
730-
* current threads death.
740+
* current thread's death.
731741
* <br>
732742
* <b>Note:</b> this is an advanced feature and should be used only if the
733743
* caller knows this thread won't be using this EasyML instance for
@@ -740,7 +750,7 @@ public void releaseCurrentWriter() {
740750
/**
741751
* Releases the XML reader, if any, belonging to the current thread. If this
742752
* method isn't invoked, the XML reader will be released anyway at the
743-
* current threads death.
753+
* current thread's death.
744754
* <br>
745755
* <b>Note:</b> this is an advanced feature and should be used only if the
746756
* caller knows this thread won't be using this EasyML instance for

easyml/src/net/sourceforge/easyml/XMLReaderDOMDriver.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package net.sourceforge.easyml;
2020

21+
import net.sourceforge.easyml.util.XMLUtil;
2122
import org.w3c.dom.Document;
2223
import org.w3c.dom.Element;
2324
import org.w3c.dom.Node;
@@ -31,7 +32,7 @@
3132
* transforming the DOM to text so that it can be inputed as text to EasyML.
3233
*
3334
* @author Victor Cordis ( cordis.victor at gmail.com)
34-
* @version 1.3.9
35+
* @version 1.6.1
3536
* @since 1.1.0
3637
*/
3738
final class XMLReaderDOMDriver extends XMLReader.Driver {
@@ -155,7 +156,7 @@ public String elementName() {
155156
if (this.crt == null) {
156157
throw new IllegalStateException("not at element start or end: " + this.positionDescriptor());
157158
}
158-
return this.crt.getNodeName();
159+
return XMLUtil.unescapeXMLTag(this.crt.getNodeName());
159160
}
160161

161162
/**

easyml/src/net/sourceforge/easyml/XMLReaderTextDriver.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package net.sourceforge.easyml;
2020

21+
import net.sourceforge.easyml.util.XMLUtil;
2122
import org.kxml2.io.KXmlParser;
2223
import org.xmlpull.v1.XmlPullParser;
2324
import org.xmlpull.v1.XmlPullParserException;
@@ -33,7 +34,7 @@
3334
* parameters.
3435
*
3536
* @author Victor Cordis ( cordis.victor at gmail.com)
36-
* @version 1.4.5
37+
* @version 1.6.1
3738
* @since 1.1.0
3839
*/
3940
final class XMLReaderTextDriver extends XMLReader.Driver {
@@ -131,7 +132,7 @@ public String elementName() {
131132
try {
132133
final int eventType = this.parser.getEventType();
133134
if (eventType == XmlPullParser.START_TAG || eventType == XmlPullParser.END_TAG) {
134-
return this.parser.getName();
135+
return XMLUtil.unescapeXMLTag(this.parser.getName());
135136
}
136137
throw new IllegalStateException("expected element start or end: " + this.parser.getLineNumber() + "," + this.parser.getColumnNumber());
137138
} catch (XmlPullParserException xppX) {

easyml/src/net/sourceforge/easyml/XMLWriterDOMDriver.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package net.sourceforge.easyml;
2020

21+
import net.sourceforge.easyml.util.XMLUtil;
2122
import org.w3c.dom.Document;
2223
import org.w3c.dom.Element;
2324
import org.w3c.dom.Node;
@@ -27,7 +28,7 @@
2728
* XML to DOM documents.
2829
*
2930
* @author Victor Cordis ( cordis.victor at gmail.com)
30-
* @version 1.3.9
31+
* @version 1.6.1
3132
* @since 1.1.0
3233
*/
3334
final class XMLWriterDOMDriver extends XMLWriter.Driver {
@@ -61,7 +62,8 @@ public void startElement(String name) {
6162
throw new IllegalStateException("cannot write element start");
6263
}
6364
// update state:
64-
final Element started = this.root.createElement(name);
65+
final String escapedName = XMLUtil.escapeXMLTag(name);
66+
final Element started = this.root.createElement(escapedName);
6567
if (this.crt == null) {
6668
this.root.appendChild(started);
6769
} else {

easyml/src/net/sourceforge/easyml/XMLWriterTextDriver.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
* XML to output streams.
3131
*
3232
* @author Victor Cordis ( cordis.victor at gmail.com)
33-
* @version 1.4.1
33+
* @version 1.6.1
3434
* @since 1.1.0
3535
*/
3636
final class XMLWriterTextDriver extends XMLWriter.Driver {
@@ -73,10 +73,11 @@ public void startElement(String name) {
7373
} else {
7474
writeIndentedLt();
7575
}
76-
this.writer.write(name);
76+
final String escapedName = XMLUtil.escapeXMLTag(name);
77+
this.writer.write(escapedName);
7778
this.writeOneTimeUniqueId(id -> tryWriteAttrEqValue(DTD.ATTRIBUTE_ID, id));
7879
// update state:
79-
this.elementStack.add(name);
80+
this.elementStack.add(escapedName);
8081
this.state = XMLWriter.Driver.STATE_START;
8182
} catch (IOException ioX) {
8283
throw new RuntimeException(ioX);
@@ -123,11 +124,14 @@ private void writeAttrEqValue(String attr, String value) throws IOException {
123124
*/
124125
@Override
125126
public void setAttribute(String attribute, String value) {
127+
if (!XMLUtil.isLegalXMLTag(attribute)) {
128+
throw new IllegalArgumentException("attribute: " + attribute);
129+
}
126130
if (this.state != XMLWriter.Driver.STATE_START) {
127131
throw new IllegalStateException("cannot write element attributes");
128132
}
129133
try {
130-
writeAttrEqValue(XMLUtil.escapeXML(attribute), XMLUtil.escapeXML(value));
134+
writeAttrEqValue(attribute, XMLUtil.escapeXML(value));
131135
} catch (IOException ioX) {
132136
throw new RuntimeException(ioX);
133137
}

easyml/src/net/sourceforge/easyml/marshalling/java/awt/ColorStrategy.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
* thread-safe.
3030
*
3131
* @author Victor Cordis ( cordis.victor at gmail.com)
32-
* @version 1.2.4
32+
* @version 1.6.1
3333
* @since 1.0
3434
*/
3535
public final class ColorStrategy extends AbstractStrategy implements CompositeStrategy<Color> {
@@ -52,12 +52,21 @@ private static Class targetClass() {
5252
try {
5353
// init Color.class by using Class.forName with initialize=false:
5454
return Class.forName("java.awt.Color", false, ColorStrategy.class.getClassLoader());
55-
} catch (ClassNotFoundException neverThrown) {
55+
} catch (ClassNotFoundException unavailableAWT) {
5656
// java.awt.Color should be a part of the JDK.
5757
return null;
5858
}
5959
}
6060

61+
/**
62+
* Returns true if this strategy is available for usage, false if AWT not present.
63+
*
64+
* @return true if AWT present, false otherwise
65+
*/
66+
public static boolean isAvailable() {
67+
return TARGET != null;
68+
}
69+
6170
private ColorStrategy() {
6271
}
6372

easyml/src/net/sourceforge/easyml/util/XMLUtil.java

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,41 @@
1818
*/
1919
package net.sourceforge.easyml.util;
2020

21+
import java.util.regex.Matcher;
22+
import java.util.regex.Pattern;
23+
2124
/**
2225
* XMLUtil utility class used to format the XML element and element attribute
2326
* values by escaping and un-escaping illegal XML characters.
2427
*
2528
* @author Victor Cordis ( cordis.victor at gmail.com)
26-
* @version 1.5.0
29+
* @version 1.6.1
2730
* @since 1.0
2831
*/
2932
public final class XMLUtil {
3033

31-
static final char XML_ILLEGAL_LT = '<';
32-
static final char XML_ILLEGAL_GT = '>';
33-
static final char XML_ILLEGAL_AMP = '&';
34-
static final char XML_ILLEGAL_QUOT = '"';
35-
static final char XML_ILLEGAL_APOS = '\'';
36-
static final char XML_ILLEGAL_CR = '\r';
37-
static final String XML_LEGAL_LT = "&lt;";
38-
static final String XML_LEGAL_GT = "&gt;";
39-
static final String XML_LEGAL_AMP = "&amp;";
40-
static final String XML_LEGAL_QUOT = "&quot;";
41-
static final String XML_LEGAL_APOS = "&apos;";
42-
static final String XML_LEGAL_CR = "&#13;";
34+
/**
35+
* Constant holding the illegal <code>$</code>.
36+
*/
37+
public static final char XML_TAG_ILLEGAL_$ = '$';
38+
/**
39+
* Constant holding the escaped form of <code>$</code>.
40+
*/
41+
public static final String XML_TAG_LEGAL_$ = "_-_";
42+
private static final String XML_TAG_ILLEGAL_$_QUOTED = Matcher.quoteReplacement(Character.toString(XML_TAG_ILLEGAL_$));
43+
private static final Pattern XML_TAG_LEGAL_$_PATTERN = Pattern.compile(Pattern.quote(XML_TAG_LEGAL_$));
44+
private static final char XML_ILLEGAL_LT = '<';
45+
private static final char XML_ILLEGAL_GT = '>';
46+
private static final char XML_ILLEGAL_AMP = '&';
47+
private static final char XML_ILLEGAL_QUOT = '"';
48+
private static final char XML_ILLEGAL_APOS = '\'';
49+
private static final char XML_ILLEGAL_CR = '\r';
50+
private static final String XML_LEGAL_LT = "&lt;";
51+
private static final String XML_LEGAL_GT = "&gt;";
52+
private static final String XML_LEGAL_AMP = "&amp;";
53+
private static final String XML_LEGAL_QUOT = "&quot;";
54+
private static final String XML_LEGAL_APOS = "&apos;";
55+
private static final String XML_LEGAL_CR = "&#13;";
4356

4457
/**
4558
* Returns true if the input text is free from illegal XML characters, false otherwise.
@@ -130,6 +143,49 @@ private static void appendEscaped(StringBuilder destination, char c) {
130143
}
131144
}
132145

146+
/**
147+
* Escapes <code>$</code> in the given tag.
148+
*
149+
* @param tag to escape
150+
* @return the escaped tag
151+
*/
152+
public static String escapeXMLTag(String tag) {
153+
final int symbolIdx = tag.indexOf(XML_TAG_ILLEGAL_$);
154+
if (symbolIdx == -1) {
155+
return tag; // all legal.
156+
}
157+
final int len = tag.length();
158+
final StringBuilder sb = new StringBuilder(len + 10);
159+
// copy initial legal part:
160+
for (int i = 0; i < symbolIdx; i++) {
161+
sb.append(tag.charAt(i));
162+
}
163+
// escape the remaining part:
164+
for (int i = symbolIdx; i < len; i++) {
165+
final char c = tag.charAt(i);
166+
if (c == XML_TAG_ILLEGAL_$) {
167+
sb.append(XML_TAG_LEGAL_$);
168+
} else {
169+
sb.append(c);
170+
}
171+
}
172+
return sb.toString();
173+
}
174+
175+
/**
176+
* Unescapes <code>$</code> in the given tag.
177+
*
178+
* @param tag to unescape
179+
* @return the unescaped tag
180+
*/
181+
public static String unescapeXMLTag(String tag) {
182+
final int escapedIdx = tag.indexOf(XML_TAG_LEGAL_$);
183+
if (escapedIdx == -1) {
184+
return tag; // all legal.
185+
}
186+
return XML_TAG_LEGAL_$_PATTERN.matcher(tag).replaceAll(XML_TAG_ILLEGAL_$_QUOTED);
187+
}
188+
133189
private XMLUtil() {
134190
}
135191
}

0 commit comments

Comments
 (0)