/*
* $Id$
*
* Copyright (c) 2000-2009 by Rodney Kinney, Joel Uckelman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.tools;
import java.awt.Color;
import java.awt.event.InputEvent;
import java.util.Iterator;
import java.util.NoSuchElementException;
import javax.swing.KeyStroke;
import VASSAL.configure.ColorConfigurer;
import VASSAL.configure.HotKeyConfigurer;
import VASSAL.configure.NamedHotKeyConfigurer;
import VASSAL.configure.PropertyExpression;
import VASSAL.configure.StringArrayConfigurer;
/**
* Encodes a sequence of Strings into a single String with a given delimiter.
* Escapes the delimiter character if it occurs in the element strings.
*
* This is a very handy class for storing structured data into flat text and
* quite a bit faster than parsing an XML document.
*
* For example, a structure such as {A,{B,C}} can be encoded with
*
* <pre>
* new SequenceEncoder("A",',').append(new SequenceEncoder("B",',').append("C").getValue()).getValue()
* </pre>
*
* which returns <code>A,B\,C</code>
*
* and then extracted with
*
* <pre>
* SequenceEncoder.Decoder st = new SequenceEncoder.Decoder("A,B\,C",',');
* String A = st.nextToken();
* SequenceEncoder.Decoder BC = new SequenceEncoder.Decoder(st.nextToken(),',');
* String B = BC.nextToken();
* String C = BC.nextToken();
* </pre>
*/
public class SequenceEncoder {
private StringBuilder buffer;
private final char delimit;
public SequenceEncoder(char delimiter) {
delimit = delimiter;
}
public SequenceEncoder(String val, char delimiter) {
this(delimiter);
append(val);
}
public SequenceEncoder append(String s) {
// start the buffer, or add delimiter after previous token
if (buffer == null) {
buffer = new StringBuilder();
}
else {
buffer.append(delimit);
}
if (s != null) {
if (s.endsWith("\\") || (s.startsWith("'") && s.endsWith("'"))) {
buffer.append("'");
appendEscapedString(s);
buffer.append("'");
}
else {
appendEscapedString(s);
}
}
return this;
}
public SequenceEncoder append(char c) {
return append(String.valueOf(c));
}
public SequenceEncoder append(int i) {
return append(String.valueOf(i));
}
public SequenceEncoder append(long l) {
return append(String.valueOf(l));
}
public SequenceEncoder append(double d) {
return append(String.valueOf(d));
}
public SequenceEncoder append(boolean b) {
return append(String.valueOf(b));
}
public SequenceEncoder append(KeyStroke stroke) {
return append(HotKeyConfigurer.encode(stroke));
}
public SequenceEncoder append(NamedKeyStroke stroke) {
return append(NamedHotKeyConfigurer.encode(stroke));
}
public SequenceEncoder append(Color color) {
return append(ColorConfigurer.colorToString(color));
}
public SequenceEncoder append(String[] s) {
return append(StringArrayConfigurer.arrayToString(s));
}
public SequenceEncoder append(PropertyExpression p) {
return append(p.getExpression());
}
public String getValue() {
return buffer != null ? buffer.toString() : null;
}
private void appendEscapedString(String s) {
int begin = 0;
int end = s.indexOf(delimit);
while (begin <= end) {
buffer.append(s.substring(begin, end)).append('\\');
begin = end;
end = s.indexOf(delimit, end + 1);
}
buffer.append(s.substring(begin));
}
public static class Decoder implements Iterator<String> {
private String val;
private final char delimit;
public Decoder(String value, char delimiter) {
val = value;
delimit = delimiter;
}
public boolean hasMoreTokens() {
return val != null;
}
public String nextToken() {
if (!hasMoreTokens()) throw new NoSuchElementException();
String value;
final int i = val.indexOf(delimit);
if (i < 0) {
value = val;
val = null;
}
else {
final StringBuilder buffer = new StringBuilder();
int begin = 0;
int end = i;
while (begin < end) {
if (val.charAt(end - 1) == '\\') {
buffer.append(val.substring(begin, end - 1));
begin = end;
end = val.indexOf(delimit, end + 1);
}
else {
break;
}
}
if (end < 0) {
buffer.append(val.substring(begin));
val = null;
}
else {
buffer.append(val.substring(begin, end));
val = end >= val.length() - 1 ? "" : val.substring(end + 1);
}
value = buffer.toString();
}
if (value.startsWith("'") && value.endsWith("'") && value.length() > 1) {
value = value.substring(1, value.length() - 1);
}
return value;
}
public boolean hasNext() {
return hasMoreTokens();
}
public String next() {
return nextToken();
}
public void remove() {
throw new UnsupportedOperationException();
}
public Decoder copy() {
return new Decoder(val, delimit);
}
/**
* Parse the next token into an integer
* @param defaultValue Return this value if no more tokens, or next token doesn't parse to an integer
* @return
*/
public int nextInt(int defaultValue) {
if (val != null) {
try {
defaultValue = Integer.parseInt(nextToken());
}
catch (NumberFormatException e) {
}
}
return defaultValue;
}
public long nextLong(long defaultValue) {
if (val != null) {
try {
defaultValue = Long.parseLong(nextToken());
}
catch (NumberFormatException e) {
}
}
return defaultValue;
}
public double nextDouble(double defaultValue) {
if (val != null) {
try {
defaultValue = Double.parseDouble(nextToken());
}
catch (NumberFormatException e) {
}
}
return defaultValue;
}
public boolean nextBoolean(boolean defaultValue) {
return val != null ? "true".equals(nextToken()) : defaultValue;
}
/**
* Return the first character of the next token
* @param defaultValue Return this value if no more tokens, or if next token has zero length
* @return
*/
public char nextChar(char defaultValue) {
if (val != null) {
final String s = nextToken();
defaultValue = s.length() > 0 ? s.charAt(0) : defaultValue;
}
return defaultValue;
}
public KeyStroke nextKeyStroke(char defaultValue) {
return nextKeyStroke(
KeyStroke.getKeyStroke(defaultValue, InputEvent.CTRL_MASK));
}
public Color nextColor(Color defaultValue) {
if (val != null) {
final String s = nextToken();
if (s.length() > 0) {
defaultValue = ColorConfigurer.stringToColor(s);
}
else {
defaultValue = null;
}
}
return defaultValue;
}
public KeyStroke nextKeyStroke(KeyStroke defaultValue) {
if (val != null) {
final String s = nextToken();
if (s.length() == 0) {
defaultValue = null;
}
else if (s.indexOf(',') < 0) {
defaultValue =
KeyStroke.getKeyStroke(s.charAt(0), InputEvent.CTRL_MASK);
}
else {
defaultValue = HotKeyConfigurer.decode(s);
}
}
return defaultValue;
}
public NamedKeyStroke nextNamedKeyStroke(char defaultValue) {
return nextNamedKeyStroke(NamedKeyStroke.getNamedKeyStroke(defaultValue, InputEvent.CTRL_MASK));
}
public NamedKeyStroke nextNamedKeyStroke() {
return nextNamedKeyStroke(NamedKeyStroke.NULL_KEYSTROKE);
}
public NamedKeyStroke nextNamedKeyStroke(NamedKeyStroke defaultValue) {
if (val != null) {
String s = nextToken();
if (s.length() == 0) {
defaultValue = null;
}
else if (s.indexOf(',') < 0) {
defaultValue = NamedKeyStroke.getNamedKeyStroke(s.charAt(0), InputEvent.CTRL_MASK);
}
else {
defaultValue = NamedHotKeyConfigurer.decode(s);
}
}
return defaultValue == null ? NamedKeyStroke.NULL_KEYSTROKE : defaultValue;
}
/**
* Return the next token, or the default value if there are no more tokens
* @param defaultValue
* @return
*/
public String nextToken(String defaultValue) {
return val != null ? nextToken() : defaultValue;
}
public String[] nextStringArray(int minLength) {
String[] retVal;
if (val != null) {
retVal = StringArrayConfigurer.stringToArray(nextToken());
}
else {
retVal = new String[0];
}
if (retVal.length < minLength) {
retVal = ArrayUtils.copyOf(retVal, minLength);
}
return retVal;
}
}
public static void main(String args[]) {
SequenceEncoder se = new SequenceEncoder(',');
for (int i = 0; i < args.length; ++i) {
se.append(args[i]);
}
System.out.println(se.getValue());
SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(se.getValue(), ',');
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
}
}