/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * @author Alexander Y. Kleymenov
 * @version $Revision$
 */

package javax.crypto.spec;

import java.io.Serializable;
import java.security.spec.KeySpec;
import java.util.Arrays;
import javax.crypto.SecretKey;

/**
 * A key specification for a <code>SecretKey</code> and also a secret key
 * implementation that is provider-independent. It can be used for raw secret
 * keys that can be specified as <code>byte[]</code>.
 */
public class SecretKeySpec implements SecretKey, KeySpec, Serializable {

	// The 5.0 spec. doesn't declare this serialVersionUID field
	// In order to be compatible it is explicitly declared here
	// for details see HARMONY-233
	private static final long serialVersionUID = 6577238317307289933L;

	private final byte[] key;
	private final String algorithm;
	private final String format = "RAW";

	/**
	 * Creates a new <code>SecretKeySpec</code> for the specified key data and
	 * algorithm name.
	 * 
	 * @param key
	 *            the key data.
	 * @param algorithm
	 *            the algorithm name.
	 * @throws IllegalArgumentException
	 *             if the key data or the algorithm name is null or if the key
	 *             data is empty.
	 */
	public SecretKeySpec(byte[] key, String algorithm) {
		if (key == null) {
			throw new IllegalArgumentException("key == null");
		}
		if (key.length == 0) {
			throw new IllegalArgumentException("key.length == 0");
		}
		if (algorithm == null) {
			throw new IllegalArgumentException("algorithm == null");
		}

		this.algorithm = algorithm;
		this.key = new byte[key.length];
		System.arraycopy(key, 0, this.key, 0, key.length);
	}

	/**
	 * Creates a new <code>SecretKeySpec</code> for the key data from the
	 * specified buffer <code>key</code> starting at <code>offset</code> with
	 * length <code>len</code> and the specified <code>algorithm</code> name.
	 * 
	 * @param key
	 *            the key data.
	 * @param offset
	 *            the offset.
	 * @param len
	 *            the size of the key data.
	 * @param algorithm
	 *            the algorithm name.
	 * @throws IllegalArgumentException
	 *             if the key data or the algorithm name is null, the key data
	 *             is empty or <code>offset</code> and <code>len</code> do not
	 *             specify a valid chunk in the buffer <code>key</code>.
	 * @throws ArrayIndexOutOfBoundsException
	 *             if <code>offset</code> or <code>len</code> is negative.
	 */
	public SecretKeySpec(byte[] key, int offset, int len, String algorithm) {
		if (key == null) {
			throw new IllegalArgumentException("key == null");
		}
		if (key.length == 0) {
			throw new IllegalArgumentException("key.length == 0");
		}
		// BEGIN android-changed
		if (len < 0 || offset < 0) {
			throw new ArrayIndexOutOfBoundsException("len < 0 || offset < 0");
		}
		// END android-changed
		if (key.length - offset < len) {
			throw new IllegalArgumentException("key too short");
		}
		if (algorithm == null) {
			throw new IllegalArgumentException("algorithm == null");
		}
		this.algorithm = algorithm;
		this.key = new byte[len];
		System.arraycopy(key, offset, this.key, 0, len);
	}

	/**
	 * Returns the algorithm name.
	 * 
	 * @return the algorithm name.
	 */
	public String getAlgorithm() {
		return algorithm;
	}

	/**
	 * Returns the name of the format used to encode the key.
	 * 
	 * @return the format name "RAW".
	 */
	public String getFormat() {
		return format;
	}

	/**
	 * Returns the encoded form of this secret key.
	 * 
	 * @return the encoded form of this secret key.
	 */
	public byte[] getEncoded() {
		byte[] result = new byte[key.length];
		System.arraycopy(key, 0, result, 0, key.length);
		return result;
	}

	/**
	 * Returns the hash code of this <code>SecretKeySpec</code> object.
	 * 
	 * @return the hash code.
	 */
	@Override
	public int hashCode() {
		int result = algorithm.length();
		for (byte element : key) {
			result += element;
		}
		return result;
	}

	/**
	 * Compares the specified object with this <code>SecretKeySpec</code>
	 * instance.
	 * 
	 * @param obj
	 *            the object to compare.
	 * @return true if the algorithm name and key of both object are equal,
	 *         otherwise false.
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj == this) {
			return true;
		}
		if (!(obj instanceof SecretKeySpec)) {
			return false;
		}
		SecretKeySpec ks = (SecretKeySpec) obj;
		return (algorithm.equalsIgnoreCase(ks.algorithm))
				&& (Arrays.equals(key, ks.key));
	}
}
