001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.shiro.codec;
020
021 import org.apache.shiro.util.ByteSource;
022
023 import java.io.*;
024
025 /**
026 * Base abstract class that provides useful encoding and decoding operations, especially for character data.
027 *
028 * @since 0.9
029 */
030 public abstract class CodecSupport {
031
032 /**
033 * Shiro's default preferred character encoding, equal to <b><code>UTF-8</code></b>.
034 */
035 public static final String PREFERRED_ENCODING = "UTF-8";
036
037 /**
038 * Converts the specified character array to a byte array using the Shiro's preferred encoding (UTF-8).
039 * <p/>
040 * This is a convenience method equivalent to calling the {@link #toBytes(String,String)} method with a
041 * a wrapping String and {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}, i.e.
042 * <p/>
043 * <code>toBytes( new String(chars), {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING} );</code>
044 *
045 * @param chars the character array to be converted to a byte array.
046 * @return the byte array of the UTF-8 encoded character array.
047 */
048 public static byte[] toBytes(char[] chars) {
049 return toBytes(new String(chars), PREFERRED_ENCODING);
050 }
051
052 /**
053 * Converts the specified character array into a byte array using the specified character encoding.
054 * <p/>
055 * This is a convenience method equivalent to calling the {@link #toBytes(String,String)} method with a
056 * a wrapping String and the specified encoding, i.e.
057 * <p/>
058 * <code>toBytes( new String(chars), encoding );</code>
059 *
060 * @param chars the character array to be converted to a byte array
061 * @param encoding the character encoding to use to when converting to bytes.
062 * @return the bytes of the specified character array under the specified encoding.
063 * @throws CodecException if the JVM does not support the specified encoding.
064 */
065 public static byte[] toBytes(char[] chars, String encoding) throws CodecException {
066 return toBytes(new String(chars), encoding);
067 }
068
069 /**
070 * Converts the specified source argument to a byte array with Shiro's
071 * {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}.
072 *
073 * @param source the string to convert to a byte array.
074 * @return the bytes representing the specified string under the {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}.
075 * @see #toBytes(String, String)
076 */
077 public static byte[] toBytes(String source) {
078 return toBytes(source, PREFERRED_ENCODING);
079 }
080
081 /**
082 * Converts the specified source to a byte array via the specified encoding, throwing a
083 * {@link CodecException CodecException} if the encoding fails.
084 *
085 * @param source the source string to convert to a byte array.
086 * @param encoding the encoding to use to use.
087 * @return the byte array of the specified source with the given encoding.
088 * @throws CodecException if the JVM does not support the specified encoding.
089 */
090 public static byte[] toBytes(String source, String encoding) throws CodecException {
091 try {
092 return source.getBytes(encoding);
093 } catch (UnsupportedEncodingException e) {
094 String msg = "Unable to convert source [" + source + "] to byte array using " +
095 "encoding '" + encoding + "'";
096 throw new CodecException(msg, e);
097 }
098 }
099
100 /**
101 * Converts the specified byte array to a String using the {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}.
102 *
103 * @param bytes the byte array to turn into a String.
104 * @return the specified byte array as an encoded String ({@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}).
105 * @see #toString(byte[], String)
106 */
107 public static String toString(byte[] bytes) {
108 return toString(bytes, PREFERRED_ENCODING);
109 }
110
111 /**
112 * Converts the specified byte array to a String using the specified character encoding. This implementation
113 * does the same thing as <code>new {@link String#String(byte[], String) String(byte[], encoding)}</code>, but will
114 * wrap any {@link UnsupportedEncodingException} with a nicer runtime {@link CodecException}, allowing you to
115 * decide whether or not you want to catch the exception or let it propagate.
116 *
117 * @param bytes the byte array to convert to a String
118 * @param encoding the character encoding used to encode the String.
119 * @return the specified byte array as an encoded String
120 * @throws CodecException if the JVM does not support the specified encoding.
121 */
122 public static String toString(byte[] bytes, String encoding) throws CodecException {
123 try {
124 return new String(bytes, encoding);
125 } catch (UnsupportedEncodingException e) {
126 String msg = "Unable to convert byte array to String with encoding '" + encoding + "'.";
127 throw new CodecException(msg, e);
128 }
129 }
130
131 /**
132 * Returns the specified byte array as a character array using the
133 * {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}.
134 *
135 * @param bytes the byte array to convert to a char array
136 * @return the specified byte array encoded as a character array ({@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}).
137 * @see #toChars(byte[], String)
138 */
139 public static char[] toChars(byte[] bytes) {
140 return toChars(bytes, PREFERRED_ENCODING);
141 }
142
143 /**
144 * Converts the specified byte array to a character array using the specified character encoding.
145 * <p/>
146 * Effectively calls <code>{@link #toString(byte[], String) toString(bytes,encoding)}.{@link String#toCharArray() toCharArray()};</code>
147 *
148 * @param bytes the byte array to convert to a String
149 * @param encoding the character encoding used to encode the bytes.
150 * @return the specified byte array as an encoded char array
151 * @throws CodecException if the JVM does not support the specified encoding.
152 */
153 public static char[] toChars(byte[] bytes, String encoding) throws CodecException {
154 return toString(bytes, encoding).toCharArray();
155 }
156
157 /**
158 * Returns {@code true} if the specified object can be easily converted to bytes by instances of this class,
159 * {@code false} otherwise.
160 * <p/>
161 * The default implementation returns {@code true} IFF the specified object is an instance of one of the following
162 * types:
163 * <ul>
164 * <li>{@code byte[]}</li>
165 * <li>{@code char[]}</li>
166 * <li>{@link ByteSource}</li>
167 * <li>{@link String}</li>
168 * <li>{@link File}</li>
169 * </li>{@link InputStream}</li>
170 * </ul>
171 *
172 * @param o the object to test to see if it can be easily converted to a byte array
173 * @return {@code true} if the specified object can be easily converted to bytes by instances of this class,
174 * {@code false} otherwise.
175 * @since 1.0
176 */
177 protected boolean isByteSource(Object o) {
178 return o instanceof byte[] || o instanceof char[] || o instanceof String ||
179 o instanceof ByteSource || o instanceof File || o instanceof InputStream;
180 }
181
182 /**
183 * Converts the specified Object into a byte array.
184 * <p/>
185 * If the argument is a {@code byte[]}, {@code char[]}, {@link ByteSource}, {@link String}, {@link File}, or
186 * {@link InputStream}, it will be converted automatically and returned.}
187 * <p/>
188 * If the argument is anything other than these types, it is passed to the
189 * {@link #objectToBytes(Object) objectToBytes} method which must be overridden by subclasses.
190 *
191 * @param o the Object to convert into a byte array
192 * @return a byte array representation of the Object argument.
193 */
194 protected byte[] toBytes(Object o) {
195 if (o == null) {
196 String msg = "Argument for byte conversion cannot be null.";
197 throw new IllegalArgumentException(msg);
198 }
199 if (o instanceof byte[]) {
200 return (byte[]) o;
201 } else if (o instanceof ByteSource) {
202 return ((ByteSource) o).getBytes();
203 } else if (o instanceof char[]) {
204 return toBytes((char[]) o);
205 } else if (o instanceof String) {
206 return toBytes((String) o);
207 } else if (o instanceof File) {
208 return toBytes((File) o);
209 } else if (o instanceof InputStream) {
210 return toBytes((InputStream) o);
211 } else {
212 return objectToBytes(o);
213 }
214 }
215
216 /**
217 * Converts the specified Object into a String.
218 * <p/>
219 * If the argument is a {@code byte[]} or {@code char[]} it will be converted to a String using the
220 * {@link #PREFERRED_ENCODING}. If a String, it will be returned as is.
221 * <p/>
222 * If the argument is anything other than these three types, it is passed to the
223 * {@link #objectToString(Object) objectToString} method.
224 *
225 * @param o the Object to convert into a byte array
226 * @return a byte array representation of the Object argument.
227 */
228 protected String toString(Object o) {
229 if (o == null) {
230 String msg = "Argument for String conversion cannot be null.";
231 throw new IllegalArgumentException(msg);
232 }
233 if (o instanceof byte[]) {
234 return toString((byte[]) o);
235 } else if (o instanceof char[]) {
236 return new String((char[]) o);
237 } else if (o instanceof String) {
238 return (String) o;
239 } else {
240 return objectToString(o);
241 }
242 }
243
244 protected byte[] toBytes(File file) {
245 if (file == null) {
246 throw new IllegalArgumentException("File argument cannot be null.");
247 }
248 try {
249 return toBytes(new FileInputStream(file));
250 } catch (FileNotFoundException e) {
251 String msg = "Unable to acquire InputStream for file [" + file + "]";
252 throw new CodecException(msg, e);
253 }
254 }
255
256 /**
257 * Converts the specified {@link InputStream InputStream} into a byte array.
258 *
259 * @param in the InputStream to convert to a byte array
260 * @return the bytes of the input stream
261 * @throws IllegalArgumentException if the {@code InputStream} argument is {@code null}.
262 * @throws CodecException if there is any problem reading from the {@link InputStream}.
263 * @since 1.0
264 */
265 protected byte[] toBytes(InputStream in) {
266 if (in == null) {
267 throw new IllegalArgumentException("InputStream argument cannot be null.");
268 }
269 final int BUFFER_SIZE = 512;
270 ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE);
271 byte[] buffer = new byte[BUFFER_SIZE];
272 int bytesRead;
273 try {
274 while ((bytesRead = in.read(buffer)) != -1) {
275 out.write(buffer, 0, bytesRead);
276 }
277 return out.toByteArray();
278 } catch (IOException ioe) {
279 throw new CodecException(ioe);
280 } finally {
281 try {
282 in.close();
283 } catch (IOException ignored) {
284 }
285 try {
286 out.close();
287 } catch (IOException ignored) {
288 }
289 }
290 }
291
292 /**
293 * Default implementation throws a CodecException immediately since it can't infer how to convert the Object
294 * to a byte array. This method must be overridden by subclasses if anything other than the three default
295 * types (listed in the {@link #toBytes(Object) toBytes(Object)} JavaDoc) are to be converted to a byte array.
296 *
297 * @param o the Object to convert to a byte array.
298 * @return a byte array representation of the Object argument.
299 */
300 protected byte[] objectToBytes(Object o) {
301 String msg = "The " + getClass().getName() + " implementation only supports conversion to " +
302 "byte[] if the source is of type byte[], char[], String, " + ByteSource.class.getName() +
303 " File or InputStream. The instance provided as a method " +
304 "argument is of type [" + o.getClass().getName() + "]. If you would like to convert " +
305 "this argument type to a byte[], you can 1) convert the argument to one of the supported types " +
306 "yourself and then use that as the method argument or 2) subclass " + getClass().getName() +
307 "and override the objectToBytes(Object o) method.";
308 throw new CodecException(msg);
309 }
310
311 /**
312 * Default implementation merely returns <code>objectArgument.toString()</code>. Subclasses can override this
313 * method for different mechanisms of converting an object to a String.
314 *
315 * @param o the Object to convert to a byte array.
316 * @return a String representation of the Object argument.
317 */
318 protected String objectToString(Object o) {
319 return o.toString();
320 }
321 }