/***** BEGIN LICENSE BLOCK *****
 * Version: EPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Eclipse Public
 * License Version 1.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.eclipse.org/legal/epl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2006, 2007 Ola Bini <ola@ologix.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the EPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the EPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby.ext.openssl;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;

import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.BERTags;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.asn1.DLSet;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.X509DefaultEntryConverter;
import org.bouncycastle.asn1.x509.X509NameEntryConverter;

import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyBoolean;
import org.jruby.RubyClass;
import org.jruby.RubyFixnum;
import org.jruby.RubyHash;
import org.jruby.RubyInteger;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.RubyObject;
import org.jruby.RubyString;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;

import org.jruby.ext.openssl.x509store.Name;
import static org.jruby.ext.openssl.OpenSSL.*;
import static org.jruby.ext.openssl.X509._X509;
import static org.jruby.ext.openssl.StringHelper.newString;

/**
 *
 * TODO member variables and methods are based on BC X509 way of doing things (now deprecated). Change
 * it to do it the X500 way, with RDN and X500NameBuilder.
 *
 * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
 */
public class X509Name extends RubyObject {
    private static final long serialVersionUID = -226196051911335103L;

    private static ObjectAllocator X509NAME_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new X509Name(runtime, klass);
        }
    };

    public static void createX509Name(final Ruby runtime, final RubyModule _X509) {
        RubyClass _Name = _X509.defineClassUnder("Name", runtime.getObject(), X509NAME_ALLOCATOR);
        RubyClass _OpenSSLError = runtime.getModule("OpenSSL").getClass("OpenSSLError");
        _X509.defineClassUnder("NameError", _OpenSSLError, _OpenSSLError.getAllocator());

        _Name.defineAnnotatedMethods(X509Name.class);
        _Name.includeModule(runtime.getComparable());

        _Name.setConstant("COMPAT", runtime.newFixnum(COMPAT));
        _Name.setConstant("RFC2253", runtime.newFixnum(RFC2253));
        _Name.setConstant("ONELINE", runtime.newFixnum(ONELINE));
        _Name.setConstant("MULTILINE", runtime.newFixnum(MULTILINE));

        final RubyFixnum UTF8_STRING = runtime.newFixnum(BERTags.UTF8_STRING);
        _Name.setConstant("DEFAULT_OBJECT_TYPE", UTF8_STRING);

        final RubyFixnum PRINTABLE_STRING = runtime.newFixnum(BERTags.PRINTABLE_STRING);
        final RubyFixnum IA5_STRING = runtime.newFixnum(BERTags.IA5_STRING);

        final ThreadContext context = runtime.getCurrentContext();
        final RubyHash hash = new RubyHash(runtime, UTF8_STRING);
        hash.op_aset(context, newString(runtime, new byte[] { 'C' }), PRINTABLE_STRING);
        final byte[] countryName = { 'c','o','u','n','t','r','y','N','a','m','e' };
        hash.op_aset(context, newString(runtime, countryName), PRINTABLE_STRING);
        final byte[] serialNumber = { 's','e','r','i','a','l','N','u','m','b','e','r' };
        hash.op_aset(context, newString(runtime, serialNumber), PRINTABLE_STRING);
        final byte[] dnQualifier = { 'd','n','Q','u','a','l','i','f','i','e','r' };
        hash.op_aset(context, newString(runtime, dnQualifier), PRINTABLE_STRING);
        hash.op_aset(context, newString(runtime, new byte[] { 'D','C' }), IA5_STRING);
        final byte[] domainComponent = { 'd','o','m','a','i','n','C','o','m','p','o','n','e','n','t' };
        hash.op_aset(context, newString(runtime, domainComponent), IA5_STRING);
        final byte[] emailAddress = { 'e','m','a','i','l','A','d','d','r','e','s','s' };
        hash.op_aset(context, newString(runtime, emailAddress), IA5_STRING);

        _Name.setConstant("OBJECT_TYPE_TEMPLATE", hash);
    }

    static X509Name newName(final Ruby runtime) {
        return new X509Name(runtime, _Name(runtime));
    }

    static X509Name newName(final Ruby runtime, final X500Principal principal) {
        final X509Name name = newName(runtime);
        name.fromASN1Sequence( principal.getEncoded() );
        return name;
    }

    static X509Name newName(final Ruby runtime, org.bouncycastle.asn1.x500.X500Name realName) {
        final X509Name name = newName(runtime);
        name.fromASN1Sequence((ASN1Sequence) realName.toASN1Primitive());
        return name;
    }

    @Deprecated
    public static X509Name create(final Ruby runtime, org.bouncycastle.asn1.x500.X500Name realName) {
        return newName(runtime, realName);
    }

    static RubyClass _Name(final Ruby runtime) {
        return _X509(runtime).getClass("Name");
    }

    public static final int COMPAT = 0;
    public static final int RFC2253 = 17892119;
    public static final int ONELINE = 8520479;
    public static final int MULTILINE = 44302342;

    public X509Name(Ruby runtime, RubyClass type) {
        super(runtime,type);
        oids = new ArrayList<ASN1ObjectIdentifier>();
        values = new ArrayList<ASN1Encodable>();
        types = new ArrayList<RubyInteger>();
    }

    private final List<ASN1ObjectIdentifier> oids;
    private final List<ASN1Encodable> values; // <ASN1String>
    private final List<RubyInteger> types;

    private transient X500Name name;

    private void fromASN1Sequence(final byte[] encoded) {
        try {
            fromASN1Sequence((ASN1Sequence) new ASN1InputStream(encoded).readObject());
        }
        catch (IOException e) {
            throw newNameError(getRuntime(), e.getClass().getName() + ":" + e.getMessage());
        }
    }

    void fromASN1Sequence(final ASN1Sequence seq) {
        oids.clear(); values.clear(); types.clear();
        if ( seq != null ) {
            for ( Enumeration e = seq.getObjects(); e.hasMoreElements(); ) {
                ASN1Object element = (ASN1Object) e.nextElement();
                if ( element instanceof RDN ) {
                    fromRDNElement((RDN) element);
                }
                else if ( element instanceof ASN1Sequence ) {
                    fromASN1Sequence(element);
                }
                else {
                    fromASN1Set(element);
                }
            }
        }
    }

    private void fromRDNElement(final RDN rdn) {
        final Ruby runtime = getRuntime();
        for( AttributeTypeAndValue tv: rdn.getTypesAndValues() ) {
            oids.add( tv.getType() );
            final ASN1Encodable val = tv.getValue();
            addValue( val );
            addType( runtime, val );
        }
    }

    private void fromASN1Set(final ASN1Object element) {
        ASN1Set typeAndValue = ASN1Set.getInstance(element);
        for ( int i = 0; i < typeAndValue.size(); i++ ) {
            fromASN1Sequence( typeAndValue.getObjectAt(i) );
        }
    }

    private void fromASN1Sequence(final ASN1Encodable element) {
        ASN1Sequence typeAndValue = ASN1Sequence.getInstance(element);
        oids.add( (ASN1ObjectIdentifier) typeAndValue.getObjectAt(0) );
        final ASN1Encodable val = typeAndValue.getObjectAt(1);
        addValue( val );
        addType( getRuntime(), val );
    }

    private void addValue(final ASN1Encodable value) {
        if ( value instanceof ASN1String ) {
            this.values.add( value );
        }
        else {
            warn(getRuntime().getCurrentContext(), this + " addValue() value not an ASN1 string = '" + value + "' (" + ( value == null ? "" : value.getClass().getName()) + ")");
            this.values.add( value ); // TODO should not happen?!
        }
    }

    @SuppressWarnings("unchecked")
    private void addType(final Ruby runtime, final ASN1Encodable value) {
        this.name = null; // NOTE: each fromX factory calls this ...
        final Integer type = ASN1.typeId(value);
        if ( type == null ) {
            warn(runtime.getCurrentContext(), this + " addType() could not resolve type for: " +
                 value + " (" + (value == null ? "" : value.getClass().getName()) + ")");
            ((List) this.types).add( runtime.getNil() );
        }
        else {
            this.types.add( runtime.newFixnum( type.intValue() ) );
        }
    }

    private void addEntry(ASN1ObjectIdentifier oid, RubyString value, RubyInteger type)
        throws IOException {
        this.name = null;
        this.oids.add(oid);
        final ASN1Encodable convertedValue = getNameEntryConverted().
                getConvertedValue(oid, value.toString()
        );
        this.values.add( convertedValue );
        this.types.add(type);
    }

    private static X509NameEntryConverter getNameEntryConverted() {
        return new X509DefaultEntryConverter();
    }

    @Override
    @JRubyMethod(visibility = Visibility.PRIVATE)
    public IRubyObject initialize(ThreadContext context) {
        return this;
    }

    @JRubyMethod(visibility = Visibility.PRIVATE)
    public IRubyObject initialize(ThreadContext context, IRubyObject str_or_dn) {
        return initialize(context, str_or_dn, context.nil);
    }

    @JRubyMethod(visibility = Visibility.PRIVATE)
    public IRubyObject initialize(final ThreadContext context, IRubyObject dn, IRubyObject template) {
        final Ruby runtime = context.runtime;

        if ( dn instanceof RubyArray ) {
            RubyArray ary = (RubyArray) dn;

            final RubyClass _Name = _Name(runtime);

            if ( template.isNil() ) template = _Name.getConstant("OBJECT_TYPE_TEMPLATE");

            for (int i = 0; i < ary.size(); i++) {
                IRubyObject obj = ary.eltOk(i);

                if ( ! (obj instanceof RubyArray) ) {
                    throw runtime.newTypeError(obj, runtime.getArray());
                }

                RubyArray arr = (RubyArray)obj;

                IRubyObject entry0, entry1, entry2;
                entry0 = arr.size() > 0 ? arr.eltOk(0) : context.nil;
                entry1 = arr.size() > 1 ? arr.eltOk(1) : context.nil;
                entry2 = arr.size() > 2 ? arr.eltOk(2) : context.nil;

                if (entry2.isNil()) entry2 = template.callMethod(context, "[]", entry0);
                if (entry2.isNil()) entry2 = _Name.getConstant("DEFAULT_OBJECT_TYPE");

                add_entry(context, entry0, entry1, entry2);
            }
        }
        else {
            IRubyObject enc = to_der_if_possible(context, dn);
            fromASN1Sequence( enc.asString().getBytes() );
        }
        return this;
    }

    /*
    private static void printASN(final ASN1Encodable obj, final StringBuilder out) {
        printASN(obj, 0, out);
    }

    private static void printASN(final ASN1Encodable obj, final int indent, final StringBuilder out) {
        for( int i = 0; i < indent; i++ ) out.append(' ');
        if ( obj instanceof ASN1Sequence ) {
            out.append("- Sequence:");
            for ( Enumeration e = ((ASN1Sequence) obj).getObjects(); e.hasMoreElements(); ) {
                printASN((ASN1Encodable) e.nextElement(), indent + 1, out);
            }
        }
        else if ( obj instanceof ASN1Set ) {
            out.append("- Set:");
            for ( Enumeration e = ((ASN1Set) obj).getObjects(); e.hasMoreElements(); ) {
                printASN((ASN1Encodable) e.nextElement(), indent + 1, out);
            }
        }
        else {
            if ( obj instanceof ASN1String ) {
                out.append("- ").append(obj).
                    append('=').append( ((ASN1String) obj).getString() ).
                    append('[').append( obj.getClass().getName() ).append(']');
            } else {
                out.append("- ").append(obj).
                    append('[').append( obj.getClass().getName() ).append(']');
            }
        }
    } */

    @JRubyMethod
    public IRubyObject add_entry(ThreadContext context, IRubyObject oid, IRubyObject value) {
        return add_entry(context, oid, value, null);
    }

    @JRubyMethod
    public IRubyObject add_entry(final ThreadContext context,
        final IRubyObject oid, final IRubyObject value, IRubyObject type) {
        final Ruby runtime = context.runtime;

        final RubyString oidStr = oid.asString();

        if ( type == null || type.isNil() ) type = getDefaultType(context, oidStr);

        final ASN1ObjectIdentifier objectId;
        try {
            objectId = ASN1.getObjectID( runtime, oidStr.toString() );
        }
        catch (IllegalArgumentException e) {
            throw newNameError(runtime, "invalid field name: " + oidStr, e);
        }
        // NOTE: won't reach here :
        if ( objectId == null ) throw newNameError(runtime, "invalid field name");

        try {
            addEntry(objectId, value.asString(), (RubyInteger) type);
        }
        catch (IOException e) {
            throw newNameError(runtime, "invalid value", e);
        }
        return this;
    }

    private static IRubyObject getDefaultType(final ThreadContext context, final RubyString oid) {
        IRubyObject template = _Name(context.runtime).getConstant("OBJECT_TYPE_TEMPLATE");
        if ( template instanceof RubyHash ) {
            return ((RubyHash) template).op_aref(context, oid);
        }
        return template.callMethod(context, "[]", oid);
    }

    @SuppressWarnings("unchecked")
    @JRubyMethod(name = "to_s", rest = true)
    public IRubyObject to_s(IRubyObject[] args) {
        final Ruby runtime = getRuntime();

        int flag = 0;
        if ( args.length > 0 && ! args[0].isNil() ) {
            flag = RubyNumeric.fix2int( args[0] );
        }

        /* Should follow parameters like this:
        if 0 (COMPAT):
        irb(main):025:0> x.to_s(OpenSSL::X509::Name::COMPAT)
        => "CN=ola.bini, O=sweden/streetAddress=sweden, O=sweden/2.5.4.43343=sweden"
        irb(main):026:0> x.to_s(OpenSSL::X509::Name::ONELINE)
        => "CN = ola.bini, O = sweden, streetAddress = sweden, O = sweden, 2.5.4.43343 = sweden"
        irb(main):027:0> x.to_s(OpenSSL::X509::Name::MULTILINE)
        => "commonName                = ola.bini\norganizationName          = sweden\nstreetAddress             = sweden\norganizationName          = sweden\n2.5.4.43343 = sweden"
        irb(main):028:0> x.to_s(OpenSSL::X509::Name::RFC2253)
        => "2.5.4.43343=#0C0673776564656E,O=sweden,streetAddress=sweden,O=sweden,CN=ola.bini"
        else
        => /CN=ola.bini/O=sweden/streetAddress=sweden/O=sweden/2.5.4.43343=sweden
         */

        final Iterator<ASN1ObjectIdentifier> oidsIter;
        final Iterator<Object> valuesIter;
        if ( flag == RFC2253 ) {
            ArrayList<ASN1ObjectIdentifier> reverseOids = new ArrayList<ASN1ObjectIdentifier>(oids);
            ArrayList<Object> reverseValues = new ArrayList<Object>(values);
            Collections.reverse(reverseOids);
            Collections.reverse(reverseValues);
            oidsIter = reverseOids.iterator();
            valuesIter = reverseValues.iterator();
        } else {
            oidsIter = oids.iterator();
            valuesIter = (Iterator) values.iterator();
        }

        final StringBuilder str = new StringBuilder(48); String sep = "";
        while( oidsIter.hasNext() ) {
            final ASN1ObjectIdentifier oid = oidsIter.next();
            String oName = name(runtime, oid);
            if ( oName == null ) oName = oid.toString();
            final Object value = valuesIter.next();

            switch(flag) {
                case RFC2253:
                    str.append(sep).append(oName).append('=').append(value);
                    sep = ",";
                    break;
                case ONELINE:
                    str.append(sep).append(oName).append(" = ").append(value);
                    sep = ",";
                    break;
                case MULTILINE:
                    final Integer nid = ASN1.oid2nid(runtime, oid);
                    if ( nid != null ) {
                        final String ln = ASN1.nid2ln(runtime, nid);
                        if ( ln != null ) oName = ln;
                    } // TODO need indention :
                    str.append(sep).append(oName).append(" = ").append(value);
                    sep = "\n";
                    break;
                case COMPAT:
                default:
                    str.append('/').append(oName).append('=').append(value);
            }
        }

        return runtime.newString( str.toString() );
    }

    @Override
    @SuppressWarnings("unchecked")
    @JRubyMethod
    public IRubyObject inspect() {
        return ObjectSupport.inspect(this, Collections.EMPTY_LIST);
    }

    @Override
    @JRubyMethod
    public RubyArray to_a() {
        final Ruby runtime = getRuntime();
        final RubyArray entries = runtime.newArray( oids.size() );
        final Iterator<ASN1ObjectIdentifier> oidsIter = oids.iterator();
        @SuppressWarnings("unchecked")
        final Iterator<Object> valuesIter = (Iterator) values.iterator();
        final Iterator<RubyInteger> typesIter = types.iterator();
        while ( oidsIter.hasNext() ) {
            final ASN1ObjectIdentifier oid = oidsIter.next();
            String oName = name(runtime, oid);
            if ( oName == null ) oName = oid.toString();
            final String value = valuesIter.next().toString();
            final IRubyObject type = typesIter.next();
            final IRubyObject[] entry = new IRubyObject[] {
                runtime.newString(oName), runtime.newString(value), type
            };
            entries.append( runtime.newArrayNoCopy(entry) );
        }
        return entries;
    }

    private static String name(final Ruby runtime, final ASN1ObjectIdentifier oid) {
        return ASN1.oid2name(runtime, oid, true);
    }

    @Deprecated
    @SuppressWarnings("unchecked")
    org.bouncycastle.asn1.x509.X509Name getRealName() {
        final java.util.Vector strValues = new java.util.Vector();
        for ( ASN1Encodable value : values ) strValues.add( value.toString() );
        return new org.bouncycastle.asn1.x509.X509Name(
            new java.util.Vector<Object>(oids), strValues
        );
    }

    final X500Name getX500Name() {
        if ( name != null ) return name;

        final X500NameBuilder builder = new X500NameBuilder( BCStyle.INSTANCE );
        for ( int i = 0; i < oids.size(); i++ ) {
            builder.addRDN( oids.get(i), values.get(i) );
        }
        return name = builder.build();
    }

    @JRubyMethod(name = { "cmp", "<=>" })
    public RubyFixnum cmp(IRubyObject other) {
        if ( equals(other) ) {
            return RubyFixnum.zero( getRuntime() );
        }
        // TODO: do we really need cmp - if so what order huh?
        if ( other instanceof X509Name ) {
            final X509Name that = (X509Name) other;
            final X500Name thisName = this.getX500Name();
            final X500Name thatName = that.getX500Name();
            int cmp = thisName.toString().compareTo( thatName.toString() );
            return RubyFixnum.newFixnum( getRuntime(), cmp );
        }
        return RubyFixnum.one( getRuntime() );
    }

    @Override
    public boolean equals(Object other) {
        if ( this == other ) return true;
        if ( other instanceof X509Name ) {
            final X509Name that = (X509Name) other;
            final X500Name thisName = this.getX500Name();
            final X500Name thatName = that.getX500Name();
            return thisName.equals(thatName);
        }
        return false;
    }

    @Override
    public int hashCode() {
        try {
            return Name.hash( getX500Name() );
        }
        catch (IOException e) {
            debugStackTrace(getRuntime(), e); return 0;
        }
        catch (RuntimeException e) {
            debugStackTrace(getRuntime(), e); return 0;
        }
        // return 41 * this.oids.hashCode();
    }

    @JRubyMethod(name = "eql?")
    public RubyBoolean eql_p(final ThreadContext context, final IRubyObject other) {
        if ( ! (other instanceof X509Name) ) return getRuntime().getFalse();
        return getRuntime().newBoolean( equals(other) );
    }

    @Override
    public IRubyObject eql_p(final IRubyObject obj) {
        return eql_p(getRuntime().getCurrentContext(), obj);
    }

    @Override
    @JRubyMethod
    public RubyFixnum hash() {
        return getRuntime().newFixnum( hashCode() );
    }

    @JRubyMethod
    public RubyString to_der(final ThreadContext context) {
        final Ruby runtime = context.runtime;
        final DLSequence seq;
        if ( oids.size() > 0 ) {
            ASN1EncodableVector vec = new ASN1EncodableVector();
            ASN1EncodableVector sVec = new ASN1EncodableVector();
            ASN1ObjectIdentifier lastOid = null;
            for ( int i = 0; i != oids.size(); i++ ) {
                final ASN1ObjectIdentifier oid = oids.get(i);
                ASN1EncodableVector v = new ASN1EncodableVector();
                v.add(oid);
                // TODO DO NOT USE DL types !
                //final String value = values.get(i);
                //final int type = RubyNumeric.fix2int(types.get(i));
                //v.add( convert(oid, value, type) );
                v.add( values.get(i) );

                if ( lastOid == null ) {
                    sVec.add(new DLSequence(v));
                }
                else {
                    vec.add(new DLSet(sVec));
                    sVec = new ASN1EncodableVector();
                    sVec.add(new DLSequence(v));
                }
                lastOid = oid;
            }
            vec.add(new DLSet(sVec));
            seq = new DLSequence(vec);
        } else {
            seq = new DLSequence();
        }
        try {
            return StringHelper.newString(runtime, seq.getEncoded(ASN1Encoding.DER));
        }
        catch (IOException e) {
            throw newNameError(runtime, e);
        }
    }

    private ASN1Primitive convert(ASN1ObjectIdentifier oid, String value, int type) {
        final Class<? extends ASN1Encodable> clazz = ASN1.typeClass(type);
        try {
            if ( clazz != null ) {
                Constructor<?> ctor = clazz.getConstructor(new Class[]{ String.class });
                if (null != ctor) {
                    return (ASN1Primitive) ctor.newInstance(new Object[]{ value });
                }
            }
            return new X509DefaultEntryConverter().getConvertedValue(oid, value);
        }
        catch (NoSuchMethodException e) {
            throw newNameError(getRuntime(), e);
        }
        catch (InstantiationException e) {
            throw newNameError(getRuntime(), e);
        }
        catch (IllegalAccessException e) {
            throw newNameError(getRuntime(), e);
        }
        catch (IllegalArgumentException e) {
            throw newNameError(getRuntime(), e);
        }
        catch (InvocationTargetException e) {
            throw newNameError(getRuntime(), e.getTargetException());
        }
        catch (RuntimeException e) {
            debugStackTrace(getRuntime(), e);
            throw newNameError(getRuntime(), e);
        }
    }

    private static RaiseException newNameError(Ruby runtime, String msg, Throwable e) {
        return Utils.newError(runtime, _X509(runtime).getClass("NameError"), msg, e);
    }

    private static RaiseException newNameError(Ruby runtime, Throwable e) {
        return Utils.newError(runtime, _X509(runtime).getClass("NameError"), e);
    }

    private static RaiseException newNameError(Ruby runtime, String message) {
        return Utils.newError(runtime, _X509(runtime).getClass("NameError"), message);
    }

}// X509Name
