View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.codec;
20  
21  import org.apache.shiro.util.ByteSource;
22  
23  import java.io.*;
24  
25  /**
26   * Base abstract class that provides useful encoding and decoding operations, especially for character data.
27   *
28   * @since 0.9
29   */
30  public abstract class CodecSupport {
31  
32      /**
33       * Shiro's default preferred character encoding, equal to <b><code>UTF-8</code></b>.
34       */
35      public static final String PREFERRED_ENCODING = "UTF-8";
36  
37      /**
38       * Converts the specified character array to a byte array using the Shiro's preferred encoding (UTF-8).
39       * <p/>
40       * This is a convenience method equivalent to calling the {@link #toBytes(String,String)} method with a
41       * a wrapping String and {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}, i.e.
42       * <p/>
43       * <code>toBytes( new String(chars), {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING} );</code>
44       *
45       * @param chars the character array to be converted to a byte array.
46       * @return the byte array of the UTF-8 encoded character array.
47       */
48      public static byte[] toBytes(char[] chars) {
49          return toBytes(new String(chars), PREFERRED_ENCODING);
50      }
51  
52      /**
53       * Converts the specified character array into a byte array using the specified character encoding.
54       * <p/>
55       * This is a convenience method equivalent to calling the {@link #toBytes(String,String)} method with a
56       * a wrapping String and the specified encoding, i.e.
57       * <p/>
58       * <code>toBytes( new String(chars), encoding );</code>
59       *
60       * @param chars    the character array to be converted to a byte array
61       * @param encoding the character encoding to use to when converting to bytes.
62       * @return the bytes of the specified character array under the specified encoding.
63       * @throws CodecException if the JVM does not support the specified encoding.
64       */
65      public static byte[] toBytes(char[] chars, String encoding) throws CodecException {
66          return toBytes(new String(chars), encoding);
67      }
68  
69      /**
70       * Converts the specified source argument to a byte array with Shiro's
71       * {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}.
72       *
73       * @param source the string to convert to a byte array.
74       * @return the bytes representing the specified string under the {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}.
75       * @see #toBytes(String, String)
76       */
77      public static byte[] toBytes(String source) {
78          return toBytes(source, PREFERRED_ENCODING);
79      }
80  
81      /**
82       * Converts the specified source to a byte array via the specified encoding, throwing a
83       * {@link CodecException CodecException} if the encoding fails.
84       *
85       * @param source   the source string to convert to a byte array.
86       * @param encoding the encoding to use to use.
87       * @return the byte array of the specified source with the given encoding.
88       * @throws CodecException if the JVM does not support the specified encoding.
89       */
90      public static byte[] toBytes(String source, String encoding) throws CodecException {
91          try {
92              return source.getBytes(encoding);
93          } catch (UnsupportedEncodingException e) {
94              String msg = "Unable to convert source [" + source + "] to byte array using " +
95                      "encoding '" + encoding + "'";
96              throw new CodecException(msg, e);
97          }
98      }
99  
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 }