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.util;
020
021import org.apache.shiro.lang.codec.Base64;
022import org.apache.shiro.lang.codec.CodecSupport;
023import org.apache.shiro.lang.codec.Hex;
024
025import java.io.File;
026import java.io.InputStream;
027import java.util.Arrays;
028
029/**
030 * Very simple {@link ByteSource ByteSource} implementation that maintains an internal {@code byte[]} array and uses the
031 * {@link Hex Hex} and {@link Base64 Base64} codec classes to support the
032 * {@link #toHex() toHex()} and {@link #toBase64() toBase64()} implementations.
033 * <p/>
034 * The constructors on this class accept the following implicit byte-backed data types and will convert them to
035 * a byte-array automatically:
036 * <ul>
037 * <li>byte[]</li>
038 * <li>char[]</li>
039 * <li>String</li>
040 * <li>{@link ByteSource ByteSource}</li>
041 * <li>{@link File File}</li>
042 * <li>{@link InputStream InputStream}</li>
043 * </ul>
044 *
045 * @since 1.0
046 */
047@SuppressWarnings("checkstyle:BooleanExpressionComplexity")
048public class SimpleByteSource implements ByteSource {
049
050    private final byte[] bytes;
051    private String cachedHex;
052    private String cachedBase64;
053
054    public SimpleByteSource(byte[] bytes) {
055        this.bytes = bytes;
056    }
057
058    /**
059     * Creates an instance by converting the characters to a byte array (assumes UTF-8 encoding).
060     *
061     * @param chars the source characters to use to create the underlying byte array.
062     * @since 1.1
063     */
064    public SimpleByteSource(char[] chars) {
065        this.bytes = CodecSupport.toBytes(chars);
066    }
067
068    /**
069     * Creates an instance by converting the String to a byte array (assumes UTF-8 encoding).
070     *
071     * @param string the source string to convert to a byte array (assumes UTF-8 encoding).
072     * @since 1.1
073     */
074    public SimpleByteSource(String string) {
075        this.bytes = CodecSupport.toBytes(string);
076    }
077
078    /**
079     * Creates an instance using the sources bytes directly - it does not create a copy of the
080     * argument's byte array.
081     *
082     * @param source the source to use to populate the underlying byte array.
083     * @since 1.1
084     */
085    public SimpleByteSource(ByteSource source) {
086        this.bytes = source.getBytes();
087    }
088
089    /**
090     * Creates an instance by converting the file to a byte array.
091     *
092     * @param file the file from which to acquire bytes.
093     * @since 1.1
094     */
095    public SimpleByteSource(File file) {
096        this.bytes = new BytesHelper().getBytes(file);
097    }
098
099    /**
100     * Creates an instance by converting the stream to a byte array.
101     *
102     * @param stream the stream from which to acquire bytes.
103     * @since 1.1
104     */
105    public SimpleByteSource(InputStream stream) {
106        this.bytes = new BytesHelper().getBytes(stream);
107    }
108
109    /**
110     * Returns {@code true} if the specified object is a recognized data type that can be easily converted to
111     * bytes by instances of this class, {@code false} otherwise.
112     * <p/>
113     * This implementation returns {@code true} IFF the specified object is an instance of one of the following
114     * types:
115     * <ul>
116     * <li>{@code byte[]}</li>
117     * <li>{@code char[]}</li>
118     * <li>{@link ByteSource}</li>
119     * <li>{@link String}</li>
120     * <li>{@link File}</li>
121     * </li>{@link InputStream}</li>
122     * </ul>
123     *
124     * @param o the object to test to see if it can be easily converted to bytes by instances of this class.
125     * @return {@code true} if the specified object can be easily converted to bytes by instances of this class,
126     * {@code false} otherwise.
127     * @since 1.2
128     */
129    public static boolean isCompatible(Object o) {
130        return o instanceof byte[] || o instanceof char[] || o instanceof String
131                || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
132    }
133
134    public static ByteSource empty() {
135        return new SimpleByteSource(new byte[] {});
136    }
137
138    @Override
139    public byte[] getBytes() {
140        return Arrays.copyOf(this.bytes, this.bytes.length);
141    }
142
143    @Override
144    public boolean isEmpty() {
145        return this.bytes == null || this.bytes.length == 0;
146    }
147
148    @Override
149    public String toHex() {
150        if (this.cachedHex == null) {
151            this.cachedHex = Hex.encodeToString(getBytes());
152        }
153        return this.cachedHex;
154    }
155
156    @Override
157    public String toBase64() {
158        if (this.cachedBase64 == null) {
159            this.cachedBase64 = Base64.encodeToString(getBytes());
160        }
161        return this.cachedBase64;
162    }
163
164    @Override
165    public String toString() {
166        return toBase64();
167    }
168
169    @Override
170    public int hashCode() {
171        if (this.bytes == null || this.bytes.length == 0) {
172            return 0;
173        }
174        return Arrays.hashCode(this.bytes);
175    }
176
177    @Override
178    public boolean equals(Object o) {
179        if (o == this) {
180            return true;
181        }
182        if (o instanceof ByteSource) {
183            ByteSource bs = (ByteSource) o;
184            return Arrays.equals(getBytes(), bs.getBytes());
185        }
186        return false;
187    }
188
189    //will probably be removed in Shiro 2.0.  See SHIRO-203:
190    //https://issues.apache.org/jira/browse/SHIRO-203
191    private static final class BytesHelper extends CodecSupport {
192        public byte[] getBytes(File file) {
193            return toBytes(file);
194        }
195
196        public byte[] getBytes(InputStream stream) {
197            return toBytes(stream);
198        }
199    }
200}