1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the  "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18/*
19 * $Id: CharInfo.java 468654 2006-10-28 07:09:23Z minchau $
20 */
21package org.apache.xml.serializer;
22
23import java.io.BufferedReader;
24import java.io.InputStream;
25import java.io.InputStreamReader;
26import java.io.UnsupportedEncodingException;
27import java.net.URL;
28import java.util.Enumeration;
29import java.util.HashMap;
30import java.util.Hashtable;
31import java.util.PropertyResourceBundle;
32import java.util.ResourceBundle;
33import java.security.AccessController;
34import java.security.PrivilegedAction;
35
36import javax.xml.transform.TransformerException;
37
38import org.apache.xml.serializer.utils.MsgKey;
39import org.apache.xml.serializer.utils.SystemIDResolver;
40import org.apache.xml.serializer.utils.Utils;
41import org.apache.xml.serializer.utils.WrappedRuntimeException;
42
43/**
44 * This class provides services that tell if a character should have
45 * special treatement, such as entity reference substitution or normalization
46 * of a newline character.  It also provides character to entity reference
47 * lookup.
48 *
49 * DEVELOPERS: See Known Issue in the constructor.
50 *
51 * @xsl.usage internal
52 */
53final class CharInfo
54{
55    /** Given a character, lookup a String to output (e.g. a decorated entity reference). */
56    private HashMap m_charToString;
57
58    /**
59     * The name of the HTML entities file.
60     * If specified, the file will be resource loaded with the default class loader.
61     */
62    public static final String HTML_ENTITIES_RESOURCE =
63                SerializerBase.PKG_NAME+".HTMLEntities";
64
65    /**
66     * The name of the XML entities file.
67     * If specified, the file will be resource loaded with the default class loader.
68     */
69    public static final String XML_ENTITIES_RESOURCE =
70                SerializerBase.PKG_NAME+".XMLEntities";
71
72    /** The horizontal tab character, which the parser should always normalize. */
73    static final char S_HORIZONAL_TAB = 0x09;
74
75    /** The linefeed character, which the parser should always normalize. */
76    static final char S_LINEFEED = 0x0A;
77
78    /** The carriage return character, which the parser should always normalize. */
79    static final char S_CARRIAGERETURN = 0x0D;
80    static final char S_SPACE = 0x20;
81    static final char S_QUOTE = 0x22;
82    static final char S_LT = 0x3C;
83    static final char S_GT = 0x3E;
84    static final char S_NEL = 0x85;
85    static final char S_LINE_SEPARATOR = 0x2028;
86
87    /** This flag is an optimization for HTML entities. It false if entities
88     * other than quot (34), amp (38), lt (60) and gt (62) are defined
89     * in the range 0 to 127.
90     * @xsl.usage internal
91     */
92    boolean onlyQuotAmpLtGt;
93
94    /** Copy the first 0,1 ... ASCII_MAX values into an array */
95    static final int ASCII_MAX = 128;
96
97    /** Array of values is faster access than a set of bits
98     * to quickly check ASCII characters in attribute values,
99     * the value is true if the character in an attribute value
100     * should be mapped to a String.
101     */
102    private final boolean[] shouldMapAttrChar_ASCII;
103
104    /** Array of values is faster access than a set of bits
105     * to quickly check ASCII characters in text nodes,
106     * the value is true if the character in a text node
107     * should be mapped to a String.
108     */
109    private final boolean[] shouldMapTextChar_ASCII;
110
111    /** An array of bits to record if the character is in the set.
112     * Although information in this array is complete, the
113     * isSpecialAttrASCII array is used first because access to its values
114     * is common and faster.
115     */
116    private final int array_of_bits[];
117
118
119    // 5 for 32 bit words,  6 for 64 bit words ...
120    /*
121     * This constant is used to shift an integer to quickly
122     * calculate which element its bit is stored in.
123     * 5 for 32 bit words (int) ,  6 for 64 bit words (long)
124     */
125    private static final int SHIFT_PER_WORD = 5;
126
127    /*
128     * A mask to get the low order bits which are used to
129     * calculate the value of the bit within a given word,
130     * that will represent the presence of the integer in the
131     * set.
132     *
133     * 0x1F for 32 bit words (int),
134     * or 0x3F for 64 bit words (long)
135     */
136    private static final int LOW_ORDER_BITMASK = 0x1f;
137
138    /*
139     * This is used for optimizing the lookup of bits representing
140     * the integers in the set. It is the index of the first element
141     * in the array array_of_bits[] that is not used.
142     */
143    private int firstWordNotUsed;
144
145
146    /**
147     * A base constructor just to explicitly create the fields,
148     * with the exception of m_charToString which is handled
149     * by the constructor that delegates base construction to this one.
150     * <p>
151     * m_charToString is not created here only for performance reasons,
152     * to avoid creating a Hashtable that will be replaced when
153     * making a mutable copy, {@link #mutableCopyOf(CharInfo)}.
154     *
155     */
156    private CharInfo()
157    {
158    	this.array_of_bits = createEmptySetOfIntegers(65535);
159    	this.firstWordNotUsed = 0;
160    	this.shouldMapAttrChar_ASCII = new boolean[ASCII_MAX];
161    	this.shouldMapTextChar_ASCII = new boolean[ASCII_MAX];
162    	this.m_charKey = new CharKey();
163
164    	// Not set here, but in a constructor that uses this one
165    	// this.m_charToString =  new Hashtable();
166
167    	this.onlyQuotAmpLtGt = true;
168
169
170    	return;
171    }
172
173    private CharInfo(String entitiesResource, String method, boolean internal)
174    {
175    	// call the default constructor to create the fields
176    	this();
177    	m_charToString = new HashMap();
178
179        ResourceBundle entities = null;
180        boolean noExtraEntities = true;
181
182        // Make various attempts to interpret the parameter as a properties
183        // file or resource file, as follows:
184        //
185        //   1) attempt to load .properties file using ResourceBundle
186        //   2) try using the class loader to find the specified file a resource
187        //      file
188        //   3) try treating the resource a URI
189
190        if (internal) {
191            try {
192                // Load entity property files by using PropertyResourceBundle,
193                // cause of security issure for applets
194                entities = PropertyResourceBundle.getBundle(entitiesResource);
195            } catch (Exception e) {}
196        }
197
198        if (entities != null) {
199            Enumeration keys = entities.getKeys();
200            while (keys.hasMoreElements()){
201                String name = (String) keys.nextElement();
202                String value = entities.getString(name);
203                int code = Integer.parseInt(value);
204                boolean extra = defineEntity(name, (char) code);
205                if (extra)
206                    noExtraEntities = false;
207            }
208        } else {
209            InputStream is = null;
210
211            // Load user specified resource file by using URL loading, it
212            // requires a valid URI as parameter
213            try {
214                if (internal) {
215                    is = CharInfo.class.getResourceAsStream(entitiesResource);
216                } else {
217                    ClassLoader cl = ObjectFactory.findClassLoader();
218                    if (cl == null) {
219                        is = ClassLoader.getSystemResourceAsStream(entitiesResource);
220                    } else {
221                        is = cl.getResourceAsStream(entitiesResource);
222                    }
223
224                    if (is == null) {
225                        try {
226                            URL url = new URL(entitiesResource);
227                            is = url.openStream();
228                        } catch (Exception e) {}
229                    }
230                }
231
232                if (is == null) {
233                    throw new RuntimeException(
234                        Utils.messages.createMessage(
235                            MsgKey.ER_RESOURCE_COULD_NOT_FIND,
236                            new Object[] {entitiesResource, entitiesResource}));
237                }
238
239                // Fix Bugzilla#4000: force reading in UTF-8
240                //  This creates the de facto standard that Xalan's resource
241                //  files must be encoded in UTF-8. This should work in all
242                // JVMs.
243                //
244                // %REVIEW% KNOWN ISSUE: IT FAILS IN MICROSOFT VJ++, which
245                // didn't implement the UTF-8 encoding. Theoretically, we should
246                // simply let it fail in that case, since the JVM is obviously
247                // broken if it doesn't support such a basic standard.  But
248                // since there are still some users attempting to use VJ++ for
249                // development, we have dropped in a fallback which makes a
250                // second attempt using the platform's default encoding. In VJ++
251                // this is apparently ASCII, which is subset of UTF-8... and
252                // since the strings we'll be reading here are also primarily
253                // limited to the 7-bit ASCII range (at least, in English
254                // versions of Xalan), this should work well enough to keep us
255                // on the air until we're ready to officially decommit from
256                // VJ++.
257
258                BufferedReader reader;
259                try {
260                    reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
261                } catch (UnsupportedEncodingException e) {
262                    reader = new BufferedReader(new InputStreamReader(is));
263                }
264
265                String line = reader.readLine();
266
267                while (line != null) {
268                    if (line.length() == 0 || line.charAt(0) == '#') {
269                        line = reader.readLine();
270
271                        continue;
272                    }
273
274                    int index = line.indexOf(' ');
275
276                    if (index > 1) {
277                        String name = line.substring(0, index);
278
279                        ++index;
280
281                        if (index < line.length()) {
282                            String value = line.substring(index);
283                            index = value.indexOf(' ');
284
285                            if (index > 0) {
286                                value = value.substring(0, index);
287                            }
288
289                            int code = Integer.parseInt(value);
290
291                            boolean extra = defineEntity(name, (char) code);
292                            if (extra)
293                                noExtraEntities = false;
294                        }
295                    }
296
297                    line = reader.readLine();
298                }
299
300                is.close();
301            } catch (Exception e) {
302                throw new RuntimeException(
303                    Utils.messages.createMessage(
304                        MsgKey.ER_RESOURCE_COULD_NOT_LOAD,
305                        new Object[] { entitiesResource,
306                                       e.toString(),
307                                       entitiesResource,
308                                       e.toString()}));
309            } finally {
310                if (is != null) {
311                    try {
312                        is.close();
313                    } catch (Exception except) {}
314                }
315            }
316        }
317
318        onlyQuotAmpLtGt = noExtraEntities;
319
320        /* Now that we've used get(ch) just above to initialize the
321         * two arrays we will change by adding a tab to the set of
322         * special chars for XML (but not HTML!).
323         * We do this because a tab is always a
324         * special character in an XML attribute,
325         * but only a special character in XML text
326         * if it has an entity defined for it.
327         * This is the reason for this delay.
328         */
329        if (Method.XML.equals(method))
330        {
331            // We choose not to escape the quotation mark as &quot; in text nodes
332            shouldMapTextChar_ASCII[S_QUOTE] = false;
333        }
334
335        if (Method.HTML.equals(method)) {
336        	// The XSLT 1.0 recommendation says
337        	// "The html output method should not escape < characters occurring in attribute values."
338        	// So we don't escape '<' in an attribute for HTML
339        	shouldMapAttrChar_ASCII['<'] = false;
340
341        	// We choose not to escape the quotation mark as &quot; in text nodes.
342            shouldMapTextChar_ASCII[S_QUOTE] = false;
343        }
344    }
345
346    /**
347     * Defines a new character reference. The reference's name and value are
348     * supplied. Nothing happens if the character reference is already defined.
349     * <p>Unlike internal entities, character references are a string to single
350     * character mapping. They are used to map non-ASCII characters both on
351     * parsing and printing, primarily for HTML documents. '&amp;lt;' is an
352     * example of a character reference.</p>
353     *
354     * @param name The entity's name
355     * @param value The entity's value
356     * @return true if the mapping is not one of:
357     * <ul>
358     * <li> '<' to "&lt;"
359     * <li> '>' to "&gt;"
360     * <li> '&' to "&amp;"
361     * <li> '"' to "&quot;"
362     * </ul>
363     */
364    private boolean defineEntity(String name, char value)
365    {
366        StringBuffer sb = new StringBuffer("&");
367        sb.append(name);
368        sb.append(';');
369        String entityString = sb.toString();
370
371        boolean extra = defineChar2StringMapping(entityString, value);
372        return extra;
373    }
374
375    /**
376     * A utility object, just used to map characters to output Strings,
377     * needed because a HashMap needs to map an object as a key, not a
378     * Java primitive type, like a char, so this object gets around that
379     * and it is reusable.
380     */
381    private final CharKey m_charKey;
382
383    /**
384     * Map a character to a String. For example given
385     * the character '>' this method would return the fully decorated
386     * entity name "&lt;".
387     * Strings for entity references are loaded from a properties file,
388     * but additional mappings defined through calls to defineChar2String()
389     * are possible. Such entity reference mappings could be over-ridden.
390     *
391     * This is reusing a stored key object, in an effort to avoid
392     * heap activity. Unfortunately, that introduces a threading risk.
393     * Simplest fix for now is to make it a synchronized method, or to give
394     * up the reuse; I see very little performance difference between them.
395     * Long-term solution would be to replace the hashtable with a sparse array
396     * keyed directly from the character's integer value; see DTM's
397     * string pool for a related solution.
398     *
399     * @param value The character that should be resolved to
400     * a String, e.g. resolve '>' to  "&lt;".
401     *
402     * @return The String that the character is mapped to, or null if not found.
403     * @xsl.usage internal
404     */
405    String getOutputStringForChar(char value)
406    {
407        // CharKey m_charKey = new CharKey(); //Alternative to synchronized
408        m_charKey.setChar(value);
409        return (String) m_charToString.get(m_charKey);
410    }
411
412    /**
413     * Tell if the character argument that is from
414     * an attribute value has a mapping to a String.
415     *
416     * @param value the value of a character that is in an attribute value
417     * @return true if the character should have any special treatment,
418     * such as when writing out entity references.
419     * @xsl.usage internal
420     */
421    final boolean shouldMapAttrChar(int value)
422    {
423        // for performance try the values in the boolean array first,
424        // this is faster access than the BitSet for common ASCII values
425
426        if (value < ASCII_MAX)
427            return shouldMapAttrChar_ASCII[value];
428
429        // rather than java.util.BitSet, our private
430        // implementation is faster (and less general).
431        return get(value);
432    }
433
434    /**
435     * Tell if the character argument that is from a
436     * text node has a mapping to a String, for example
437     * to map '<' to "&lt;".
438     *
439     * @param value the value of a character that is in a text node
440     * @return true if the character has a mapping to a String,
441     * such as when writing out entity references.
442     * @xsl.usage internal
443     */
444    final boolean shouldMapTextChar(int value)
445    {
446        // for performance try the values in the boolean array first,
447        // this is faster access than the BitSet for common ASCII values
448
449        if (value < ASCII_MAX)
450            return shouldMapTextChar_ASCII[value];
451
452        // rather than java.util.BitSet, our private
453        // implementation is faster (and less general).
454        return get(value);
455    }
456
457
458
459    private static CharInfo getCharInfoBasedOnPrivilege(
460        final String entitiesFileName, final String method,
461        final boolean internal){
462            return (CharInfo) AccessController.doPrivileged(
463                new PrivilegedAction() {
464                        public Object run() {
465                            return new CharInfo(entitiesFileName,
466                              method, internal);}
467            });
468    }
469
470    /**
471     * Factory that reads in a resource file that describes the mapping of
472     * characters to entity references.
473     *
474     * Resource files must be encoded in UTF-8 and have a format like:
475     * <pre>
476     * # First char # is a comment
477     * Entity numericValue
478     * quot 34
479     * amp 38
480     * </pre>
481     * (Note: Why don't we just switch to .properties files? Oct-01 -sc)
482     *
483     * @param entitiesResource Name of entities resource file that should
484     * be loaded, which describes that mapping of characters to entity references.
485     * @param method the output method type, which should be one of "xml", "html", "text"...
486     *
487     * @xsl.usage internal
488     */
489    static CharInfo getCharInfo(String entitiesFileName, String method)
490    {
491        CharInfo charInfo = (CharInfo) m_getCharInfoCache.get(entitiesFileName);
492        if (charInfo != null) {
493        	return mutableCopyOf(charInfo);
494        }
495
496        // try to load it internally - cache
497        try {
498            charInfo = getCharInfoBasedOnPrivilege(entitiesFileName,
499                                        method, true);
500            // Put the common copy of charInfo in the cache, but return
501            // a copy of it.
502            m_getCharInfoCache.put(entitiesFileName, charInfo);
503            return mutableCopyOf(charInfo);
504        } catch (Exception e) {}
505
506        // try to load it externally - do not cache
507        try {
508            return getCharInfoBasedOnPrivilege(entitiesFileName,
509                                method, false);
510        } catch (Exception e) {}
511
512        String absoluteEntitiesFileName;
513
514        if (entitiesFileName.indexOf(':') < 0) {
515            absoluteEntitiesFileName =
516                SystemIDResolver.getAbsoluteURIFromRelative(entitiesFileName);
517        } else {
518            try {
519                absoluteEntitiesFileName =
520                    SystemIDResolver.getAbsoluteURI(entitiesFileName, null);
521            } catch (TransformerException te) {
522                throw new WrappedRuntimeException(te);
523            }
524        }
525
526        return getCharInfoBasedOnPrivilege(entitiesFileName,
527                                method, false);
528    }
529
530    /**
531     * Create a mutable copy of the cached one.
532     * @param charInfo The cached one.
533     * @return
534     */
535    private static CharInfo mutableCopyOf(CharInfo charInfo) {
536    	CharInfo copy = new CharInfo();
537
538    	int max = charInfo.array_of_bits.length;
539    	System.arraycopy(charInfo.array_of_bits,0,copy.array_of_bits,0,max);
540
541    	copy.firstWordNotUsed = charInfo.firstWordNotUsed;
542
543    	max = charInfo.shouldMapAttrChar_ASCII.length;
544    	System.arraycopy(charInfo.shouldMapAttrChar_ASCII,0,copy.shouldMapAttrChar_ASCII,0,max);
545
546    	max = charInfo.shouldMapTextChar_ASCII.length;
547    	System.arraycopy(charInfo.shouldMapTextChar_ASCII,0,copy.shouldMapTextChar_ASCII,0,max);
548
549    	// utility field copy.m_charKey is already created in the default constructor
550
551    	copy.m_charToString = (HashMap) charInfo.m_charToString.clone();
552
553    	copy.onlyQuotAmpLtGt = charInfo.onlyQuotAmpLtGt;
554
555		return copy;
556	}
557
558	/**
559	 * Table of user-specified char infos.
560	 * The table maps entify file names (the name of the
561	 * property file without the .properties extension)
562	 * to CharInfo objects populated with entities defined in
563	 * corresponding property file.
564	 */
565    private static Hashtable m_getCharInfoCache = new Hashtable();
566
567    /**
568     * Returns the array element holding the bit value for the
569     * given integer
570     * @param i the integer that might be in the set of integers
571     *
572     */
573    private static int arrayIndex(int i) {
574        return (i >> SHIFT_PER_WORD);
575    }
576
577    /**
578     * For a given integer in the set it returns the single bit
579     * value used within a given word that represents whether
580     * the integer is in the set or not.
581     */
582    private static int bit(int i) {
583        int ret = (1 << (i & LOW_ORDER_BITMASK));
584        return ret;
585    }
586
587    /**
588     * Creates a new empty set of integers (characters)
589     * @param max the maximum integer to be in the set.
590     */
591    private int[] createEmptySetOfIntegers(int max) {
592        firstWordNotUsed = 0; // an optimization
593
594        int[] arr = new int[arrayIndex(max - 1) + 1];
595            return arr;
596
597    }
598
599    /**
600     * Adds the integer (character) to the set of integers.
601     * @param i the integer to add to the set, valid values are
602     * 0, 1, 2 ... up to the maximum that was specified at
603     * the creation of the set.
604     */
605    private final void set(int i) {
606        setASCIItextDirty(i);
607        setASCIIattrDirty(i);
608
609        int j = (i >> SHIFT_PER_WORD); // this word is used
610        int k = j + 1;
611
612        if(firstWordNotUsed < k) // for optimization purposes.
613            firstWordNotUsed = k;
614
615        array_of_bits[j] |= (1 << (i & LOW_ORDER_BITMASK));
616    }
617
618
619    /**
620     * Return true if the integer (character)is in the set of integers.
621     *
622     * This implementation uses an array of integers with 32 bits per
623     * integer.  If a bit is set to 1 the corresponding integer is
624     * in the set of integers.
625     *
626     * @param i an integer that is tested to see if it is the
627     * set of integers, or not.
628     */
629    private final boolean get(int i) {
630
631        boolean in_the_set = false;
632        int j = (i >> SHIFT_PER_WORD); // wordIndex(i)
633        // an optimization here, ... a quick test to see
634        // if this integer is beyond any of the words in use
635        if(j < firstWordNotUsed)
636            in_the_set = (array_of_bits[j] &
637                          (1 << (i & LOW_ORDER_BITMASK))
638            ) != 0;  // 0L for 64 bit words
639        return in_the_set;
640    }
641
642    /**
643     * This method returns true if there are some non-standard mappings to
644     * entities other than quot, amp, lt, gt, and its only purpose is for
645     * performance.
646     * @param charToMap The value of the character that is mapped to a String
647     * @param outputString The String to which the character is mapped, usually
648     * an entity reference such as "&lt;".
649     * @return true if the mapping is not one of:
650     * <ul>
651     * <li> '<' to "&lt;"
652     * <li> '>' to "&gt;"
653     * <li> '&' to "&amp;"
654     * <li> '"' to "&quot;"
655     * </ul>
656     */
657    private boolean extraEntity(String outputString, int charToMap)
658    {
659        boolean extra = false;
660        if (charToMap < ASCII_MAX)
661        {
662            switch (charToMap)
663            {
664                case '"' : // quot
665                	if (!outputString.equals("&quot;"))
666                		extra = true;
667                	break;
668                case '&' : // amp
669                	if (!outputString.equals("&amp;"))
670                		extra = true;
671                	break;
672                case '<' : // lt
673                	if (!outputString.equals("&lt;"))
674                		extra = true;
675                	break;
676                case '>' : // gt
677                	if (!outputString.equals("&gt;"))
678                		extra = true;
679                    break;
680                default : // other entity in range 0 to 127
681                    extra = true;
682            }
683        }
684        return extra;
685    }
686
687    /**
688     * If the character is in the ASCII range then
689     * mark it as needing replacement with
690     * a String on output if it occurs in a text node.
691     * @param ch
692     */
693    private void setASCIItextDirty(int j)
694    {
695        if (0 <= j && j < ASCII_MAX)
696        {
697            shouldMapTextChar_ASCII[j] = true;
698        }
699    }
700
701    /**
702     * If the character is in the ASCII range then
703     * mark it as needing replacement with
704     * a String on output if it occurs in a attribute value.
705     * @param ch
706     */
707    private void setASCIIattrDirty(int j)
708    {
709        if (0 <= j && j < ASCII_MAX)
710        {
711            shouldMapAttrChar_ASCII[j] = true;
712        }
713    }
714
715
716    /**
717     * Call this method to register a char to String mapping, for example
718     * to map '<' to "&lt;".
719     * @param outputString The String to map to.
720     * @param inputChar The char to map from.
721     * @return true if the mapping is not one of:
722     * <ul>
723     * <li> '<' to "&lt;"
724     * <li> '>' to "&gt;"
725     * <li> '&' to "&amp;"
726     * <li> '"' to "&quot;"
727     * </ul>
728     */
729    boolean defineChar2StringMapping(String outputString, char inputChar)
730    {
731        CharKey character = new CharKey(inputChar);
732        m_charToString.put(character, outputString);
733        set(inputChar);  // mark the character has having a mapping to a String
734
735        boolean extraMapping = extraEntity(outputString, inputChar);
736        return extraMapping;
737
738    }
739
740    /**
741     * Simple class for fast lookup of char values, when used with
742     * hashtables.  You can set the char, then use it as a key.
743     *
744     * @xsl.usage internal
745     */
746    private static class CharKey extends Object
747    {
748
749      /** String value          */
750      private char m_char;
751
752      /**
753       * Constructor CharKey
754       *
755       * @param key char value of this object.
756       */
757      public CharKey(char key)
758      {
759        m_char = key;
760      }
761
762      /**
763       * Default constructor for a CharKey.
764       *
765       * @param key char value of this object.
766       */
767      public CharKey()
768      {
769      }
770
771      /**
772       * Get the hash value of the character.
773       *
774       * @return hash value of the character.
775       */
776      public final void setChar(char c)
777      {
778        m_char = c;
779      }
780
781
782
783      /**
784       * Get the hash value of the character.
785       *
786       * @return hash value of the character.
787       */
788      public final int hashCode()
789      {
790        return (int)m_char;
791      }
792
793      /**
794       * Override of equals() for this object
795       *
796       * @param obj to compare to
797       *
798       * @return True if this object equals this string value
799       */
800      public final boolean equals(Object obj)
801      {
802        return ((CharKey)obj).m_char == m_char;
803      }
804    }
805
806
807}
808