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.lang.codec; 020 021import org.apache.shiro.lang.util.ByteSource; 022 023import java.io.ByteArrayOutputStream; 024import java.io.File; 025import java.io.FileInputStream; 026import java.io.FileNotFoundException; 027import java.io.IOException; 028import java.io.InputStream; 029import java.io.UnsupportedEncodingException; 030 031/** 032 * Base abstract class that provides useful encoding and decoding operations, especially for character data. 033 * 034 * @since 0.9 035 */ 036@SuppressWarnings("checkstyle:BooleanExpressionComplexity") 037public abstract class CodecSupport { 038 039 /** 040 * Shiro's default preferred character encoding, equal to <b><code>UTF-8</code></b>. 041 */ 042 public static final String PREFERRED_ENCODING = "UTF-8"; 043 044 /** 045 * Converts the specified character array to a byte array using the Shiro's preferred encoding (UTF-8). 046 * <p/> 047 * This is a convenience method equivalent to calling the {@link #toBytes(String, String)} method with a 048 * a wrapping String and {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}, i.e. 049 * <p/> 050 * <code>toBytes( new String(chars), {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING} );</code> 051 * 052 * @param chars the character array to be converted to a byte array. 053 * @return the byte array of the UTF-8 encoded character array. 054 */ 055 public static byte[] toBytes(char[] chars) { 056 return toBytes(new String(chars), PREFERRED_ENCODING); 057 } 058 059 /** 060 * Converts the specified character array into a byte array using the specified character encoding. 061 * <p/> 062 * This is a convenience method equivalent to calling the {@link #toBytes(String, String)} method with a 063 * a wrapping String and the specified encoding, i.e. 064 * <p/> 065 * <code>toBytes( new String(chars), encoding );</code> 066 * 067 * @param chars the character array to be converted to a byte array 068 * @param encoding the character encoding to use to when converting to bytes. 069 * @return the bytes of the specified character array under the specified encoding. 070 * @throws CodecException if the JVM does not support the specified encoding. 071 */ 072 public static byte[] toBytes(char[] chars, String encoding) throws CodecException { 073 return toBytes(new String(chars), encoding); 074 } 075 076 /** 077 * Converts the specified source argument to a byte array with Shiro's 078 * {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}. 079 * 080 * @param source the string to convert to a byte array. 081 * @return the bytes representing the specified string under the {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}. 082 * @see #toBytes(String, String) 083 */ 084 public static byte[] toBytes(String source) { 085 return toBytes(source, PREFERRED_ENCODING); 086 } 087 088 /** 089 * Converts the specified source to a byte array via the specified encoding, throwing a 090 * {@link CodecException CodecException} if the encoding fails. 091 * 092 * @param source the source string to convert to a byte array. 093 * @param encoding the encoding to use to use. 094 * @return the byte array of the specified source with the given encoding. 095 * @throws CodecException if the JVM does not support the specified encoding. 096 */ 097 public static byte[] toBytes(String source, String encoding) throws CodecException { 098 try { 099 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}