/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2007-2018 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://oss.oracle.com/licenses/CDDL+GPL-1.1
 * or LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package org.glassfish.gmbal.impl ;

import org.glassfish.gmbal.impl.trace.TraceRuntime;
import org.glassfish.gmbal.impl.trace.TraceRegistrationFine;
import org.glassfish.pfl.tf.spi.annotation.InfoMethod;
import org.glassfish.gmbal.impl.trace.TraceRegistration;
import org.glassfish.pfl.basic.algorithm.DumpToString;
import org.glassfish.pfl.basic.contain.Pair;
import org.glassfish.pfl.basic.facet.FacetAccessor;
import org.glassfish.pfl.basic.algorithm.Algorithms;
import org.glassfish.pfl.basic.func.UnaryPredicate;

import java.lang.reflect.Array ;
import java.lang.reflect.Constructor ;
import java.lang.reflect.InvocationTargetException;

import java.util.Collection ;
import java.util.List ;
import java.util.ArrayList ;
import java.util.Map ;
import java.util.HashMap ;
import java.util.Iterator ;
import java.util.Enumeration ;
import java.util.Dictionary ;
import java.util.Arrays;

import javax.management.ObjectName ;
import javax.management.JMException;

import javax.management.openmbean.ArrayType ;
import javax.management.openmbean.OpenType ;
import javax.management.openmbean.OpenDataException ;
import javax.management.openmbean.SimpleType ;
import javax.management.openmbean.TabularType ;
import javax.management.openmbean.CompositeType ;
import javax.management.openmbean.CompositeData ;
import javax.management.openmbean.CompositeDataSupport ;
import javax.management.openmbean.TabularData ;
import javax.management.openmbean.TabularDataSupport ;

import org.glassfish.gmbal.AMXClient;
import org.glassfish.gmbal.ManagedObject ;
import org.glassfish.gmbal.ManagedData ;
import org.glassfish.gmbal.typelib.EvaluatedArrayType;
import org.glassfish.gmbal.typelib.EvaluatedClassAnalyzer;
import org.glassfish.gmbal.typelib.EvaluatedClassDeclaration;
import org.glassfish.gmbal.typelib.EvaluatedMethodDeclaration;
import org.glassfish.gmbal.typelib.EvaluatedType;

import static org.glassfish.gmbal.typelib.EvaluatedType.* ;

/** A ManagedEntity is one of the pre-defined Open MBean types: SimpleType, 
 * ObjectName, TabularData, or CompositeData.
 */
@TraceRuntime
@TraceRegistration
@TraceRegistrationFine
public abstract class TypeConverterImpl implements TypeConverter {
    private static final Map<EvaluatedType,OpenType> simpleTypeMap =
        new HashMap<EvaluatedType,OpenType>() ;
    private static final Map<OpenType,EvaluatedClassDeclaration> simpleOpenTypeMap =
        new HashMap<OpenType,EvaluatedClassDeclaration>() ;

    // Map each type in types to otype in simpleTypeMap.
    // Map otype to the first type in types in simpleOpenTypeMap.
    private static void initMaps( final OpenType otype, 
        final EvaluatedClassDeclaration... types ) {
        boolean first = true ;
        for (EvaluatedClassDeclaration type : types) {
            simpleTypeMap.put( type, otype ) ;
            if (first) {
                first = false ;
                simpleOpenTypeMap.put( otype, type ) ;
            }
        }
    }

    static {
        initMaps( SimpleType.BOOLEAN, EBOOLEANW, EBOOLEAN ) ;
        initMaps( SimpleType.CHARACTER, ECHARW, ECHAR ) ;
        initMaps( SimpleType.INTEGER, EINTW, EINT ) ;
        initMaps( SimpleType.SHORT, ESHORTW, ESHORT ) ;
        initMaps( SimpleType.LONG, ELONGW, ELONG ) ;
        initMaps( SimpleType.BYTE, EBYTEW, EBYTE ) ;
        initMaps( SimpleType.FLOAT, EFLOATW, EFLOAT ) ;
        initMaps( SimpleType.DOUBLE, EDOUBLEW, EDOUBLE ) ;

	initMaps( SimpleType.STRING, ESTRING ) ;
	initMaps( SimpleType.VOID, EVOID ) ;
	initMaps( SimpleType.DATE, EDATE ) ;
	initMaps( SimpleType.OBJECTNAME, EOBJECT_NAME ) ;
	initMaps( SimpleType.BIGDECIMAL, EBIG_DECIMAL ) ;
	initMaps( SimpleType.BIGINTEGER, EBIG_INTEGER ) ;
    }

    public static Class getJavaClass( final OpenType ot ) {
	if (ot instanceof SimpleType) {
	    final SimpleType st = (SimpleType)ot ;
	    return simpleOpenTypeMap.get( st ).cls() ;
	} else if (ot instanceof ArrayType) {
	    // This code is rather odd.  We need to get the opentype of the 
            // array components, convert that to a java type, and then 
            // construct a Java type (Class) that has that java type as its 
            // component (java) type.  I think the only way to do this 
            // is to call Array.newInstance, and then take the class from the 
            // resulting array instance.
	    final ArrayType at = (ArrayType)ot ;
	    final OpenType cot = at.getElementOpenType() ;
	    final Class cjt = getJavaClass( cot ) ;
	    final Object temp = Array.newInstance( cjt, 0 ) ;
	    return temp.getClass() ;
	} else if (ot instanceof TabularType) {
	    return TabularData.class ;
	} else if (ot instanceof CompositeType) {
	    return CompositeData.class ;
	} else {
            throw Exceptions.self.unsupportedOpenType( ot ) ;
	}
    }

    // Make sure that EINT is replaced with EINTW, etc.
    // We only want wrapped types for primitives.
    private static EvaluatedType canonicalType( EvaluatedType et ) {
        OpenType ot = simpleTypeMap.get( et ) ;
        if (ot == null) {
            return et ;
        } else {
            return simpleOpenTypeMap.get( ot ) ;
        }
    }

    public static Class getJavaClass( final EvaluatedType type ) {
        if (type instanceof EvaluatedClassDeclaration) {
	    return ((EvaluatedClassDeclaration)type).cls() ;
	} else if (type instanceof EvaluatedArrayType) {
	    // Same trick as above.
	    final EvaluatedArrayType gat = (EvaluatedArrayType)type ;
	    final EvaluatedType ctype = canonicalType( gat.componentType() ) ;
	    final Class cclass = getJavaClass( ctype ) ;
	    final Object temp = Array.newInstance( cclass, 0 ) ;
	    return temp.getClass() ;
	} else {
            throw Exceptions.self.cannotConvertToJavaType(type) ;
	}
    }

    /* Type mapping rules for OT : Type -> OpenType:
     *  <pre>
     *  Java Type			Open Type
     *  -----------------------------------------
     *  boolean, Boolean		BOOLEAN
     *  char, Character			CHARACTER
     *  byte, Byte			BYTE
     *  short, Short			SHORT
     *  int, Integer			INTEGER
     *  long, LONG			LONG
     *  float, Float			FLOAT	
     *  double, Double			DOUBLE
     *  String				STRING
     *  void				VOID
     *  java.util.Date			DATE
     *  javax.management.ObjectName	OBJECTNAME
     *  java.math.BigDecimal		BIGDECIMAL
     *  java.math.BigInteger		BIGINTEGER
     *  (all of the above types have the same JT and OT representation.
     *  
     *  Enumeration type		String (only valid values are keywords)
     *
     *  @ManagedObject			ObjectName
     *
     *  @ManagedData			CompositeType( 
     *					    @ManagedData.name, 
     *					    @ManagedData.description, 
     *					    ALL @ManagedAttribute ID, (extracted from the method 
     *					                               name)
     *					    ALL @ManagedAttribute.description, 
     *					    ALL @ManagedAttribute OT(Type) ) (extracted from the 
     *						                              method attribute 
     *						                              TYPE)
     *					We also need to include @IncludeSubclass and 
     *					@InheritedAttribute(s) attributes
     *					Also note that @InheritTable adds an attribute to the 
     *					class.  
     *					The inherited table is mapped to an array.  
     *					The InheritTable annotation specifies a class X, which must
     *					be part of the Type C<X>, where C is a subclass of 
     *					Collection, Iterable, Iterator, or Enumerable.  
     *					JT -> OT: invoke attribute methods, collect results, 
     *					    construct CompositeDataSupport
     *					OT -> JT: NOT SUPPORTED (for now)
     *					(can only be supported in Collection case)
     *
     *	C<X>, C a subtype of Collection, Iterator, Iterable, or Enumeration
     *	        			Mapped to array data containing OT(X) 
     *					JT -> OT: Construct ArrayType of type OT(X), 
     *					    fill it with all JT elements as mapped to their OT type
     *					OT -> JT: NOT SUPPORTED 
     *					    (unless CompositeData -> Java type is supported)
     *					What about SortedSet?
     *
     *	M<K,V>, M a subtype of Map or Dictionary
     *	                                Mapped to tabular data containing id="key" OT(K) as 
     *	                                the key and id="value" OT(V) 
     *	                                XXX What about SortedMap?
     *	    
     *	X[]				OT(X)[]
     *					    As for CompositeData, we will mostly treat this as 
     *					    readonly.  Mappings same as C<X> case. 
     *
     *	TypeVariable			
     *	WildCardType			Both need to be supported.  The basic idea is that 
     *	                                the upper bounds give us information on what interfaces 
     *	                                must be implemented by by an instance of the type 
     *	                                variable.  This in turn tells us what
     *					subclasses identified by @IncludeSubclass should be 
     *					used when mapping the actual data to a CompositeData 
     *					instance.
     *					Not supported: lower bounds (what would that mean?  
     *					               Do I need it?)
     *						       multiple upper bounds (I don't think I 
     *						       need this?)
     *					Multiple upper bounds PROBABLY means the intersection of 
     *					the included subclasses for all of the bounds
     *
     *	Other				String
     *					    JT -> OT: use toString
     *					    OT -> JT: requires a <init>(String) constructor
     *
     * Note that the CompositeData OpenType->JavaType mapping will be handled 
     * the same way as in MXBeans.  
     * XXX This is not yet implemented!
     */
    @TraceRegistration
    public static TypeConverter makeTypeConverter( final EvaluatedType type,
        final ManagedObjectManagerInternal mom ) {
        
        TypeConverter result = null ;
        try {
            final OpenType stype = simpleTypeMap.get( type ) ;
            if (stype != null) {
                result = handleSimpleType( type, stype ) ;
            } else if (type instanceof EvaluatedClassDeclaration) {
                EvaluatedClassDeclaration cls = (EvaluatedClassDeclaration)type ;
                final ManagedObject mo = mom.getFirstAnnotationOnClass( cls,
                    ManagedObject.class ) ;
                final ManagedData md = mom.getFirstAnnotationOnClass( cls,
                    ManagedData.class ) ;

                if (mo != null) {
                    result = handleManagedObject( cls, mom, mo ) ;
                } else if (md != null) {
                    result = handleManagedData( cls, mom, md ) ;
                } else if (cls.cls().isEnum()) {
                    result = handleEnum( cls ) ;
                } else {
                    result = handleClass( cls, mom ) ;
                }
            } else if (type instanceof EvaluatedArrayType) {
                result = handleArrayType( (EvaluatedArrayType)type, mom ) ;
            } else {
                // this should not happen
                throw new IllegalArgumentException( "Unknown kind of Type "
                    + type ) ;
            }
        } catch (RuntimeException exc) {
            throw exc ;
        } catch (OpenDataException exc) {
            throw new RuntimeException( exc ) ;
        }

        return result ;
    }

    private static TypeConverter handleSimpleType( final EvaluatedType type,
	final OpenType stype ) {

        final EvaluatedType canType = canonicalType( type ) ;

	return new TypeConverterImpl( type, stype ) {
	    public Object toManagedEntity( final Object obj ) {
		return obj ;
	    }

            @Override
	    public Object fromManagedEntity( final Object entity ) {
		return entity ;
	    }

            @Override
	    public boolean isIdentity() {
		return canType.equals( type ) ;
	    }
	} ;
    }

    public static final String NULL_STRING = "<NULL>" ;

    @TraceRegistration
    private static TypeConverter handleManagedObject(
        final EvaluatedClassDeclaration type,
	final ManagedObjectManagerInternal mom, final ManagedObject mo ) {

        TypeConverter result = null ;

        result = new TypeConverterImpl( type, SimpleType.OBJECTNAME ) {
            public Object toManagedEntity( Object obj ) {
                if (obj == null) {
                    return AMXClient.NULL_OBJECTNAME ;
                } else {
                    return mom.getObjectName( obj ) ;
                }
            }

            @Override
            public Object fromManagedEntity( final Object entity ) {
                if (!(entity instanceof ObjectName)) {
                    throw Exceptions.self.entityNotObjectName( entity ) ;
                }

                final ObjectName oname = (ObjectName)entity ;
                if (oname.equals( AMXClient.NULL_OBJECTNAME )) {
                    return null ;
                } else {
                    return mom.getObject( oname ) ;
                }
            }
        } ;

        return result ;
    }

    @TraceRegistration
    private static Collection<AttributeDescriptor> analyzeManagedData(
        final EvaluatedClassDeclaration cls, final ManagedObjectManagerInternal mom ) {

        Collection<AttributeDescriptor> result = null ;

        final EvaluatedClassAnalyzer ca = mom.getClassAnalyzer( cls,
            ManagedData.class ).second() ;

        final Pair<Map<String,AttributeDescriptor>,
            Map<String,AttributeDescriptor>> ainfos =
                mom.getAttributes( ca,
                    ManagedObjectManagerInternal.AttributeDescriptorType
                        .COMPOSITE_DATA_ATTR ) ;

        result = ainfos.first().values() ;

        return result ;
    }

    // XXX need to make tf support static info methods
    @InfoMethod
    private static void describe( String msg, Object data ) {}

    @TraceRegistration
    private static CompositeType makeCompositeType(
        final EvaluatedClassDeclaration cls,
        final ManagedObjectManagerInternal mom, final ManagedData md,
        Collection<AttributeDescriptor> minfos ) {

        CompositeType result = null ;

        String name = md.name() ;
        if (name.equals( "" )) {
            name = mom.getTypeName( cls.cls(), "GMBAL_TYPE",
                md.name() ) ;
        }

        describe( "name", name ) ;

        final String mdDescription = mom.getDescription( cls ) ;
        describe( "mdDescription", mdDescription ) ;

        final int length = minfos.size() ;
        final String[] attrNames = new String[ length ] ;
        final String[] attrDescriptions = new String[ length ] ;
        final OpenType[] attrOTypes = new OpenType[ length ] ;

        int ctr = 0 ;
        for (AttributeDescriptor minfo : minfos) {
            attrNames[ctr] = minfo.id() ;
            attrDescriptions[ctr] = minfo.description() ;
            attrOTypes[ctr] = minfo.tc().getManagedType() ;
            ctr++ ;
        }

        describe( "attrNames=", Arrays.asList(attrNames) ) ;
        describe( "attrDescriptions=", Arrays.asList(attrDescriptions) ) ;
        describe( "attrOTypes=", Arrays.asList(attrOTypes) ) ;

        try {
            result = new CompositeType(
                name, mdDescription, attrNames, attrDescriptions, attrOTypes ) ;
        } catch (OpenDataException exc) {
            throw Exceptions.self.exceptionInMakeCompositeType(exc) ;
        }

        return result ;
    }

    @TraceRegistration
    private static TypeConverter handleManagedData(
        final EvaluatedClassDeclaration cls,
	final ManagedObjectManagerInternal mom, final ManagedData md ) {

        TypeConverter result = null ;
        final Collection<AttributeDescriptor> minfos = analyzeManagedData(
            cls, mom ) ;
        final CompositeType myType = makeCompositeType( cls, mom, md, minfos ) ;
        describe( "minfos=", minfos ) ;
        describe( "myType=", myType ) ;

        result = new TypeConverterImpl( cls, myType ) {
            @TraceRuntime
            public Object toManagedEntity( Object obj ) {
                Object runResult = null ;
                Map<String,Object> data = new HashMap<String,Object>() ;
                for (AttributeDescriptor minfo : minfos) {
                    describe( "Fetching attribute ", minfo.id() ) ;

                    Object value = null ;
                    if (minfo.isApplicable( obj )) {
                        try {
                            FacetAccessor fa = mom.getFacetAccessor( obj ) ;
                            value = minfo.get(fa );
                        } catch (JMException ex) {
                            Exceptions.self.errorInConstructingOpenData(
                                cls.name(), minfo.id(), ex ) ;
                        }
                    }

                    data.put( minfo.id(), value ) ;
                }

                try {
                    runResult = new CompositeDataSupport( myType, data ) ;
                } catch (OpenDataException exc) {
                    throw Exceptions.self.exceptionInHandleManagedData(exc) ;
                }

                return runResult ;
            }
        } ;

        return result ;
    }

    private static TypeConverter handleEnum( final EvaluatedClassDeclaration cls ) {

	return new TypeConverterImpl( cls, SimpleType.STRING ) {
	    public Object toManagedEntity( final Object obj ) {
                if (obj == null)
                    return NULL_STRING ;
                else
                    return obj.toString() ;
	    }

            @Override
            @SuppressWarnings("unchecked")
	    public Object fromManagedEntity( final Object entity ) {
                if (NULL_STRING.equals(entity)) {
                    return null ; 
                }

		if (!(entity instanceof String)) {
                    throw Exceptions.self.notAString(entity) ;
                }

		return Enum.valueOf( cls.cls(), (String)entity ) ;
	    }
	} ;
    }

    // Note: this is only needed on JDK 5, but should work fine on JDK 6 as well.
    private static ArrayType getArrayType( OpenType ot ) throws OpenDataException {
        ArrayType result ;

        if (ot instanceof ArrayType) {
            ArrayType atype = (ArrayType)ot ;
            int dim = atype.getDimension() ;
            OpenType lowestComponentType = atype.getElementOpenType() ;
            result = new ArrayType( dim + 1, lowestComponentType ) ;
        } else {
            result = new ArrayType( 1, ot ) ;
        }

        return result ;
    }

    @SuppressWarnings("unchecked")
    @TraceRegistration
    private static TypeConverter handleArrayType( final EvaluatedArrayType type,
	final ManagedObjectManagerInternal mom ) throws OpenDataException {

        TypeConverter result = null ;
        final EvaluatedType ctype = type.componentType() ;
        final TypeConverter ctypeTc = mom.getTypeConverter( ctype ) ;
        final OpenType cotype = ctypeTc.getManagedType() ;
        final OpenType ot = getArrayType( cotype ) ;

        describe( "ctype", ctype ) ;
        describe( "ctypeTc", ctypeTc  ) ;
        describe( "cotype", cotype ) ;
        describe( "ot", ot ) ;

        result = new TypeConverterImpl( type, ot ) {
            @TraceRuntime
            public Object toManagedEntity( final Object obj ) {
                if (isIdentity()) {
                    return obj ;
                } else {
                    final Class cclass = getJavaClass( cotype ) ;
                    final int length =
                        obj == null ? 0 : Array.getLength( obj ) ;
                    final Object result = Array.newInstance( cclass, length ) ;
                    for (int ctr=0; ctr<length; ctr++) {
                        describe( "Entering (handleArrayType):toManagedEntity",
                            ctr ) ;
                        try {
                            final Object elem = Array.get( obj, ctr ) ;
                            final Object relem =  ctypeTc.toManagedEntity( elem ) ;
                            Array.set( result, ctr, relem ) ;
                        } finally {
                            describe( "Exiting (handleArrayType):toManagedEntity",
                                ctr ) ;
                        }
                    }

                    return result ;
                }
            }

            @Override
            @TraceRuntime
            public Object fromManagedEntity( final Object entity ) {
                if (isIdentity()) {
                    return entity ;
                } else {
                    final Class cclass = getJavaClass( ctype ) ;

                    final int length =
                        entity == null ? 0 : Array.getLength( entity ) ;
                    final Object result = Array.newInstance( cclass, length ) ;
                    for (int ctr=0; ctr<length; ctr++) {
                        describe( "Entering (handleArrayType):fromManagedEntity",
                            ctr ) ;
                        try {
                            final Object elem = Array.get( entity, ctr ) ;
                            final Object relem =
                                ctypeTc.fromManagedEntity( elem ) ;
                            Array.set( result, ctr, relem ) ;
                        } finally {
                            describe( "Exiting (handleArrayType):fromManagedEntity",
                                ctr ) ;
                        }
                    }

                    return result ;
                }
            }

            @Override
            public boolean isIdentity() {
                return ctypeTc.isIdentity() ;
            }
        } ;

        return result ;
    }

    private static final Runnable NoOp = new Runnable() {
        public void run() {}
    } ;

    private static EvaluatedMethodDeclaration findMethod(
        final EvaluatedClassAnalyzer eca, final String mname ) {

        return Algorithms.getFirst( eca.findMethods(
            new UnaryPredicate<EvaluatedMethodDeclaration>() {
                public boolean evaluate( EvaluatedMethodDeclaration m ) {
                    return m.name().equals( mname ) ;
                } 
            } 
        ),  NoOp ) ;
    }

    private static EvaluatedType getReturnType( EvaluatedClassDeclaration decl,
        String mname ) {

        EvaluatedClassAnalyzer eca = new EvaluatedClassAnalyzer( decl ) ;
        EvaluatedMethodDeclaration meth = findMethod( eca, mname ) ;

        if (meth == null) {
            return null ;
        } else {
            return meth.returnType() ;
        }
    }

    private static EvaluatedType getParameterType( 
        EvaluatedClassDeclaration decl, String mname, int pindex ) {

        EvaluatedClassAnalyzer eca = new EvaluatedClassAnalyzer( decl ) ;
        EvaluatedMethodDeclaration meth = findMethod( eca, mname ) ;

        if (meth == null) {
            return null ;
        } else {
            if (pindex < meth.parameterTypes().size()) {
                return meth.parameterTypes().get( pindex ) ;
            } else {
                throw new IndexOutOfBoundsException(
                    "Parameter index is out of bounds" ) ;
            }
        }
    }

    private static Table emptyTable() {
        return new Table() {

            public Object get(Object key) {
                return null ;
            }

            public Iterator iterator() {
                return emptyIterator() ;
            }
        
        } ;
    }

    private static Iterator emptyIterator() {
        List list = new ArrayList() ;
        return list.iterator() ;
    }

    @TraceRegistration
    private static TypeConverter handleClass( 
        final EvaluatedClassDeclaration type,
        final ManagedObjectManagerInternal mom ) {

        TypeConverter result = null ;
        
        // Case 1: Some kind of collection.
        if (Iterable.class.isAssignableFrom(type.cls())) {
            EvaluatedClassDeclaration type2 =
                (EvaluatedClassDeclaration)getReturnType( type, "iterator") ;
            if (type2 == null) {
                throw Exceptions.self.iteratorNotFound(type) ;
            }

            EvaluatedType tcType = getReturnType( type2, "next" ) ;
            if (tcType == null) {
                throw Exceptions.self.nextNotFound(type) ;
            }

            TypeConverter tc = mom.getTypeConverter( tcType ) ;

            result = new TypeConverterListBase( type, tc ) {
                protected Iterator getIterator( Object obj ) {
                    if (obj == null) {
                        return emptyIterator() ;
                    } else {
                        return ((Iterable)obj).iterator() ;
                    }
                }
            } ;
        } else if (Collection.class.isAssignableFrom(type.cls())) {
            EvaluatedClassDeclaration type2 =
                (EvaluatedClassDeclaration)getReturnType( type, "iterator") ;
            EvaluatedType tcType = getReturnType( type2, "next" ) ;
            TypeConverter tc = mom.getTypeConverter( tcType ) ;

            result = new TypeConverterListBase( type, tc ) {
                protected Iterator getIterator( Object obj ) {
                    if (obj == null) {
                        return emptyIterator() ;
                    } else {
                        return ((Iterable)obj).iterator() ;
                    }
                }
            } ;
        } else if (Iterator.class.isAssignableFrom(type.cls())) {
            EvaluatedType tcType = getReturnType( type, "next" ) ;
            TypeConverter tc = mom.getTypeConverter( tcType ) ;

            result = new TypeConverterListBase( type, tc ) {
                protected Iterator getIterator( Object obj ) {
                    if (obj == null) {
                        return emptyIterator() ;
                    } else {
                        return (Iterator)obj ;
                    }
                }
            } ;
        } else if (Enumeration.class.isAssignableFrom(type.cls())) {
            EvaluatedType tcType = getReturnType( type, "next" ) ;

            TypeConverter tc = mom.getTypeConverter( tcType ) ;
            result = new TypeConverterListBase( type, tc ) {
                @SuppressWarnings("unchecked")
                protected Iterator getIterator( Object obj ) {
                    if (obj == null) {
                        return emptyIterator() ;
                    } else {
                        return new EnumerationAdapter( (Enumeration)obj ) ;
                    }
                }
            } ;
        } else if (Map.class.isAssignableFrom(type.cls())) {
            EvaluatedType type1 = getParameterType( type, "put", 0 ) ;
            TypeConverter firstTc = mom.getTypeConverter( type1 ) ;
            EvaluatedType type2 = getReturnType( type, "put" ) ;
            TypeConverter secondTc = mom.getTypeConverter( type2 ) ;

            result = new TypeConverterMapBase( type, firstTc, secondTc ) {
                @SuppressWarnings("unchecked")
                protected Table getTable( Object obj ) {
                    if (obj == null) {
                        return emptyTable() ;
                    } else {
                        return new TableMapImpl( (Map)obj ) ;
                    }
                }
            } ;
        } else if (Dictionary.class.isAssignableFrom(type.cls())) {
            EvaluatedType type1 = getParameterType( type, "put", 0 ) ;
            TypeConverter firstTc = mom.getTypeConverter( type1 ) ;
            EvaluatedType type2 = getReturnType( type, "put" ) ;
            TypeConverter secondTc = mom.getTypeConverter( type2 ) ;

            result = new TypeConverterMapBase( type, firstTc, secondTc ) {
                @SuppressWarnings("unchecked")
                protected Table getTable( Object obj ) {
                    if (obj == null) {
                        return emptyTable() ;
                    } else {
                        return new TableDictionaryImpl( (Dictionary)obj ) ;
                    }
                }
            } ;
        } else {
            result = handleAsString( type ) ;
        }
        
        return result ;
    } 
	
    @SuppressWarnings({"unchecked"})
    private static TypeConverter handleAsString( 
        final EvaluatedClassDeclaration cls ) {

        Constructor tcons ;
        try {
            SecurityManager sman = System.getSecurityManager() ;
            if (sman == null) {
                tcons = cls.cls().getDeclaredConstructor(String.class) ;
            } else {
                tcons = Algorithms.doPrivileged(
                    new Algorithms.Action<Constructor>() {
                        public Constructor run() throws Exception {
                            return cls.cls().getDeclaredConstructor(String.class ) ;
                        }
                    }) ;
            }
        } catch (Exception exc) {
            Exceptions.self.noStringConstructorAvailable( exc, cls.name() ) ;
            tcons = null ;
        }
        final Constructor cons = tcons ;

	return new TypeConverterImpl( cls, SimpleType.STRING ) {
	    public Object toManagedEntity( Object obj ) {
                if (obj == null) {
                    return NULL_STRING ;
                } else {
                    return obj.toString() ;
                }
	    }

            @Override
	    public Object fromManagedEntity( final Object entity ) {
                if (entity == null) {
                    return null ;
                }

		if (cons == null) {
                    throw Exceptions.self.noStringConstructor(cls.cls());
                }
                
		try {
                    final String str = (String) entity;
                    return cons.newInstance(str);
                } catch (InstantiationException exc) {
                    throw Exceptions.self.stringConversionError(cls.cls(), exc ) ;
                } catch (IllegalAccessException exc) {
                    throw Exceptions.self.stringConversionError(cls.cls(), exc ) ;
                } catch (InvocationTargetException exc) {
                    throw Exceptions.self.stringConversionError(cls.cls(), exc ) ;
                }
	    }
	} ;
    }

    private static class EnumerationAdapter<T> implements Iterator<T> {
        final private Enumeration<T> enumeration ;

        public EnumerationAdapter( final Enumeration<T> en ) {
            this.enumeration = en ;
        }

        public boolean hasNext() {
            return enumeration.hasMoreElements() ;
        }

        public T next() {
            return enumeration.nextElement() ;
        }

        public void remove() {
            throw Exceptions.self.removeNotSupported() ;
        }
    }

    // TypeConverter that throws exceptions for its methods.  Used as a 
    // place holder to detect recursive types.
    public static class TypeConverterPlaceHolderImpl implements TypeConverter {
        private EvaluatedType et ;

        public TypeConverterPlaceHolderImpl( EvaluatedType type ) {
            et = type ;
        }

        public EvaluatedType getDataType() {
            throw Exceptions.self.recursiveTypesNotSupported( et ) ;
        }

        public OpenType getManagedType() {
            throw Exceptions.self.recursiveTypesNotSupported( et ) ;
        }

        public Object toManagedEntity( Object obj ) {
            throw Exceptions.self.recursiveTypesNotSupported( et ) ;
        }

        public Object fromManagedEntity( Object entity ) {
            throw Exceptions.self.recursiveTypesNotSupported( et ) ;
        }

        public boolean isIdentity() {
            throw Exceptions.self.recursiveTypesNotSupported( et ) ;
        }
    }

    private abstract static class TypeConverterListBase 
        extends TypeConverterImpl {
        
        final TypeConverter memberTc ;

        public TypeConverterListBase( final EvaluatedType dataType,
            final TypeConverter memberTc ) {
            
            super( dataType, makeArrayType( memberTc.getManagedType() ) ) ;
            this.memberTc = memberTc ;
        }

        @SuppressWarnings("unchecked")
        private static ArrayType makeArrayType( final OpenType ot ) {
            try {
                return getArrayType( ot ) ;
            } catch (OpenDataException exc) {
                throw Exceptions.self.openTypeInArrayTypeException( ot, exc) ;
            }
        }

        protected abstract Iterator getIterator( Object obj ) ;

        public Object toManagedEntity( final Object obj ) {
            final Iterator iter = getIterator( obj ) ;
            final List<Object> list = new ArrayList<Object>() ;
            while (iter.hasNext()) {
                list.add( iter.next() ) ;
            }

            final Class cclass = getJavaClass( memberTc.getManagedType() ) ;
            final Object result = Array.newInstance( cclass, list.size() ) ;
            int ctr = 0 ;
            for (Object elem : list) {
                final Object mappedElem = memberTc.toManagedEntity( elem ) ;
                Array.set( result, ctr++, mappedElem ) ;
            }

            return result ;
        }
    }

    private interface Table<K,V> extends Iterable<K> {
        V get( K key ) ;
    }

    private static class TableMapImpl<K,V> implements Table<K,V> {
        final private Map<K,V> map ;

        public TableMapImpl( final Map<K,V> map ) {
            this.map = map ;
        }

        public Iterator<K> iterator() {
            return map.keySet().iterator() ;
        }

        public V get( final K key ) {
            return map.get( key ) ;
        }
    }

    private static class TableDictionaryImpl<K,V> implements Table<K,V> {
        final private Dictionary<K,V> dict ;

        public TableDictionaryImpl( final Dictionary<K,V> dict ) {
            this.dict = dict ;
        }

        @SuppressWarnings("unchecked")
        public Iterator<K> iterator() {
            return new EnumerationAdapter( dict.keys() ) ;
        }

        public V get( final K key ) {
            return dict.get( key ) ;
        }
    }

    private abstract static class TypeConverterMapBase 
        extends TypeConverterImpl {
        
        final private TypeConverter keyTypeConverter ;
        final private TypeConverter valueTypeConverter ;

        public TypeConverterMapBase( EvaluatedType dataType,
            TypeConverter keyTypeConverter, TypeConverter valueTypeConverter ) {
            
            super( dataType, makeMapTabularType( keyTypeConverter, 
                valueTypeConverter ) ) ;
            this.keyTypeConverter = keyTypeConverter ;
            this.valueTypeConverter = valueTypeConverter ;
        }

        private static TabularType makeMapTabularType( 
            final TypeConverter firstTc, final TypeConverter secondTc ) {
            
            final String mapType = firstTc + "->" + secondTc ;

            final String[] itemNames = new String[] { "key", "value" } ;

            final String description = Exceptions.self.rowTypeDescription(
                mapType) ;

            final String[] itemDescriptions = new String[] {
                Exceptions.self.keyFieldDescription(mapType),
                Exceptions.self.valueFieldDescription(mapType)
            } ;

            final OpenType[] itemTypes = new OpenType[] {
                firstTc.getManagedType(), secondTc.getManagedType() 
            } ;

            try {
                final CompositeType rowType = new CompositeType( mapType,
                    description, itemNames, itemDescriptions, itemTypes ) ;

                final String[] keys = new String[] { "key" } ;
                final String tableName =
                    Exceptions.self.tableName( mapType ) ;
                final String tableDescription = 
                    Exceptions.self.tableDescription( mapType ) ;

                final TabularType result = new TabularType( tableName,
                    tableDescription, rowType, keys ) ;

                return result ;
            } catch (OpenDataException exc) {
                throw Exceptions.self.exceptionInMakeMapTabularType(exc) ;
            }
        }

        protected abstract Table getTable( Object obj ) ;

        @SuppressWarnings("unchecked")
        public Object toManagedEntity( Object obj ) {
            try {
                final Table table = getTable( obj ) ;
                final TabularType ttype = (TabularType)getManagedType() ;
                final CompositeType ctype = ttype.getRowType() ;
                final TabularData result = new TabularDataSupport( ttype ) ;
                for (Object key : table) {
                    @SuppressWarnings("unchecked")
                    final Object value = table.get( key ) ;
                    final Object mappedKey = 
                        keyTypeConverter.toManagedEntity( key ) ;
                    final Object mappedValue = 
                        valueTypeConverter.toManagedEntity( value ) ;
                    final Map items = new HashMap() ;
                    items.put( "key", mappedKey ) ;
                    items.put( "value", mappedValue ) ;
                    CompositeDataSupport cdata = new CompositeDataSupport( 
                        ctype, items ) ;
                    result.put( cdata ) ;
                }

                return result ;
            } catch (OpenDataException exc) {
                throw Exceptions.self.excInMakeMapTabularDataToManagedEntity( 
                    exc ) ;
            }
        }
    }

    // Basic support for all TypeConverters.
    @DumpToString
    protected final EvaluatedType dataType ;
    @DumpToString
    protected final OpenType managedType ;

    protected TypeConverterImpl( final EvaluatedType dataType,
        final OpenType managedType ) {
        
        this.dataType = dataType ;
        this.managedType = managedType ;
    }

    /* Java generic type of attribute in problem-domain Object.
     */
    public final EvaluatedType getDataType() {
        return dataType ;
    }

    /* Open MBeans Open Type for management domain object.
     */
    public final OpenType getManagedType() {
        return managedType ;
    }

    /* Convert from a problem-domain Object obj to a ManagedEntity.
     */
    public abstract Object toManagedEntity( Object obj ) ;

    /* Convert from a ManagedEntity to a problem-domain Object.
     */
    public Object fromManagedEntity( Object entity ) {
        throw Exceptions.self.openToJavaNotSupported(managedType, dataType) ;
    }

    /* Returns true if this TypeConverter is an identity transformation.
     */
    public boolean isIdentity() {
        return false ;
    }
    
    private String displayOpenType( OpenType otype ) {
        if (otype instanceof SimpleType) {
            SimpleType stype = (SimpleType)otype ;
            return "SimpleType(" + stype.getTypeName() + ")" ;
        } else if (otype instanceof ArrayType) {
            ArrayType atype = (ArrayType)otype ;
            return "ArrayType(" + displayOpenType( atype.getElementOpenType() )
                + "," + atype.getDimension() + ")" ;
        } else if (otype instanceof CompositeType) {
            CompositeType ctype = (CompositeType)otype ;
            return "CompositeType(" + ctype.getTypeName() + ")" ;
        } else if (otype instanceof TabularType) {
            TabularType ttype = (TabularType)otype ;
            return "TabularType(" + ttype.getTypeName() + ","
                + "rowType=" + ttype.getRowType()
                + "indexNames=" + ttype.getIndexNames() + ")" ;
        } else {
            return "UNKNOWN(" + otype + ")" ;
        }
    }

	@Override
    public String toString() {
        return "TypeConverter[dataType=" + dataType 
            + ",managedType=" + displayOpenType( managedType ) + "]" ;
    }
}

