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