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 */
019package org.apache.shiro.codec;
020
021import org.apache.shiro.util.ByteSource;
022
023import 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 */
030public 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}