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}