001: /*
002: * Copyright 2000-2002,2004,2005 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.jasig.portal.serialize;
018:
019: import java.io.OutputStream;
020: import java.io.OutputStreamWriter;
021: import java.io.UnsupportedEncodingException;
022: import java.io.Writer;
023: import org.apache.xerces.util.EncodingMap;
024:
025: /**
026: * This class represents an encoding.
027: *
028: * @version $Id: EncodingInfo.java 36559 2006-04-28 18:38:13Z bjohnson $
029: */
030: public class EncodingInfo {
031:
032: // An array to hold the argument for a method of Charset, CharsetEncoder or CharToByteConverter.
033: private Object[] fArgsForMethod = null;
034:
035: // name of encoding as registered with IANA;
036: // preferably a MIME name, but aliases are fine too.
037: String ianaName;
038: String javaName;
039: int lastPrintable;
040:
041: // The CharsetEncoder with which we test unusual characters.
042: Object fCharsetEncoder = null;
043:
044: // The CharToByteConverter with which we test unusual characters.
045: Object fCharToByteConverter = null;
046:
047: // Is the converter null because it can't be instantiated
048: // for some reason (perhaps we're running with insufficient authority as
049: // an applet?
050: boolean fHaveTriedCToB = false;
051:
052: // Is the charset encoder usable or available.
053: boolean fHaveTriedCharsetEncoder = false;
054:
055: /**
056: * Creates new <code>EncodingInfo</code> instance.
057: */
058: public EncodingInfo(String ianaName, String javaName,
059: int lastPrintable) {
060: this .ianaName = ianaName;
061: this .javaName = EncodingMap.getIANA2JavaMapping(ianaName);
062: this .lastPrintable = lastPrintable;
063: }
064:
065: /**
066: * Returns a MIME charset name of this encoding.
067: */
068: public String getIANAName() {
069: return this .ianaName;
070: }
071:
072: /**
073: * Returns a writer for this encoding based on
074: * an output stream.
075: *
076: * @return A suitable writer
077: * @exception UnsupportedEncodingException There is no convertor
078: * to support this encoding
079: */
080: public Writer getWriter(OutputStream output)
081: throws UnsupportedEncodingException {
082: // this should always be true!
083: if (javaName != null)
084: return new OutputStreamWriter(output, javaName);
085: javaName = EncodingMap.getIANA2JavaMapping(ianaName);
086: if (javaName == null)
087: // use UTF-8 as preferred encoding
088: return new OutputStreamWriter(output, "UTF8");
089: return new OutputStreamWriter(output, javaName);
090: }
091:
092: /**
093: * Checks whether the specified character is printable or not in this encoding.
094: *
095: * @param ch a code point (0-0x10ffff)
096: */
097: public boolean isPrintable(char ch) {
098: if (ch <= this .lastPrintable) {
099: return true;
100: }
101: return isPrintable0(ch);
102: }
103:
104: /**
105: * Checks whether the specified character is printable or not in this encoding.
106: * This method accomplishes this using a java.nio.CharsetEncoder. If NIO isn't
107: * available it will attempt use a sun.io.CharToByteConverter.
108: *
109: * @param ch a code point (0-0x10ffff)
110: */
111: private boolean isPrintable0(char ch) {
112:
113: // Attempt to get a CharsetEncoder for this encoding.
114: if (fCharsetEncoder == null
115: && CharsetMethods.fgNIOCharsetAvailable
116: && !fHaveTriedCharsetEncoder) {
117: if (fArgsForMethod == null) {
118: fArgsForMethod = new Object[1];
119: }
120: // try and create the CharsetEncoder
121: try {
122: fArgsForMethod[0] = javaName;
123: Object charset = CharsetMethods.fgCharsetForNameMethod
124: .invoke(null, fArgsForMethod);
125: if (((Boolean) CharsetMethods.fgCharsetCanEncodeMethod
126: .invoke(charset, (Object[]) null))
127: .booleanValue()) {
128: fCharsetEncoder = CharsetMethods.fgCharsetNewEncoderMethod
129: .invoke(charset, (Object[]) null);
130: }
131: // This charset cannot be used for encoding, don't try it again...
132: else {
133: fHaveTriedCharsetEncoder = true;
134: }
135: } catch (Exception e) {
136: // don't try it again...
137: fHaveTriedCharsetEncoder = true;
138: }
139: }
140: // Attempt to use the CharsetEncoder to determine whether the character is printable.
141: if (fCharsetEncoder != null) {
142: try {
143: fArgsForMethod[0] = new Character(ch);
144: return ((Boolean) CharsetMethods.fgCharsetEncoderCanEncodeMethod
145: .invoke(fCharsetEncoder, fArgsForMethod))
146: .booleanValue();
147: } catch (Exception e) {
148: // obviously can't use this charset encoder; possibly a JDK bug
149: fCharsetEncoder = null;
150: fHaveTriedCharsetEncoder = false;
151: }
152: }
153:
154: // As a last resort try to use a sun.io.CharToByteConverter to
155: // determine whether this character is printable. We will always
156: // reach here on JDK 1.3 or below.
157: if (fCharToByteConverter == null) {
158: if (fHaveTriedCToB
159: || !CharToByteConverterMethods.fgConvertersAvailable) {
160: // forget it; nothing we can do...
161: return false;
162: }
163: if (fArgsForMethod == null) {
164: fArgsForMethod = new Object[1];
165: }
166: // try and create the CharToByteConverter
167: try {
168: fArgsForMethod[0] = javaName;
169: fCharToByteConverter = CharToByteConverterMethods.fgGetConverterMethod
170: .invoke(null, fArgsForMethod);
171: } catch (Exception e) {
172: // don't try it again...
173: fHaveTriedCToB = true;
174: return false;
175: }
176: }
177: try {
178: fArgsForMethod[0] = new Character(ch);
179: return ((Boolean) CharToByteConverterMethods.fgCanConvertMethod
180: .invoke(fCharToByteConverter, fArgsForMethod))
181: .booleanValue();
182: } catch (Exception e) {
183: // obviously can't use this converter; probably some kind of
184: // security restriction
185: fCharToByteConverter = null;
186: fHaveTriedCToB = false;
187: return false;
188: }
189: }
190:
191: // is this an encoding name recognized by this JDK?
192: // if not, will throw UnsupportedEncodingException
193: public static void testJavaEncodingName(String name)
194: throws UnsupportedEncodingException {
195: final byte[] bTest = { (byte) 'v', (byte) 'a', (byte) 'l',
196: (byte) 'i', (byte) 'd' };
197: String s = new String(bTest, name);
198: }
199:
200: /**
201: * Holder of methods from java.nio.charset.Charset and java.nio.charset.CharsetEncoder.
202: */
203: static class CharsetMethods {
204:
205: // Method: java.nio.charset.Charset.forName(java.lang.String)
206: private static java.lang.reflect.Method fgCharsetForNameMethod = null;
207:
208: // Method: java.nio.charset.Charset.canEncode()
209: private static java.lang.reflect.Method fgCharsetCanEncodeMethod = null;
210:
211: // Method: java.nio.charset.Charset.newEncoder()
212: private static java.lang.reflect.Method fgCharsetNewEncoderMethod = null;
213:
214: // Method: java.nio.charset.CharsetEncoder.canEncode(char)
215: private static java.lang.reflect.Method fgCharsetEncoderCanEncodeMethod = null;
216:
217: // Flag indicating whether or not java.nio.charset.* is available.
218: private static boolean fgNIOCharsetAvailable = false;
219:
220: private CharsetMethods() {
221: }
222:
223: // Attempt to get methods for Charset and CharsetEncoder on class initialization.
224: static {
225: try {
226: Class charsetClass = Class
227: .forName("java.nio.charset.Charset");
228: Class charsetEncoderClass = Class
229: .forName("java.nio.charset.CharsetEncoder");
230: fgCharsetForNameMethod = charsetClass.getMethod(
231: "forName", new Class[] { String.class });
232: fgCharsetCanEncodeMethod = charsetClass.getMethod(
233: "canEncode", new Class[] {});
234: fgCharsetNewEncoderMethod = charsetClass.getMethod(
235: "newEncoder", new Class[] {});
236: fgCharsetEncoderCanEncodeMethod = charsetEncoderClass
237: .getMethod("canEncode",
238: new Class[] { Character.TYPE });
239: fgNIOCharsetAvailable = true;
240: }
241: // ClassNotFoundException, NoSuchMethodException or SecurityException
242: // Whatever the case, we cannot use java.nio.charset.*.
243: catch (Exception exc) {
244: fgCharsetForNameMethod = null;
245: fgCharsetCanEncodeMethod = null;
246: fgCharsetEncoderCanEncodeMethod = null;
247: fgCharsetNewEncoderMethod = null;
248: fgNIOCharsetAvailable = false;
249: }
250: }
251: }
252:
253: /**
254: * Holder of methods from sun.io.CharToByteConverter.
255: */
256: static class CharToByteConverterMethods {
257:
258: // Method: sun.io.CharToByteConverter.getConverter(java.lang.String)
259: private static java.lang.reflect.Method fgGetConverterMethod = null;
260:
261: // Method: sun.io.CharToByteConverter.canConvert(char)
262: private static java.lang.reflect.Method fgCanConvertMethod = null;
263:
264: // Flag indicating whether or not sun.io.CharToByteConverter is available.
265: private static boolean fgConvertersAvailable = false;
266:
267: private CharToByteConverterMethods() {
268: }
269:
270: // Attempt to get methods for char to byte converter on class initialization.
271: static {
272: try {
273: Class clazz = Class
274: .forName("sun.io.CharToByteConverter");
275: fgGetConverterMethod = clazz.getMethod("getConverter",
276: new Class[] { String.class });
277: fgCanConvertMethod = clazz.getMethod("canConvert",
278: new Class[] { Character.TYPE });
279: fgConvertersAvailable = true;
280: }
281: // ClassNotFoundException, NoSuchMethodException or SecurityException
282: // Whatever the case, we cannot use sun.io.CharToByteConverter.
283: catch (Exception exc) {
284: fgGetConverterMethod = null;
285: fgCanConvertMethod = null;
286: fgConvertersAvailable = false;
287: }
288: }
289: }
290: }
|