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 }