001: package gnu.text;
002:
003: import java.io.*;
004: import java.util.Hashtable;
005: import gnu.lists.Consumer;
006:
007: /**
008: * A wrapper for characters.
009: * #author Per Bothner
010: */
011:
012: /*
013: * This is similar to java.lang.Character, so why don't we just use that?
014: * Good question, since this new class makes us a little less compatible
015: * with "standard" Java. However, that should be fairly minor, since
016: * few methods will require Character parameters or arrays (better to
017: * just use chars then).
018: * The Char class uses hashing to ensure that characters are unique.
019: * Thus equal? Char are eq?, which is convenient.
020: * Also, using our own class lets us make sure it implements Printable.
021: * Finally, we can use 32-bit character values to allow for non-Unicode chars.
022: */
023:
024: public class Char implements
025: /* #ifdef JAVA2 */
026: Comparable,
027: /* #endif */
028: Externalizable {
029: // Leave open the possibility for characters beyond Unicode.
030: int value;
031:
032: /** Should only be used for serialization. */
033: public Char() {
034: }
035:
036: private Char(int ch) {
037: value = ch;
038: }
039:
040: public void print(Consumer out) {
041: print(value, out);
042: }
043:
044: public static void print(int i, Consumer out) {
045: if (i >= 0x10000) {
046: out.write((char) (((i - 0x10000) >> 10) + 0xD800));
047: out.write((char) ((i & 0x3FF) + 0xDC00));
048: } else
049: out.write((char) i);
050: }
051:
052: public final char charValue() {
053: return (char) value;
054: }
055:
056: public final int intValue() {
057: return value;
058: }
059:
060: public int hashCode() {
061: return value;
062: }
063:
064: static Char[] ascii;
065:
066: static Char temp = new Char(0);
067: static Hashtable hashTable;
068:
069: static {
070: ascii = new Char[128];
071: for (int i = 128; --i >= 0;)
072: ascii[i] = new Char(i);
073: }
074:
075: public static Char make(int ch) {
076: if (ch < 128)
077: return ascii[ch];
078: else {
079: // Re-writing this will allow equals to just use ==. FIXME.
080: temp.value = ch;
081: if (hashTable == null)
082: hashTable = new Hashtable();
083: Object entry = hashTable.get(temp);
084: if (entry != null)
085: return (Char) entry;
086: Char newChar = new Char(ch);
087: hashTable.put(newChar, newChar);
088: return newChar;
089: }
090: }
091:
092: public boolean equals(Object obj) {
093: // This does not work for hashing in make! Redo make! FIXME
094: // return this == obj;
095: return obj != null && (obj instanceof Char)
096: && ((Char) obj).intValue() == value;
097: }
098:
099: static char[] charNameValues = { ' ', '\t', '\n', '\n', '\r', '\f',
100: '\b', '\033', '\177', '\177', '\007', '\0' };
101: static String[] charNames = { "space", "tab", "newline",
102: "linefeed", "return", "page", "backspace", "esc", "del",
103: "rubout", "bel", "nul" };
104:
105: public static int nameToChar(String name) {
106: for (int i = charNames.length; --i >= 0;) {
107: if (charNames[i].equals(name))
108: return charNameValues[i];
109: }
110: for (int i = charNames.length; --i >= 0;) {
111: if (charNames[i].equalsIgnoreCase(name))
112: return charNameValues[i];
113: }
114: int len = name.length();
115: if (len > 1 && name.charAt(0) == 'u') {
116: int value = 0;
117: for (int pos = 1;; pos++) {
118: if (pos == len)
119: return value;
120: int dig = Character.digit(name.charAt(pos), 16);
121: if (dig < 0)
122: break;
123: value = (value << 4) + dig;
124: }
125: }
126:
127: // Check for Emacs control character syntax.
128: if (len == 3 && name.charAt(1) == '-') {
129: char ch = name.charAt(0);
130: if (ch == 'c' || ch == 'C') {
131: ch = name.charAt(2);
132: return ch & 31;
133: }
134: }
135:
136: return -1;
137: }
138:
139: public String toString() {
140: StringBuffer buf = new StringBuffer();
141: buf.append('\'');
142: if (value >= (int) ' ' && value < 127 && value != '\'')
143: buf.append((char) value);
144: else {
145: buf.append('\\');
146: if (value == '\'')
147: buf.append('\'');
148: else if (value == '\n')
149: buf.append('n');
150: else if (value == '\r')
151: buf.append('r');
152: else if (value == '\t')
153: buf.append('t');
154: else if (value < 256) {
155: String str = Integer.toOctalString(value);
156: for (int i = 3 - str.length(); --i >= 0;)
157: buf.append('0');
158: buf.append(str);
159: } else {
160: buf.append('u');
161: String str = Integer.toHexString(value);
162: for (int i = 4 - str.length(); --i >= 0;)
163: buf.append('0');
164: buf.append(str);
165: }
166: }
167: buf.append('\'');
168: return buf.toString();
169: }
170:
171: public static String toScmReadableString(int ch) {
172: StringBuffer sbuf = new StringBuffer(20);
173: sbuf.append("#\\");
174: for (int i = 0; i < charNameValues.length; i++) {
175: if ((char) ch == charNameValues[i]) {
176: sbuf.append(charNames[i]);
177: return sbuf.toString();
178: }
179: }
180: if (ch < 8) {
181: sbuf.append('0'); // make sure there at least two octal digits
182: sbuf.append(ch);
183: } else if (ch < ' ' || ch > 0x7F) {
184: sbuf.append(Integer.toString(ch, 8));
185: } else
186: sbuf.append((char) ch);
187: return sbuf.toString();
188: }
189:
190: /**
191: * @serialData Writes the char value as a char.
192: * If the value is > 0xFFFF, write a pair of surrogate values.
193: * If the value is is a high surrogate only, write it followed by '\0'.
194: */
195: public void writeExternal(ObjectOutput out) throws IOException {
196: if (value > 0xD800) {
197: if (value > 0xFFFF) {
198: out.writeChar(((value - 0x10000) >> 10) + 0xD800);
199: value = (value & 0x3FF) + 0xDC00;
200: } else if (value <= 0xDBFF) {
201: out.writeChar(value);
202: value = '\0';
203: }
204: }
205: out.writeChar(value);
206: }
207:
208: public void readExternal(ObjectInput in) throws IOException,
209: ClassNotFoundException {
210: value = in.readChar();
211: if (value >= 0xD800 && value < 0xDBFF) {
212: char next = in.readChar();
213: if (next >= 0xDC00 && next <= 0xDFFF)
214: value = ((value - 0xD800) << 10) + (next - 0xDC00)
215: + 0x10000;
216: }
217: }
218:
219: public Object readResolve() throws ObjectStreamException {
220: return make(value);
221: }
222:
223: public int compareTo(Object o) {
224: return value - ((Char) o).value;
225: }
226: }
|