/*
 *  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.
 */

package javax.security.auth.x500;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.Principal;

import org.apache.harmony.auth.internal.nls.Messages;
import org.apache.harmony.security.x501.Name;

/**
 * Represents an X.500 principal, which holds the distinguished name of some
 * network entity. An example of a distinguished name is {@code "O=Google,
 * OU=Android, C=US"}. The class can be instantiated from a byte representation
 * of an object identifier (OID), an ASN.1 DER-encoded version, or a simple
 * string holding the distinguished name. The representations must follow either
 * RFC 2253, RFC 1779, or RFC2459.
 * 
 * @since Android 1.0
 */
public final class X500Principal implements Serializable, Principal {

    private static final long serialVersionUID = -500463348111345721L;

    /**
     * Defines a constant for the canonical string format of distinguished
     * names.
     */
    public static final String CANONICAL = "CANONICAL"; //$NON-NLS-1$

    /**
     * Defines a constant for the RFC 1779 string format of distinguished
     * names.
     */
    public static final String RFC1779 = "RFC1779"; //$NON-NLS-1$

    /**
     * Defines a constant for the RFC 2253 string format of distinguished
     * names.
     */
    public static final String RFC2253 = "RFC2253"; //$NON-NLS-1$

    //Distinguished Name
    private transient Name dn;

    /**
     * Creates a new X500Principal from a given ASN.1 DER encoding of a
     * distinguished name.
     * 
     * @param name
     *            the ASN.1 DER-encoded distinguished name
     * 
     * @throws IllegalArgumentException
     *             if the ASN.1 DER-encoded distinguished name is incorrect
     */
    public X500Principal(byte[] name) {
        super();
        if (name == null) {
            throw new IllegalArgumentException(Messages.getString("auth.00")); //$NON-NLS-1$
        }
        try {
            // FIXME dn = new Name(name);
            dn = (Name) Name.ASN1.decode(name);
        } catch (IOException e) {
            IllegalArgumentException iae = new IllegalArgumentException(Messages
                    .getString("auth.2B")); //$NON-NLS-1$
            iae.initCause(e);
            throw iae;
        }
    }

    /**
     * Creates a new X500Principal from a given ASN.1 DER encoding of a
     * distinguished name.
     * 
     * @param in
     *            an {@code InputStream} holding the ASN.1 DER-encoded
     *            distinguished name
     * 
     * @throws IllegalArgumentException
     *             if the ASN.1 DER-encoded distinguished name is incorrect
     */
    public X500Principal(InputStream in) {
        super();
        if (in == null) {
            throw new NullPointerException(Messages.getString("auth.2C")); //$NON-NLS-1$
        }
        try {
            // FIXME dn = new Name(is);
            dn = (Name) Name.ASN1.decode(in);
        } catch (IOException e) {
            IllegalArgumentException iae = new IllegalArgumentException(Messages
                    .getString("auth.2B")); //$NON-NLS-1$
            iae.initCause(e);
            throw iae;
        }
    }

    /**
     * Creates a new X500Principal from a string representation of a
     * distinguished name.
     * 
     * @param name
     *            the string representation of the distinguished name
     * 
     * @throws IllegalArgumentException
     *             if the string representation of the distinguished name is
     *             incorrect
     */
    public X500Principal(String name) {
        super();
        if (name == null) {
            throw new NullPointerException(Messages.getString("auth.00")); //$NON-NLS-1$
        }
        try {
            dn = new Name(name);
        } catch (IOException e) {
            IllegalArgumentException iae = new IllegalArgumentException(Messages
                    .getString("auth.2D")); //$NON-NLS-1$
            iae.initCause(e);
            throw iae;
        }
    }

// BEGIN android-added
    private transient String canonicalName;
    private synchronized String getCanonicalName() {
        if (canonicalName == null) {
            canonicalName = dn.getName(CANONICAL);
        }
        return canonicalName;
    }
// END android-added

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || this.getClass() != o.getClass()) {
            return false;
        }
        X500Principal principal = (X500Principal) o;
// BEGIN android-changed
        return getCanonicalName().equals(principal.getCanonicalName());
// END android-changed
    }

    /**
     * Returns an ASN.1 DER-encoded representation of the distinguished name
     * contained in this X.500 principal. 
     * 
     * @return the ASN.1 DER-encoded representation
     */
    public byte[] getEncoded() {
        byte[] src = dn.getEncoded();
        byte[] dst = new byte[src.length];
        System.arraycopy(src, 0, dst, 0, dst.length);
        return dst;
    }

    /**
     * Returns a human-readable string representation of the distinguished name 
     * contained in this X.500 principal.
     * 
     * @return the string representation
     */
    public String getName() {
        return dn.getName(RFC2253);
    }

    /**
     * Returns a string representation of the distinguished name contained in
     * this X.500 principal. The format of the representation can be chosen.
     * Valid arguments are {@link #RFC1779}, {@link #RFC2253}, and
     * {@link #CANONICAL}. The representations are specified in RFC 1779 and RFC
     * 2253, respectively. The canonical form is based on RFC 2253, but adds
     * some canonicalizing operations like removing leading and trailing
     * whitespace, lower-casing the whole name, and bringing it into a
     * normalized Unicode representation.
     * 
     * @param format
     *            the name of the format to use for the representation
     * 
     * @return the string representation
     * 
     * @throws IllegalArgumentException
     *             if the {@code format} argument is not one of the three
     *             mentioned above
     */
    public String getName(String format) {
// BEGIN android-changed
        if (CANONICAL.equals(format)) {
            return getCanonicalName();
        }

        return dn.getName(format);
    }

    @Override
    public int hashCode() {
        return getCanonicalName().hashCode();
    }
// END android-changed

    @Override
    public String toString() {
        return dn.getName(RFC1779);
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.writeObject(dn.getEncoded());
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {

        dn = (Name) Name.ASN1.decode((byte[]) in.readObject());
    }
}
