001: /*
002: * Copyright 2001-2005 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.nio.cs.ext;
027:
028: import java.util.Collections;
029: import java.util.ArrayList;
030: import java.util.HashMap;
031: import java.util.List;
032: import java.util.Map;
033: import java.nio.charset.*;
034:
035: final class CompoundTextSupport {
036:
037: private static final class ControlSequence {
038:
039: final int hash;
040: final byte[] escSequence;
041: final byte[] encoding;
042:
043: ControlSequence(byte[] escSequence) {
044: this (escSequence, null);
045: }
046:
047: ControlSequence(byte[] escSequence, byte[] encoding) {
048: if (escSequence == null) {
049: throw new NullPointerException();
050: }
051:
052: this .escSequence = escSequence;
053: this .encoding = encoding;
054:
055: int hash = 0;
056: int length = escSequence.length;
057:
058: for (int i = 0; i < escSequence.length; i++) {
059: hash += (((int) escSequence[i]) & 0xff) << (i % 4);
060: }
061: if (encoding != null) {
062: for (int i = 0; i < encoding.length; i++) {
063: hash += (((int) encoding[i]) & 0xff) << (i % 4);
064: }
065: length += 2 /* M L */+ encoding.length + 1 /* 0x02 */;
066: }
067:
068: this .hash = hash;
069:
070: if (MAX_CONTROL_SEQUENCE_LEN < length) {
071: MAX_CONTROL_SEQUENCE_LEN = length;
072: }
073: }
074:
075: public boolean equals(Object obj) {
076: if (this == obj) {
077: return true;
078: }
079: if (!(obj instanceof ControlSequence)) {
080: return false;
081: }
082: ControlSequence rhs = (ControlSequence) obj;
083: if (escSequence != rhs.escSequence) {
084: if (escSequence.length != rhs.escSequence.length) {
085: return false;
086: }
087: for (int i = 0; i < escSequence.length; i++) {
088: if (escSequence[i] != rhs.escSequence[i]) {
089: return false;
090: }
091: }
092: }
093: if (encoding != rhs.encoding) {
094: if (encoding == null || rhs.encoding == null
095: || encoding.length != rhs.encoding.length) {
096: return false;
097: }
098: for (int i = 0; i < encoding.length; i++) {
099: if (encoding[i] != rhs.encoding[i]) {
100: return false;
101: }
102: }
103: }
104: return true;
105: }
106:
107: public int hashCode() {
108: return hash;
109: }
110:
111: ControlSequence concatenate(ControlSequence rhs) {
112: if (encoding != null) {
113: throw new IllegalArgumentException(
114: "cannot concatenate to a non-standard charset escape "
115: + "sequence");
116: }
117:
118: int len = escSequence.length + rhs.escSequence.length;
119: byte[] newEscSequence = new byte[len];
120: System.arraycopy(escSequence, 0, newEscSequence, 0,
121: escSequence.length);
122: System.arraycopy(rhs.escSequence, 0, newEscSequence,
123: escSequence.length, rhs.escSequence.length);
124: return new ControlSequence(newEscSequence, rhs.encoding);
125: }
126: }
127:
128: static int MAX_CONTROL_SEQUENCE_LEN;
129:
130: /**
131: * Maps a GL or GR escape sequence to an encoding.
132: */
133: private static final Map sequenceToEncodingMap;
134:
135: /**
136: * Indicates whether a particular encoding wants the high bit turned on
137: * or off.
138: */
139: private static final Map highBitsMap;
140:
141: /**
142: * Maps an encoding to an escape sequence. Rather than manage two
143: * converters in CharToByteCOMPOUND_TEXT, we output escape sequences which
144: * modify both GL and GR if necessary. This makes the output slightly less
145: * efficient, but our code much simpler.
146: */
147: private static final Map encodingToSequenceMap;
148:
149: /**
150: * The keys of 'encodingToSequenceMap', sorted in preferential order.
151: */
152: private static final List encodings;
153:
154: static {
155: HashMap tSequenceToEncodingMap = new HashMap(33, 1.0f);
156: HashMap tHighBitsMap = new HashMap(31, 1.0f);
157: HashMap tEncodingToSequenceMap = new HashMap(21, 1.0f);
158: ArrayList tEncodings = new ArrayList(21);
159:
160: if (!(isEncodingSupported("US-ASCII") && isEncodingSupported("ISO-8859-1"))) {
161: throw new ExceptionInInitializerError(
162: "US-ASCII and ISO-8859-1 unsupported");
163: }
164:
165: ControlSequence leftAscii = // high bit off, leave off
166: new ControlSequence(new byte[] { 0x1B, 0x28, 0x42 });
167: tSequenceToEncodingMap.put(leftAscii, "US-ASCII");
168: tHighBitsMap.put(leftAscii, Boolean.FALSE);
169:
170: {
171: ControlSequence rightAscii = // high bit on, turn off
172: new ControlSequence(new byte[] { 0x1B, 0x29, 0x42 });
173: tSequenceToEncodingMap.put(rightAscii, "US-ASCII");
174: tHighBitsMap.put(rightAscii, Boolean.FALSE);
175: }
176:
177: {
178: ControlSequence rightHalf = // high bit on, leave on
179: new ControlSequence(new byte[] { 0x1B, 0x2D, 0x41 });
180: tSequenceToEncodingMap.put(rightHalf, "ISO-8859-1");
181: tHighBitsMap.put(rightHalf, Boolean.TRUE);
182:
183: ControlSequence fullSet = leftAscii.concatenate(rightHalf);
184: tEncodingToSequenceMap.put("ISO-8859-1", fullSet);
185: tEncodings.add("ISO-8859-1");
186: }
187: if (isEncodingSupported("ISO-8859-2")) {
188: ControlSequence rightHalf = // high bit on, leave on
189: new ControlSequence(new byte[] { 0x1B, 0x2D, 0x42 });
190: tSequenceToEncodingMap.put(rightHalf, "ISO-8859-2");
191: tHighBitsMap.put(rightHalf, Boolean.TRUE);
192:
193: ControlSequence fullSet = leftAscii.concatenate(rightHalf);
194: tEncodingToSequenceMap.put("ISO-8859-2", fullSet);
195: tEncodings.add("ISO-8859-2");
196: }
197: if (isEncodingSupported("ISO-8859-3")) {
198: ControlSequence rightHalf = // high bit on, leave on
199: new ControlSequence(new byte[] { 0x1B, 0x2D, 0x43 });
200: tSequenceToEncodingMap.put(rightHalf, "ISO-8859-3");
201: tHighBitsMap.put(rightHalf, Boolean.TRUE);
202:
203: ControlSequence fullSet = leftAscii.concatenate(rightHalf);
204: tEncodingToSequenceMap.put("ISO-8859-3", fullSet);
205: tEncodings.add("ISO-8859-3");
206: }
207: if (isEncodingSupported("ISO-8859-4")) {
208: ControlSequence rightHalf = // high bit on, leave on
209: new ControlSequence(new byte[] { 0x1B, 0x2D, 0x44 });
210: tSequenceToEncodingMap.put(rightHalf, "ISO-8859-4");
211: tHighBitsMap.put(rightHalf, Boolean.TRUE);
212:
213: ControlSequence fullSet = leftAscii.concatenate(rightHalf);
214: tEncodingToSequenceMap.put("ISO-8859-4", fullSet);
215: tEncodings.add("ISO-8859-4");
216: }
217: if (isEncodingSupported("ISO-8859-5")) {
218: ControlSequence rightHalf = // high bit on, leave on
219: new ControlSequence(new byte[] { 0x1B, 0x2D, 0x4C });
220: tSequenceToEncodingMap.put(rightHalf, "ISO-8859-5");
221: tHighBitsMap.put(rightHalf, Boolean.TRUE);
222:
223: ControlSequence fullSet = leftAscii.concatenate(rightHalf);
224: tEncodingToSequenceMap.put("ISO-8859-5", fullSet);
225: tEncodings.add("ISO-8859-5");
226: }
227: if (isEncodingSupported("ISO-8859-6")) {
228: ControlSequence rightHalf = // high bit on, leave on
229: new ControlSequence(new byte[] { 0x1B, 0x2D, 0x47 });
230: tSequenceToEncodingMap.put(rightHalf, "ISO-8859-6");
231: tHighBitsMap.put(rightHalf, Boolean.TRUE);
232:
233: ControlSequence fullSet = leftAscii.concatenate(rightHalf);
234: tEncodingToSequenceMap.put("ISO-8859-6", fullSet);
235: tEncodings.add("ISO-8859-6");
236: }
237: if (isEncodingSupported("ISO-8859-7")) {
238: ControlSequence rightHalf = // high bit on, leave on
239: new ControlSequence(new byte[] { 0x1B, 0x2D, 0x46 });
240: tSequenceToEncodingMap.put(rightHalf, "ISO-8859-7");
241: tHighBitsMap.put(rightHalf, Boolean.TRUE);
242:
243: ControlSequence fullSet = leftAscii.concatenate(rightHalf);
244: tEncodingToSequenceMap.put("ISO-8859-7", fullSet);
245: tEncodings.add("ISO-8859-7");
246: }
247: if (isEncodingSupported("ISO-8859-8")) {
248: ControlSequence rightHalf = // high bit on, leave on
249: new ControlSequence(new byte[] { 0x1B, 0x2D, 0x48 });
250: tSequenceToEncodingMap.put(rightHalf, "ISO-8859-8");
251: tHighBitsMap.put(rightHalf, Boolean.TRUE);
252:
253: ControlSequence fullSet = leftAscii.concatenate(rightHalf);
254: tEncodingToSequenceMap.put("ISO-8859-8", fullSet);
255: tEncodings.add("ISO-8859-8");
256: }
257: if (isEncodingSupported("ISO-8859-9")) {
258: ControlSequence rightHalf = // high bit on, leave on
259: new ControlSequence(new byte[] { 0x1B, 0x2D, 0x4D });
260: tSequenceToEncodingMap.put(rightHalf, "ISO-8859-9");
261: tHighBitsMap.put(rightHalf, Boolean.TRUE);
262:
263: ControlSequence fullSet = leftAscii.concatenate(rightHalf);
264: tEncodingToSequenceMap.put("ISO-8859-9", fullSet);
265: tEncodings.add("ISO-8859-9");
266: }
267: if (isEncodingSupported("JIS_X0201")) {
268: ControlSequence glLeft = // high bit off, leave off
269: new ControlSequence(new byte[] { 0x1B, 0x28, 0x4A });
270: ControlSequence glRight = // high bit off, turn on
271: new ControlSequence(new byte[] { 0x1B, 0x28, 0x49 });
272: ControlSequence grLeft = // high bit on, turn off
273: new ControlSequence(new byte[] { 0x1B, 0x29, 0x4A });
274: ControlSequence grRight = // high bit on, leave on
275: new ControlSequence(new byte[] { 0x1B, 0x29, 0x49 });
276: tSequenceToEncodingMap.put(glLeft, "JIS_X0201");
277: tSequenceToEncodingMap.put(glRight, "JIS_X0201");
278: tSequenceToEncodingMap.put(grLeft, "JIS_X0201");
279: tSequenceToEncodingMap.put(grRight, "JIS_X0201");
280: tHighBitsMap.put(glLeft, Boolean.FALSE);
281: tHighBitsMap.put(glRight, Boolean.TRUE);
282: tHighBitsMap.put(grLeft, Boolean.FALSE);
283: tHighBitsMap.put(grRight, Boolean.TRUE);
284:
285: ControlSequence fullSet = glLeft.concatenate(grRight);
286: tEncodingToSequenceMap.put("JIS_X0201", fullSet);
287: tEncodings.add("JIS_X0201");
288: }
289: if (isEncodingSupported("X11GB2312")) {
290: ControlSequence leftHalf = // high bit off, leave off
291: new ControlSequence(new byte[] { 0x1B, 0x24, 0x28, 0x41 });
292: ControlSequence rightHalf = // high bit on, turn off
293: new ControlSequence(new byte[] { 0x1B, 0x24, 0x29, 0x41 });
294: tSequenceToEncodingMap.put(leftHalf, "X11GB2312");
295: tSequenceToEncodingMap.put(rightHalf, "X11GB2312");
296: tHighBitsMap.put(leftHalf, Boolean.FALSE);
297: tHighBitsMap.put(rightHalf, Boolean.FALSE);
298:
299: tEncodingToSequenceMap.put("X11GB2312", leftHalf);
300: tEncodings.add("X11GB2312");
301: }
302: if (isEncodingSupported("x-JIS0208")) {
303: ControlSequence leftHalf = // high bit off, leave off
304: new ControlSequence(new byte[] { 0x1B, 0x24, 0x28, 0x42 });
305: ControlSequence rightHalf = // high bit on, turn off
306: new ControlSequence(new byte[] { 0x1B, 0x24, 0x29, 0x42 });
307: tSequenceToEncodingMap.put(leftHalf, "x-JIS0208");
308: tSequenceToEncodingMap.put(rightHalf, "x-JIS0208");
309: tHighBitsMap.put(leftHalf, Boolean.FALSE);
310: tHighBitsMap.put(rightHalf, Boolean.FALSE);
311:
312: tEncodingToSequenceMap.put("x-JIS0208", leftHalf);
313: tEncodings.add("x-JIS0208");
314: }
315: if (isEncodingSupported("X11KSC5601")) {
316: ControlSequence leftHalf = // high bit off, leave off
317: new ControlSequence(new byte[] { 0x1B, 0x24, 0x28, 0x43 });
318: ControlSequence rightHalf = // high bit on, turn off
319: new ControlSequence(new byte[] { 0x1B, 0x24, 0x29, 0x43 });
320: tSequenceToEncodingMap.put(leftHalf, "X11KSC5601");
321: tSequenceToEncodingMap.put(rightHalf, "X11KSC5601");
322: tHighBitsMap.put(leftHalf, Boolean.FALSE);
323: tHighBitsMap.put(rightHalf, Boolean.FALSE);
324:
325: tEncodingToSequenceMap.put("X11KSC5601", leftHalf);
326: tEncodings.add("X11KSC5601");
327: }
328:
329: // Encodings not listed in Compound Text Encoding spec
330:
331: // Esc seq: -b
332: if (isEncodingSupported("ISO-8859-15")) {
333: ControlSequence rightHalf = // high bit on, leave on
334: new ControlSequence(new byte[] { 0x1B, 0x2D, 0x62 });
335: tSequenceToEncodingMap.put(rightHalf, "ISO-8859-15");
336: tHighBitsMap.put(rightHalf, Boolean.TRUE);
337:
338: ControlSequence fullSet = leftAscii.concatenate(rightHalf);
339: tEncodingToSequenceMap.put("ISO-8859-15", fullSet);
340: tEncodings.add("ISO-8859-15");
341: }
342: // Esc seq: -T
343: if (isEncodingSupported("TIS-620")) {
344: ControlSequence rightHalf = // high bit on, leave on
345: new ControlSequence(new byte[] { 0x1B, 0x2D, 0x54 });
346: tSequenceToEncodingMap.put(rightHalf, "TIS-620");
347: tHighBitsMap.put(rightHalf, Boolean.TRUE);
348:
349: ControlSequence fullSet = leftAscii.concatenate(rightHalf);
350: tEncodingToSequenceMap.put("TIS-620", fullSet);
351: tEncodings.add("TIS-620");
352: }
353: if (isEncodingSupported("JIS_X0212-1990")) {
354: ControlSequence leftHalf = // high bit off, leave off
355: new ControlSequence(new byte[] { 0x1B, 0x24, 0x28, 0x44 });
356: ControlSequence rightHalf = // high bit on, turn off
357: new ControlSequence(new byte[] { 0x1B, 0x24, 0x29, 0x44 });
358: tSequenceToEncodingMap.put(leftHalf, "JIS_X0212-1990");
359: tSequenceToEncodingMap.put(rightHalf, "JIS_X0212-1990");
360: tHighBitsMap.put(leftHalf, Boolean.FALSE);
361: tHighBitsMap.put(rightHalf, Boolean.FALSE);
362:
363: tEncodingToSequenceMap.put("JIS_X0212-1990", leftHalf);
364: tEncodings.add("JIS_X0212-1990");
365: }
366: if (isEncodingSupported("X11CNS11643P1")) {
367: ControlSequence leftHalf = // high bit off, leave off
368: new ControlSequence(new byte[] { 0x1B, 0x24, 0x28, 0x47 });
369: ControlSequence rightHalf = // high bit on, turn off
370: new ControlSequence(new byte[] { 0x1B, 0x24, 0x29, 0x47 });
371: tSequenceToEncodingMap.put(leftHalf, "X11CNS11643P1");
372: tSequenceToEncodingMap.put(rightHalf, "X11CNS11643P1");
373: tHighBitsMap.put(leftHalf, Boolean.FALSE);
374: tHighBitsMap.put(rightHalf, Boolean.FALSE);
375:
376: tEncodingToSequenceMap.put("X11CNS11643P1", leftHalf);
377: tEncodings.add("X11CNS11643P1");
378: }
379: if (isEncodingSupported("X11CNS11643P2")) {
380: ControlSequence leftHalf = // high bit off, leave off
381: new ControlSequence(new byte[] { 0x1B, 0x24, 0x28, 0x48 });
382: ControlSequence rightHalf = // high bit on, turn off
383: new ControlSequence(new byte[] { 0x1B, 0x24, 0x29, 0x48 });
384: tSequenceToEncodingMap.put(leftHalf, "X11CNS11643P2");
385: tSequenceToEncodingMap.put(rightHalf, "X11CNS11643P2");
386: tHighBitsMap.put(leftHalf, Boolean.FALSE);
387: tHighBitsMap.put(rightHalf, Boolean.FALSE);
388:
389: tEncodingToSequenceMap.put("X11CNS11643P2", leftHalf);
390: tEncodings.add("X11CNS11643P2");
391: }
392: if (isEncodingSupported("X11CNS11643P3")) {
393: ControlSequence leftHalf = // high bit off, leave off
394: new ControlSequence(new byte[] { 0x1B, 0x24, 0x28, 0x49 });
395: ControlSequence rightHalf = // high bit on, turn off
396: new ControlSequence(new byte[] { 0x1B, 0x24, 0x29, 0x49 });
397: tSequenceToEncodingMap.put(leftHalf, "X11CNS11643P3");
398: tSequenceToEncodingMap.put(rightHalf, "X11CNS11643P3");
399: tHighBitsMap.put(leftHalf, Boolean.FALSE);
400: tHighBitsMap.put(rightHalf, Boolean.FALSE);
401:
402: tEncodingToSequenceMap.put("X11CNS11643P3", leftHalf);
403: tEncodings.add("X11CNS11643P3");
404: }
405: // Esc seq: %/2??SUN-KSC5601.1992-3
406: if (isEncodingSupported("x-Johab")) {
407: // 0x32 looks wrong. It's copied from the Sun X11 Compound Text
408: // support code. It implies that all Johab characters comprise two
409: // octets, which isn't true. Johab supports the ASCII/KS-Roman
410: // characters from 0x21-0x7E with single-byte representations.
411: ControlSequence johab = new ControlSequence(new byte[] {
412: 0x1b, 0x25, 0x2f, 0x32 }, new byte[] { 0x53, 0x55,
413: 0x4e, 0x2d, 0x4b, 0x53, 0x43, 0x35, 0x36, 0x30,
414: 0x31, 0x2e, 0x31, 0x39, 0x39, 0x32, 0x2d, 0x33 });
415: tSequenceToEncodingMap.put(johab, "x-Johab");
416: tEncodingToSequenceMap.put("x-Johab", johab);
417: tEncodings.add("x-Johab");
418: }
419: // Esc seq: %/2??SUN-BIG5-1
420: if (isEncodingSupported("Big5")) {
421: // 0x32 looks wrong. It's copied from the Sun X11 Compound Text
422: // support code. It implies that all Big5 characters comprise two
423: // octets, which isn't true. Big5 supports the ASCII/CNS-Roman
424: // characters from 0x21-0x7E with single-byte representations.
425: ControlSequence big5 = new ControlSequence(new byte[] {
426: 0x1b, 0x25, 0x2f, 0x32 }, new byte[] { 0x53, 0x55,
427: 0x4e, 0x2d, 0x42, 0x49, 0x47, 0x35, 0x2d, 0x31 });
428: tSequenceToEncodingMap.put(big5, "Big5");
429: tEncodingToSequenceMap.put("Big5", big5);
430: tEncodings.add("Big5");
431: }
432:
433: sequenceToEncodingMap = Collections
434: .unmodifiableMap(tSequenceToEncodingMap);
435: highBitsMap = Collections.unmodifiableMap(tHighBitsMap);
436: encodingToSequenceMap = Collections
437: .unmodifiableMap(tEncodingToSequenceMap);
438: encodings = Collections.unmodifiableList(tEncodings);
439: }
440:
441: private static boolean isEncodingSupported(String encoding) {
442: try {
443: if (Charset.isSupported(encoding))
444: return true;
445: } catch (IllegalArgumentException x) {
446: }
447: return (getDecoder(encoding) != null && getEncoder(encoding) != null);
448: }
449:
450: // For Decoder
451: static CharsetDecoder getStandardDecoder(byte[] escSequence) {
452: return getNonStandardDecoder(escSequence, null);
453: }
454:
455: static boolean getHighBit(byte[] escSequence) {
456: Boolean bool = (Boolean) highBitsMap.get(new ControlSequence(
457: escSequence));
458: return (bool == Boolean.TRUE);
459: }
460:
461: static CharsetDecoder getNonStandardDecoder(byte[] escSequence,
462: byte[] encoding) {
463: return getDecoder((String) sequenceToEncodingMap
464: .get(new ControlSequence(escSequence, encoding)));
465: }
466:
467: static CharsetDecoder getDecoder(String enc) {
468: if (enc == null) {
469: return null;
470: }
471: Charset cs = null;
472: try {
473: cs = Charset.forName(enc);
474: } catch (IllegalArgumentException e) {
475: Class cls;
476: try {
477: cls = Class.forName("sun.awt.motif." + enc);
478: } catch (ClassNotFoundException ee) {
479: return null;
480: }
481: try {
482: cs = (Charset) cls.newInstance();
483: } catch (InstantiationException ee) {
484: return null;
485: } catch (IllegalAccessException ee) {
486: return null;
487: }
488: }
489: try {
490: return cs.newDecoder();
491: } catch (UnsupportedOperationException e) {
492: }
493: return null;
494: }
495:
496: // For Encoder
497: static byte[] getEscapeSequence(String encoding) {
498: ControlSequence seq = (ControlSequence) encodingToSequenceMap
499: .get(encoding);
500: if (seq != null) {
501: return seq.escSequence;
502: }
503: return null;
504: }
505:
506: static byte[] getEncoding(String encoding) {
507: ControlSequence seq = (ControlSequence) encodingToSequenceMap
508: .get(encoding);
509: if (seq != null) {
510: return seq.encoding;
511: }
512: return null;
513: }
514:
515: static List getEncodings() {
516: return encodings;
517: }
518:
519: static CharsetEncoder getEncoder(String enc) {
520: if (enc == null) {
521: return null;
522: }
523: Charset cs = null;
524: try {
525: cs = Charset.forName(enc);
526: } catch (IllegalArgumentException e) {
527: Class cls;
528: try {
529: cls = Class.forName("sun.awt.motif." + enc);
530: } catch (ClassNotFoundException ee) {
531: return null;
532: }
533: try {
534: cs = (Charset) cls.newInstance();
535: } catch (InstantiationException ee) {
536: return null;
537: } catch (IllegalAccessException ee) {
538: return null;
539: }
540: }
541: try {
542: return cs.newEncoder();
543: } catch (Throwable e) {
544: }
545: return null;
546: }
547:
548: // Not an instantiable class
549: private CompoundTextSupport() {
550: }
551: }
|