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.lang.codec;
20  
21  import org.apache.shiro.lang.util.ByteSource;
22  
23  import java.io.ByteArrayOutputStream;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileNotFoundException;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.UnsupportedEncodingException;
30  
31  /**
32   * Base abstract class that provides useful encoding and decoding operations, especially for character data.
33   *
34   * @since 0.9
35   */
36  @SuppressWarnings("checkstyle:BooleanExpressionComplexity")
37  public abstract class CodecSupport {
38  
39      /**
40       * Shiro's default preferred character encoding, equal to <b><code>UTF-8</code></b>.
41       */
42      public static final String PREFERRED_ENCODING = "UTF-8";
43  
44      /**
45       * Converts the specified character array to a byte array using the Shiro's preferred encoding (UTF-8).
46       * <p/>
47       * This is a convenience method equivalent to calling the {@link #toBytes(String, String)} method with a
48       * a wrapping String and {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}, i.e.
49       * <p/>
50       * <code>toBytes( new String(chars), {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING} );</code>
51       *
52       * @param chars the character array to be converted to a byte array.
53       * @return the byte array of the UTF-8 encoded character array.
54       */
55      public static byte[] toBytes(char[] chars) {
56          return toBytes(new String(chars), PREFERRED_ENCODING);
57      }
58  
59      /**
60       * Converts the specified character array into a byte array using the specified character encoding.
61       * <p/>
62       * This is a convenience method equivalent to calling the {@link #toBytes(String, String)} method with a
63       * a wrapping String and the specified encoding, i.e.
64       * <p/>
65       * <code>toBytes( new String(chars), encoding );</code>
66       *
67       * @param chars    the character array to be converted to a byte array
68       * @param encoding the character encoding to use to when converting to bytes.
69       * @return the bytes of the specified character array under the specified encoding.
70       * @throws CodecException if the JVM does not support the specified encoding.
71       */
72      public static byte[] toBytes(char[] chars, String encoding) throws CodecException {
73          return toBytes(new String(chars), encoding);
74      }
75  
76      /**
77       * Converts the specified source argument to a byte array with Shiro's
78       * {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}.
79       *
80       * @param source the string to convert to a byte array.
81       * @return the bytes representing the specified string under the {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}.
82       * @see #toBytes(String, String)
83       */
84      public static byte[] toBytes(String source) {
85          return toBytes(source, PREFERRED_ENCODING);
86      }
87  
88      /**
89       * Converts the specified source to a byte array via the specified encoding, throwing a
90       * {@link CodecException CodecException} if the encoding fails.
91       *
92       * @param source   the source string to convert to a byte array.
93       * @param encoding the encoding to use to use.
94       * @return the byte array of the specified source with the given encoding.
95       * @throws CodecException if the JVM does not support the specified encoding.
96       */
97      public static byte[] toBytes(String source, String encoding) throws CodecException {
98          try {
99              return source.getBytes(encoding);
100         } catch (UnsupportedEncodingException e) {
101             String msg = "Unable to convert source [" + source + "] to byte array using "
102                     + "encoding '" + encoding + "'";
103             throw new CodecException(msg, e);
104         }
105     }
106 
107     /**
108      * Converts the specified byte array to a String using the {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}.
109      *
110      * @param bytes the byte array to turn into a String.
111      * @return the specified byte array as an encoded String ({@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}).
112      * @see #toString(byte[], String)
113      */
114     public static String toString(byte[] bytes) {
115         return toString(bytes, PREFERRED_ENCODING);
116     }
117 
118     /**
119      * Converts the specified byte array to a String using the specified character encoding.  This implementation
120      * does the same thing as <code>new {@link String#String(byte[], String) String(byte[], encoding)}</code>, but will
121      * wrap any {@link UnsupportedEncodingException} with a nicer runtime {@link CodecException}, allowing you to
122      * decide whether or not you want to catch the exception or let it propagate.
123      *
124      * @param bytes    the byte array to convert to a String
125      * @param encoding the character encoding used to encode the String.
126      * @return the specified byte array as an encoded String
127      * @throws CodecException if the JVM does not support the specified encoding.
128      */
129     public static String toString(byte[] bytes, String encoding) throws CodecException {
130         try {
131             return new String(bytes, encoding);
132         } catch (UnsupportedEncodingException e) {
133             String msg = "Unable to convert byte array to String with encoding '" + encoding + "'.";
134             throw new CodecException(msg, e);
135         }
136     }
137 
138     /**
139      * Returns the specified byte array as a character array using the
140      * {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}.
141      *
142      * @param bytes the byte array to convert to a char array
143      * @return the specified byte array encoded as a character array ({@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}).
144      * @see #toChars(byte[], String)
145      */
146     public static char[] toChars(byte[] bytes) {
147         return toChars(bytes, PREFERRED_ENCODING);
148     }
149 
150     /**
151      * Converts the specified byte array to a character array using the specified character encoding.
152      * <p/>
153      * Effectively calls <code>{@link #toString(byte[], String) toString(bytes,encoding)}
154      * .{@link String#toCharArray() toCharArray()};</code>
155      *
156      * @param bytes    the byte array to convert to a String
157      * @param encoding the character encoding used to encode the bytes.
158      * @return the specified byte array as an encoded char array
159      * @throws CodecException if the JVM does not support the specified encoding.
160      */
161     public static char[] toChars(byte[] bytes, String encoding) throws CodecException {
162         return toString(bytes, encoding).toCharArray();
163     }
164 
165     /**
166      * Returns {@code true} if the specified object can be easily converted to bytes by instances of this class,
167      * {@code false} otherwise.
168      * <p/>
169      * The default implementation returns {@code true} IFF the specified object is an instance of one of the following
170      * types:
171      * <ul>
172      * <li>{@code byte[]}</li>
173      * <li>{@code char[]}</li>
174      * <li>{@link ByteSource}</li>
175      * <li>{@link String}</li>
176      * <li>{@link File}</li>
177      * </li>{@link InputStream}</li>
178      * </ul>
179      *
180      * @param o the object to test to see if it can be easily converted to a byte array
181      * @return {@code true} if the specified object can be easily converted to bytes by instances of this class,
182      * {@code false} otherwise.
183      * @since 1.0
184      */
185     protected boolean isByteSource(Object o) {
186         return o instanceof byte[] || o instanceof char[] || o instanceof String
187                 || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
188     }
189 
190     /**
191      * Converts the specified Object into a byte array.
192      * <p/>
193      * If the argument is a {@code byte[]}, {@code char[]}, {@link ByteSource}, {@link String}, {@link File}, or
194      * {@link InputStream}, it will be converted automatically and returned.}
195      * <p/>
196      * If the argument is anything other than these types, it is passed to the
197      * {@link #objectToBytes(Object) objectToBytes} method which must be overridden by subclasses.
198      *
199      * @param object the Object to convert into a byte array
200      * @return a byte array representation of the Object argument.
201      */
202     protected byte[] toBytes(Object object) {
203         if (object == null) {
204             String msg = "Argument for byte conversion cannot be null.";
205             throw new IllegalArgumentException(msg);
206         }
207         if (object instanceof byte[]) {
208             return (byte[]) object;
209         } else if (object instanceof ByteSource) {
210             return ((ByteSource) object).getBytes();
211         } else if (object instanceof char[]) {
212             return toBytes((char[]) object);
213         } else if (object instanceof String) {
214             return toBytes((String) object);
215         } else if (object instanceof File) {
216             return toBytes((File) object);
217         } else if (object instanceof InputStream) {
218             return toBytes((InputStream) object);
219         } else {
220             return objectToBytes(object);
221         }
222     }
223 
224     /**
225      * Converts the specified Object into a String.
226      * <p/>
227      * If the argument is a {@code byte[]} or {@code char[]} it will be converted to a String using the
228      * {@link #PREFERRED_ENCODING}.  If a String, it will be returned as is.
229      * <p/>
230      * If the argument is anything other than these three types, it is passed to the
231      * {@link #objectToString(Object) objectToString} method.
232      *
233      * @param o the Object to convert into a byte array
234      * @return a byte array representation of the Object argument.
235      */
236     protected String toString(Object o) {
237         if (o == null) {
238             String msg = "Argument for String conversion cannot be null.";
239             throw new IllegalArgumentException(msg);
240         }
241         if (o instanceof byte[]) {
242             return toString((byte[]) o);
243         } else if (o instanceof char[]) {
244             return new String((char[]) o);
245         } else if (o instanceof String) {
246             return (String) o;
247         } else {
248             return objectToString(o);
249         }
250     }
251 
252     protected byte[] toBytes(File file) {
253         if (file == null) {
254             throw new IllegalArgumentException("File argument cannot be null.");
255         }
256         try {
257             return toBytes(new FileInputStream(file));
258         } catch (FileNotFoundException e) {
259             String msg = "Unable to acquire InputStream for file [" + file + "]";
260             throw new CodecException(msg, e);
261         }
262     }
263 
264     /**
265      * Converts the specified {@link InputStream InputStream} into a byte array.
266      *
267      * @param in the InputStream to convert to a byte array
268      * @return the bytes of the input stream
269      * @throws IllegalArgumentException if the {@code InputStream} argument is {@code null}.
270      * @throws CodecException           if there is any problem reading from the {@link InputStream}.
271      * @since 1.0
272      */
273     protected byte[] toBytes(InputStream in) {
274         if (in == null) {
275             throw new IllegalArgumentException("InputStream argument cannot be null.");
276         }
277         final int bufferSize = 512;
278         ByteArrayOutputStream out = new ByteArrayOutputStream(bufferSize);
279         byte[] buffer = new byte[bufferSize];
280         int bytesRead;
281         try {
282             while ((bytesRead = in.read(buffer)) != -1) {
283                 out.write(buffer, 0, bytesRead);
284             }
285             return out.toByteArray();
286         } catch (IOException ioe) {
287             throw new CodecException(ioe);
288         } finally {
289             try {
290                 in.close();
291             } catch (IOException ignored) {
292             }
293             try {
294                 out.close();
295             } catch (IOException ignored) {
296             }
297         }
298     }
299 
300     /**
301      * Default implementation throws a CodecException immediately since it can't infer how to convert the Object
302      * to a byte array.  This method must be overridden by subclasses if anything other than the three default
303      * types (listed in the {@link #toBytes(Object) toBytes(Object)} JavaDoc) are to be converted to a byte array.
304      *
305      * @param o the Object to convert to a byte array.
306      * @return a byte array representation of the Object argument.
307      */
308     protected byte[] objectToBytes(Object o) {
309         String msg = "The " + getClass().getName() + " implementation only supports conversion to "
310                 + "byte[] if the source is of type byte[], char[], String, " + ByteSource.class.getName()
311                 + " File or InputStream.  The instance provided as a method "
312                 + "argument is of type [" + o.getClass().getName() + "].  If you would like to convert "
313                 + "this argument type to a byte[], you can 1) convert the argument to one of the supported types "
314                 + "yourself and then use that as the method argument or 2) subclass " + getClass().getName()
315                 + "and override the objectToBytes(Object o) method.";
316         throw new CodecException(msg);
317     }
318 
319     /**
320      * Default implementation merely returns <code>objectArgument.toString()</code>.  Subclasses can override this
321      * method for different mechanisms of converting an object to a String.
322      *
323      * @param o the Object to convert to a byte array.
324      * @return a String representation of the Object argument.
325      */
326     protected String objectToString(Object o) {
327         return o.toString();
328     }
329 }