View Javadoc
1   /*
2    * reserved comment block
3    * DO NOT REMOVE OR ALTER!
4    */
5   /*
6    * Copyright 2000-2002,2004,2005 The Apache Software Foundation.
7    *
8    * Licensed under the Apache License, Version 2.0 (the "License");
9    * you may not use this file except in compliance with the License.
10   * You may obtain a copy of the License at
11   *
12   *      http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package com.sun.org.apache.xml.internal.serialize;
22  
23  import java.io.OutputStream;
24  import java.io.OutputStreamWriter;
25  import java.io.UnsupportedEncodingException;
26  import java.io.Writer;
27  import com.sun.org.apache.xerces.internal.util.EncodingMap;
28  
29  /**
30   * This class represents an encoding.
31   *
32   * @version $Id: EncodingInfo.java,v 1.6 2007/10/18 03:39:08 joehw Exp $
33   */
34  public class EncodingInfo {
35  
36      // An array to hold the argument for a method of Charset, CharsetEncoder or CharToByteConverter.
37      private Object [] fArgsForMethod = null;
38  
39      // name of encoding as registered with IANA;
40      // preferably a MIME name, but aliases are fine too.
41      String ianaName;
42      String javaName;
43      int lastPrintable;
44  
45      // The CharsetEncoder with which we test unusual characters.
46      Object fCharsetEncoder = null;
47  
48      // The CharToByteConverter with which we test unusual characters.
49      Object fCharToByteConverter = null;
50  
51      // Is the converter null because it can't be instantiated
52      // for some reason (perhaps we're running with insufficient authority as
53      // an applet?
54      boolean fHaveTriedCToB = false;
55  
56      // Is the charset encoder usable or available.
57      boolean fHaveTriedCharsetEncoder = false;
58  
59      /**
60       * Creates new <code>EncodingInfo</code> instance.
61       */
62      public EncodingInfo(String ianaName, String javaName, int lastPrintable) {
63          this.ianaName = ianaName;
64          this.javaName = EncodingMap.getIANA2JavaMapping(ianaName);
65          this.lastPrintable = lastPrintable;
66      }
67  
68      /**
69       * Returns a MIME charset name of this encoding.
70       */
71      public String getIANAName() {
72          return this.ianaName;
73      }
74  
75      /**
76       * Returns a writer for this encoding based on
77       * an output stream.
78       *
79       * @return A suitable writer
80       * @exception UnsupportedEncodingException There is no convertor
81       *  to support this encoding
82       */
83      public Writer getWriter(OutputStream output)
84          throws UnsupportedEncodingException {
85          // this should always be true!
86          if (javaName != null)
87              return new OutputStreamWriter(output, javaName);
88          javaName = EncodingMap.getIANA2JavaMapping(ianaName);
89          if(javaName == null)
90              // use UTF-8 as preferred encoding
91              return new OutputStreamWriter(output, "UTF8");
92          return new OutputStreamWriter(output, javaName);
93      }
94  
95      /**
96       * Checks whether the specified character is printable or not in this encoding.
97       *
98       * @param ch a code point (0-0x10ffff)
99       */
100     public boolean isPrintable(char ch) {
101         if (ch <= this.lastPrintable) {
102             return true;
103         }
104         return isPrintable0(ch);
105     }
106 
107     /**
108      * Checks whether the specified character is printable or not in this encoding.
109      * This method accomplishes this using a java.nio.CharsetEncoder. If NIO isn't
110      * available it will attempt use a sun.io.CharToByteConverter.
111      *
112      * @param ch a code point (0-0x10ffff)
113      */
114     private boolean isPrintable0(char ch) {
115 
116         // Attempt to get a CharsetEncoder for this encoding.
117         if (fCharsetEncoder == null && CharsetMethods.fgNIOCharsetAvailable && !fHaveTriedCharsetEncoder) {
118             if (fArgsForMethod == null) {
119                 fArgsForMethod = new Object [1];
120             }
121             // try and create the CharsetEncoder
122             try {
123                 fArgsForMethod[0] = javaName;
124                 Object charset = CharsetMethods.fgCharsetForNameMethod.invoke(null, fArgsForMethod);
125                 if (((Boolean) CharsetMethods.fgCharsetCanEncodeMethod.invoke(charset, (Object[]) null)).booleanValue()) {
126                     fCharsetEncoder = CharsetMethods.fgCharsetNewEncoderMethod.invoke(charset, (Object[]) null);
127                 }
128                 // This charset cannot be used for encoding, don't try it again...
129                 else {
130                     fHaveTriedCharsetEncoder = true;
131                 }
132             }
133             catch (Exception e) {
134                 // don't try it again...
135                 fHaveTriedCharsetEncoder = true;
136             }
137         }
138         // Attempt to use the CharsetEncoder to determine whether the character is printable.
139         if (fCharsetEncoder != null) {
140             try {
141                 fArgsForMethod[0] = new Character(ch);
142                 return ((Boolean) CharsetMethods.fgCharsetEncoderCanEncodeMethod.invoke(fCharsetEncoder, fArgsForMethod)).booleanValue();
143             }
144             catch (Exception e) {
145                 // obviously can't use this charset encoder; possibly a JDK bug
146                 fCharsetEncoder = null;
147                 fHaveTriedCharsetEncoder = false;
148             }
149         }
150 
151         // As a last resort try to use a sun.io.CharToByteConverter to
152         // determine whether this character is printable. We will always
153         // reach here on JDK 1.3 or below.
154         if (fCharToByteConverter == null) {
155             if (fHaveTriedCToB || !CharToByteConverterMethods.fgConvertersAvailable) {
156                 // forget it; nothing we can do...
157                 return false;
158             }
159             if (fArgsForMethod == null) {
160                 fArgsForMethod = new Object [1];
161             }
162             // try and create the CharToByteConverter
163             try {
164                 fArgsForMethod[0] = javaName;
165                 fCharToByteConverter = CharToByteConverterMethods.fgGetConverterMethod.invoke(null, fArgsForMethod);
166             }
167             catch (Exception e) {
168                 // don't try it again...
169                 fHaveTriedCToB = true;
170                 return false;
171             }
172         }
173         try {
174             fArgsForMethod[0] = new Character(ch);
175             return ((Boolean) CharToByteConverterMethods.fgCanConvertMethod.invoke(fCharToByteConverter, fArgsForMethod)).booleanValue();
176         }
177         catch (Exception e) {
178             // obviously can't use this converter; probably some kind of
179             // security restriction
180             fCharToByteConverter = null;
181             fHaveTriedCToB = false;
182             return false;
183         }
184     }
185 
186     // is this an encoding name recognized by this JDK?
187     // if not, will throw UnsupportedEncodingException
188     public static void testJavaEncodingName(String name)  throws UnsupportedEncodingException {
189         final byte [] bTest = {(byte)'v', (byte)'a', (byte)'l', (byte)'i', (byte)'d'};
190         String s = new String(bTest, name);
191     }
192 
193     /**
194      * Holder of methods from java.nio.charset.Charset and java.nio.charset.CharsetEncoder.
195      */
196     static class CharsetMethods {
197 
198         // Method: java.nio.charset.Charset.forName(java.lang.String)
199         private static java.lang.reflect.Method fgCharsetForNameMethod = null;
200 
201         // Method: java.nio.charset.Charset.canEncode()
202         private static java.lang.reflect.Method fgCharsetCanEncodeMethod = null;
203 
204         // Method: java.nio.charset.Charset.newEncoder()
205         private static java.lang.reflect.Method fgCharsetNewEncoderMethod = null;
206 
207         // Method: java.nio.charset.CharsetEncoder.canEncode(char)
208         private static java.lang.reflect.Method fgCharsetEncoderCanEncodeMethod = null;
209 
210         // Flag indicating whether or not java.nio.charset.* is available.
211         private static boolean fgNIOCharsetAvailable = false;
212 
213         private CharsetMethods() {}
214 
215         // Attempt to get methods for Charset and CharsetEncoder on class initialization.
216         static {
217             try {
218                 Class charsetClass = Class.forName("java.nio.charset.Charset");
219                 Class charsetEncoderClass = Class.forName("java.nio.charset.CharsetEncoder");
220                 fgCharsetForNameMethod = charsetClass.getMethod("forName", new Class [] {String.class});
221                 fgCharsetCanEncodeMethod = charsetClass.getMethod("canEncode", new Class [] {});
222                 fgCharsetNewEncoderMethod = charsetClass.getMethod("newEncoder", new Class [] {});
223                 fgCharsetEncoderCanEncodeMethod = charsetEncoderClass.getMethod("canEncode", new Class [] {Character.TYPE});
224                 fgNIOCharsetAvailable = true;
225             }
226             // ClassNotFoundException, NoSuchMethodException or SecurityException
227             // Whatever the case, we cannot use java.nio.charset.*.
228             catch (Exception exc) {
229                 fgCharsetForNameMethod = null;
230                 fgCharsetCanEncodeMethod = null;
231                 fgCharsetEncoderCanEncodeMethod = null;
232                 fgCharsetNewEncoderMethod = null;
233                 fgNIOCharsetAvailable = false;
234             }
235         }
236     }
237 
238     /**
239      * Holder of methods from sun.io.CharToByteConverter.
240      */
241     static class CharToByteConverterMethods {
242 
243         // Method: sun.io.CharToByteConverter.getConverter(java.lang.String)
244         private static java.lang.reflect.Method fgGetConverterMethod = null;
245 
246         // Method: sun.io.CharToByteConverter.canConvert(char)
247         private static java.lang.reflect.Method fgCanConvertMethod = null;
248 
249         // Flag indicating whether or not sun.io.CharToByteConverter is available.
250         private static boolean fgConvertersAvailable = false;
251 
252         private CharToByteConverterMethods() {}
253 
254         // Attempt to get methods for char to byte converter on class initialization.
255         static {
256             try {
257                 Class clazz = Class.forName("sun.io.CharToByteConverter");
258                 fgGetConverterMethod = clazz.getMethod("getConverter", new Class [] {String.class});
259                 fgCanConvertMethod = clazz.getMethod("canConvert", new Class [] {Character.TYPE});
260                 fgConvertersAvailable = true;
261             }
262             // ClassNotFoundException, NoSuchMethodException or SecurityException
263             // Whatever the case, we cannot use sun.io.CharToByteConverter.
264             catch (Exception exc) {
265                 fgGetConverterMethod = null;
266                 fgCanConvertMethod = null;
267                 fgConvertersAvailable = false;
268             }
269         }
270     }
271 }