package de.uni_hamburg.fs;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Enumeration;

import collections.CollectionEnumeration;
import collections.HashedSet;
import collections.Set;
import collections.UpdatableSet;

import de.renew.util.ClassSource;
import de.renew.util.StringUtil;
import de.renew.util.Types;


/**
 * Represents a wrapper for Java classes that implements the Concept interface.
 * This class provides access to Java class features and methods through the
 * feature structure system.
 */
public class JavaConcept implements Concept {

    /**
     * Logger instance for this class
     */
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(JavaConcept.class);
    /**
     * Empty object array used for method invocations without parameters
     */
    public static final Object[] NOPARAM = new Object[] { };
    /**
     * Empty class array used for method signatures without parameters
     */
    public static final Class<?>[] NOPARAMDEF = new Class<?>[] { };

    /**
     * Contains all method names that should be skipped during feature discovery,
     * including common Object methods and internal system methods
     */
    static final Object[] FIRSTINDEX = new Object[] { Integer.valueOf(0) };
    /**
     * Set of method names that should be excluded from feature discovery
     */
    private static final UpdatableSet DONT_USE = new HashedSet();

    static {
        DONT_USE.include("getClass");
        DONT_USE.include("hashCode");
        DONT_USE.include("clone");
        DONT_USE.include("getPeer");
        DONT_USE.include("create");
        DONT_USE.include("build");
        DONT_USE.include("toString"); // or should we allow this?
        DONT_USE.include("paramString"); // or should we allow this?


        //dontInstantiate.include("java.awt.EventQueue");
        DONT_USE.include("getNextEvent");
        DONT_USE.include("peekEvent");
    }

    /**
     * Characters representing visibility levels: private (-), protected (#), public (+)
     */
    private static final String VISIBILITY_CHARS = "-#+";

    /**
     * The wrapped Java class that this concept represents
     */
    private Class<?> _javaClass;

    /**
     * Fully qualified name of the wrapped Java class
     */
    private String _javaClassName;

    /**
     * Table mapping feature names to their implementations or types
     */
    private transient OrderedTable _approp = new OrderedTable();

    // from featureNames to Classes or JavaFeatures
    /**
     * Indicates if this concept represents a concrete class rather than an interface/abstract class
     */
    private boolean _isClass;

    /**
     * Indicates if the wrapped class can be instantiated (has accessible no-arg constructor)
     */
    private boolean _canInstantiate = false;

    /** Construct a new wrapper for the given Java Type. */
    JavaConcept(Class<?> javaClass) {
        if (Enumeration.class.isAssignableFrom(javaClass)) {
            throw new RuntimeException(
                "Someone tried to build a JavaConcept for " + javaClass.getName());
        }
        int mods = javaClass.getModifiers();
        this._javaClass = javaClass;
        _javaClassName = javaClass.getName();


        //logger.debug("Building JavaConcept for "+javaClassName);
        _isClass = !Modifier.isAbstract(mods) && !Modifier.isInterface(mods);
        if (!Modifier.isPublic(mods)) {
            _javaClassName += "(n/a)";
        } else {
            findFeatures(new StringBuffer());
        }
    }

    /** Return the name of this JavaConcept. */
    @Override
    public String getName() {
        int dotpos = _javaClassName.lastIndexOf(".");
        if (dotpos == -1) {
            return _javaClassName;
        } else {
            return _javaClassName.substring(dotpos + 1);
        }
    }

    /** Return the name of the namespace this Concept belongs to. */
    @Override
    public String getNamespace() {
        return getNamespace(_javaClassName);
    }

    /**
     * Extracts the namespace (package name) from a fully qualified Java class name.
     * @param javaClassName The fully qualified Java class name
     * @return The namespace portion of the class name, or the full name if no package exists
     */
    public static String getNamespace(String javaClassName) {
        int dotpos = javaClassName.lastIndexOf(".");
        if (dotpos == -1) {
            return javaClassName;
        } else {
            return javaClassName.substring(0, dotpos);
        }
    }

    /** Return the full name of this Concept in the form
     * namespace::name.
     */
    @Override
    public String getFullName() {
        return _javaClassName;
    }

    /** Return the Java Type that is wrapped by this JavaConcept.
     * @return The wrapped Java Class object
     */
    public Class<?> getJavaClass() {
        return _javaClass;
    }

    /**
     * Checks if the wrapped Java class can be instantiated.
     * A class can be instantiated if it has an accessible no-arg constructor.
     * @return true if the class can be instantiated, false otherwise
     */
    public boolean canInstantiate() {
        return _canInstantiate;
    }

    /**
     * Custom deserialization method called when an instance of JavaConcept is being deserialized.
     * After reading the default serialized fields, it rebuilds the transient feature table
     * by calling findFeatures().
     * @param in the ObjectInputStream to read serialized data from
     * @throws java.io.IOException    if there is an I/O error while reading the stream
     * @throws ClassNotFoundException if the class of a serialized object cannot be found
     * @see java.io.Serializable
     */
    private void readObject(java.io.ObjectInputStream in)
        throws java.io.IOException, ClassNotFoundException
    {
        in.defaultReadObject();
        findFeatures(new StringBuffer());
    }

    /** Returns the JavaFeature for the given feature.
     *  Only call this method for instantiable JavaConcepts!
     */
    JavaFeature getJavaFeature(Name featureName) {
        return (JavaFeature) _approp.at(featureName);
    }

    /** Return whether this JavaConcept is-a {@literal <that>} Concept.
     * In other words, return whether this Concept is more special than {@literal <that>}
     * Concept.
     */
    @Override
    public boolean isa(Concept that) {
        if (that instanceof JavaConcept) {
            Class<?> thatJavaClass = ((JavaConcept) that).getJavaClass();
            return thatJavaClass.isAssignableFrom(getJavaClass());
        }
        return false;
    }

    /** Return whether this JavaConcept is-not-a "that" Concept.
     * In other words, return whether this JavaConcept is incompatible with
     *  "that" JavaConcept. This is the case if
     * <ol><li>the other Concept is not a JavaConcept,</li>
     *     <li>both JavaConcepts are classes which are not in a subclass
     *              relation, or</li>
     *     <li> the Java classes/interfaces define methods in an
     *               incompatible way.</li>
     * </ol>
     */
    @Override
    public boolean isNotA(Concept that) {
        // the following is checked before caling isNotA,
        // so it doesn't have to be checked again:
        //    if (this.isa(that) || that.isa(this))
        //       return false;
        //      if (that instanceof JavaObjectFS) {
        //         return true; // the only chance was: that.isa(this)
        //      }
        if (Modifier.isFinal(getJavaClass().getModifiers())) {
            return true;
        }
        if (that instanceof JavaConcept) {
            Class<?> thatJavaClass = ((JavaConcept) that).getJavaClass();
            if (Modifier.isFinal(thatJavaClass.getModifiers())) {
                return true;
            }
            if (thatJavaClass.isInterface()) {
                return false; // method-inconsistencies are not checked yet!
            }
        }
        if (getJavaClass().isInterface()) {
            return false; // method-inconsistencies are not checked yet!
        }
        return true;
    }

    /** Return whether this Concept is extensional. */
    @Override
    public boolean isExtensional() {
        return false;
    }

    /**
     * Look for the most general common extensional supertype of this and "that".
     */
    @Override
    public ConceptEnumeration extensionalSuperconcepts() {
        return null; // not extensional, should never be called!
    }

    /**
     * Return whether the feature {@literal <feature>} is appropriate in this Concept.
     */
    @Override
    public boolean isApprop(Name feature) {
        return _approp.includesKey(feature);
    }

    private Class<?> getClassOf(Name feature) {
        if (_isClass) {
            return getJavaFeature(feature).getJavaClass();
        } else {
            return (Class<?>) _approp.at(feature);
        }
    }

    /** Return the required ParsedType for the given feature. */
    @Override
    public ParsedType appropParsedType(Name featureName) throws NoSuchFeatureException {
        return TypeSystem.instance().getParsedType(getClassOf(featureName));
    }

    /** Return the required Type of the Value found under the given feature. */
    @Override
    public Type appropType(Name featureName) throws NoSuchFeatureException {
        return TypeSystem.instance().getType(getClassOf(featureName));
    }

    /** Return an Enumeration of all appropriate features. */
    @Override
    public CollectionEnumeration appropFeatureNames() {
        return _approp.keys();
    }

    @Override
    public String toString() {
        return getName(); //+"@"+hashCode();
    }

    @Override
    public int hashCode() {
        return getJavaClass().hashCode();
    }

    @Override
    public boolean equals(Object that) {
        if (that instanceof JavaConcept) {
            return ((JavaConcept) that).getJavaClass().equals(getJavaClass());
        }
        return false;
    }

    /**
     * Finds all features of this JavaConcept and adds their UML description
     * @param umlDescr Buffer to store UML description
     * @return Set of found features
     */
    public Set findFeatures(StringBuffer umlDescr) {
        return findFeatures(umlDescr, getWellKnownPackages(_javaClass));
    }

    /**
     * Finds all features of this JavaConcept and adds their UML description
     * @param umlDescr  Buffer to store UML description
     * @param wellKnown Array of well-known package names
     * @return Set of found features
     */
    public Set findFeatures(StringBuffer umlDescr, String[] wellKnown) {
        //   Object sample=null;
        // Do not try to build a sample instance anymore!
        // Problems:
        // * some classes have side effects when instantiated
        //   (e.g. java.awt.EventQueue)
        // * if we detect a "volatile" method, it may be inherited from
        //   an abstract superclass or an interface. The type system does
        //   not allow to remove features in subtypes!
        //
        //      if (!dontInstantiate.includes(javaClassName)) {
        //        try {
        //      // try to build an instance of this class:
        //      sample=javaClass.newInstance();
        //      //logger.debug("Built a sample instance of "+javaClassName);
        //        } catch (Throwable e) {
        //      //logger.debug("Cannot instantiate "+javaClass);
        //        }
        //      }
        //      canInstantiate=sample!=null;
        // Instead, check for no-arg constructor:
        try {
            _javaClass.getConstructor(NOPARAMDEF);
            _canInstantiate = true;
        } catch (Throwable t) {
            // no accessible no-arg constructor, leave canInstantiate false.
        }
        addClassDescr(_javaClass, umlDescr);
        if (_javaClass == String.class) {
            return new HashedSet();
        }
        addFieldFeatures(umlDescr, wellKnown);
        return addMethodFeatures(umlDescr, wellKnown);
    }

    private static void addClassDescr(Class<?> clazz, StringBuffer umlDescr) {
        if (clazz.isInterface()) {
            umlDescr.append("«interface»\n");
        }
        int mods = clazz.getModifiers();
        if (!Modifier.isPublic(mods)) {
            addModifierDescr(mods, umlDescr, false);
        }
        umlDescr.append(Types.typeToString(clazz, null));
        umlDescr.append("\n");
    }

    /**
     * Returns the visibility level (0-2) for the given modifiers.
     * 0 = private, 1 = protected/package, 2 = public
     * @param mods The modifier flags to check
     * @return The visibility level (0-2)
     */
    public static int getVisibilityLevel(int mods) {
        if (Modifier.isPublic(mods)) {
            return 2;
        } else if (Modifier.isPrivate(mods)) {
            return 0;
        } else {
            return 1;
        }
    }

    /**
     * Converts a visibility character to a numeric visibility level.
     * - '-' (private) returns 0
     * - '#' (protected/package) returns 1
     * - '+' (public) returns 2
     * @param visibility The visibility character to convert ('-', '#', or '+')
     * @return The numeric visibility level (0-2) or -1 if character not found
     */
    public static int getVisibilityLevel(char visibility) {
        return VISIBILITY_CHARS.indexOf(visibility);
    }

    private static void addModifierDescr(int mods, StringBuffer umlDescr, boolean showPublic) {
        if (Modifier.isStatic(mods)) {
            umlDescr.append("_");
        } else if (Modifier.isAbstract(mods) && !Modifier.isInterface(mods)) {
            umlDescr.append("\\");
        }
        if (showPublic || !Modifier.isPublic(mods)) {
            umlDescr.append(VISIBILITY_CHARS.charAt(getVisibilityLevel(mods))).append(" ");
        }
    }

    /**
     * Ensures that a class is loaded by attempting to load it through ClassSource.
     * This helps avoid class loading issues with certain JVMs.
     * @param javaClass The class to ensure is loaded
     */
    public static void loadClass(Class<?> javaClass) {
        if (!javaClass.isPrimitive() && !javaClass.isArray()) {
            try {
                // TODO: Find out what Frank thought.
                //       Which class loader should be used here?
                //       Is the bug mentioned above really a bug
                //       or due to different class loaders?
                ClassSource.classForName(javaClass.getName());
            } catch (ClassNotFoundException e) {
                // should never happen!
                LOGGER.error(e.getMessage(), e);
            } catch (LinkageError e) {
                // should never happen!
                LOGGER.error(e.getMessage(), e);
            }
        }
    }

    private void addFieldFeatures(StringBuffer umlDescr, String[] wellKnown) {
        Field[] field = _javaClass.getFields();
        Arrays.sort(field, (f1, f2) -> f1.toString().compareTo(f2.toString()));
        for (int i = 0; i < field.length; ++i) {
            int mod = field[i].getModifiers();
            if (!Modifier.isStatic(mod) && !Modifier.isVolatile(mod)) {
                Class<?> featureType = field[i].getType();


                // JAVA BUG: assure that featureType is loaded correctly:
                loadClass(featureType);
                if (!Enumeration.class.isAssignableFrom(featureType)) {
                    Name featureName = new Name(field[i].getName());
                    Object javaType;
                    if (_isClass) {
                        javaType = new FieldFeature(field[i]);
                    } else {
                        javaType = featureType;
                    }
                    try {
                        _javaClass.getDeclaredField(featureName._name);
                        umlDescr.append(featureName).append(": ")
                            .append(Types.typeToString(featureType, wellKnown)).append("\n");
                    } catch (NoSuchFieldException e) {
                        // do nothing
                    } catch (SecurityException s) {
                        // do nothing
                    }
                    _approp.putAt(featureName, javaType);
                }
            }
        }
    }

    /**
     * Converts a getter method name to a feature name by removing the "get" or "is" prefix
     * and adjusting the case of the first character.
     * <p>
     * For example:
     * - "getName" becomes "name"
     * - "isValid" becomes "valid"
     * - "getXMLValue" remains "XMLValue" (preserves multiple uppercase letters)
     * @param getMethodName The getter method name to convert
     * @return The converted feature name with appropriate casing
     */
    public static String toFeatureName(String getMethodName) {
        int startIndex = 0;
        if (getMethodName.startsWith("get")) {
            startIndex = 3;
        } else if (getMethodName.startsWith("is")) {
            startIndex = 2;
        }
        if (startIndex > 0 && !StringUtil.isUpperCaseAt(getMethodName, startIndex)) {
            startIndex = 0; // did not continue with upper case character!
        }
        getMethodName = getMethodName.substring(startIndex);
        if (StringUtil.isUpperCaseAt(getMethodName, 1)) {
            // if more than one upper case character, leave as is
            return getMethodName;
        }
        return StringUtil.firstToLowerCase(getMethodName);
    }

    private boolean isDeclared(Class<?> clazz, Method method) {
        try {
            clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
            return true;
        } catch (NoSuchMethodException e) {
            // do nothing
        } catch (SecurityException s) {
            // do nothing
        }
        return false;
    }

    private Set addMethodFeatures(StringBuffer umlDescr, String[] wellKnown) {
        UpdatableSet usedMethods = new HashedSet();
        Method[] method = _javaClass.getMethods();
        Arrays.sort(method, (m1, m2) -> m1.toString().compareTo(m2.toString()));
        for (int i = 0; i < method.length; ++i) {
            Class<?> featureType = method[i].getReturnType();
            if (Modifier.isPublic(featureType.getModifiers())) {
                int mod = method[i].getModifiers();
                String featureStr = method[i].getName();
                Class<?>[] paramType = method[i].getParameterTypes();
                int noParams = paramType.length;
                if (!Modifier.isStatic(mod) && featureType != Void.TYPE && noParams <= 1
                    && !DONT_USE.includes(featureStr)) {
                    // construct feature name:
                    featureStr = toFeatureName(featureStr);
                    Name featureName = new Name(featureStr);


                    // JAVA BUG: assure that featureType is loaded correctly:
                    loadClass(featureType);
                    Object javaType = null;

                    // check for index feature:
                    if (noParams == 1 && paramType[0] == Integer.TYPE) {
                        javaType =
                            findIndexFeature(method[i], featureStr, featureType, usedMethods); //,sample);
                        featureName = new Name(featureStr + "s"); // set to plural
                    } else if (noParams == 0) {
                        // check for bean feature.
                        if (!_isClass) {
                            javaType = featureType;
                        } else {
                            //if (checkResult(featureStr,method[i],sample))
                            Method setMethod = findSetMethod(featureStr, featureType);
                            if (setMethod != null) {
                                usedMethods.include(setMethod);
                            }
                            javaType = new BeanFeature(method[i], setMethod);
                        }
                        usedMethods.include(method[i]);
                    }
                    if (javaType != null) {
                        boolean canWrite = _isClass && ((JavaFeature) javaType).canSet();
                        if (canWrite || !_approp.includesKey(featureName)) {
                            _approp.putAt(featureName, javaType);
                            if (_isClass) {
                                featureType = ((JavaFeature) javaType).getJavaClass();
                            } else {
                                featureType = (Class<?>) javaType;
                            }
                            if (isDeclared(_javaClass, method[i])) {
                                umlDescr.append(featureName).append(": ")
                                    .append(Types.typeToString(featureType, wellKnown));
                                if (_isClass && !canWrite) {
                                    umlDescr.append(" {readOnly}");
                                }
                                umlDescr.append("\n");
                            }
                        }
                    }
                }
            }
        }
        return usedMethods;
    }

    /**
     * Returns an array containing java.lang and the package name of the given class.
     * @param clazz The class to get package name from
     * @return Array with well-known package names
     */
    public static String[] getWellKnownPackages(Class<?> clazz) {
        return new String[] { "java.lang", Types.getPackageName(clazz) };
    }

    /**
     * Returns a UML description of this JavaConcept's class
     * @return UML class description string
     */
    public String getClassDescription() {
        return getClassDescription(getWellKnownPackages(_javaClass));
    }

    /**
     * Returns a UML description of this JavaConcept's class
     * @param wellKnown Array of well-known package names
     * @return UML class description string
     */
    public String getClassDescription(String[] wellKnown) {
        // re-establish features and use protocol as description:
        _approp = new OrderedTable();
        StringBuffer umlDescr = new StringBuffer();
        Set usedMethods = findFeatures(umlDescr, wellKnown);
        umlDescr.append("\n");
        Method[] method = _javaClass.getDeclaredMethods();
        Arrays.sort(method, (m1, m2) -> m1.toString().compareTo(m2.toString()));
        for (int i = 0; i < method.length; ++i) {
            int mods = method[i].getModifiers();
            if (Modifier.isPublic(mods) && !Modifier.isStatic(mods)
                && !usedMethods.includes(method[i])) {
                addMethodDescription(method[i], umlDescr, wellKnown);
            }
        }
        return umlDescr.toString();
    }

    /**
     * Returns a UML description of the given class implementation
     * @param clazz           The class to describe
     * @param visibilityLevel Minimum visibility level to include
     * @return UML implementation description string
     */
    public static String getImplementationDescription(Class<?> clazz, int visibilityLevel) {
        return getImplementationDescription(clazz, visibilityLevel, getWellKnownPackages(clazz));
    }

    /**
     * Returns a UML description of the given class implementation.
     * @param clazz           The class to describe
     * @param visibilityLevel Minimum visibility level to include
     * @param wellKnown       Array of well-known package names
     * @return UML implementation description string
     */
    public static String getImplementationDescription(
        Class<?> clazz, int visibilityLevel, String[] wellKnown)
    {
        //logger.debug(Types.typeToString(clazz,Types.ALLPACKAGES));
        //if (clazz.getSuperclass()!=null)
        //logger.debug(" extends "+Types.typeToString(clazz.getSuperclass(),
        //        Types.ALLPACKAGES));
        //Class[] interfaces=clazz.getInterfaces();
        //for (int i=0; i<interfaces.length; ++i)
        //  logger.debug(" implements "+Types.typeToString(interfaces[i],
        //        Types.ALLPACKAGES));
        StringBuffer umlDescr = new StringBuffer();
        addClassDescr(clazz, umlDescr);
        Field[] field = clazz.getDeclaredFields();
        Arrays.sort(field, (f1, f2) -> f1.toString().compareTo(f2.toString()));
        for (int i = 0; i < field.length; ++i) {
            if (field[i].getName().indexOf("$") < 0) {
                int mods = field[i].getModifiers();
                if (getVisibilityLevel(mods) >= visibilityLevel) {
                    addModifierDescr(mods, umlDescr, true);
                    umlDescr.append(field[i].getName()).append(": ")
                        .append(Types.typeToString(field[i].getType(), wellKnown));
                    if (Modifier.isFinal(mods)) {
                        umlDescr.append(" {frozen}");
                    }
                    umlDescr.append("\n");
                }
            }
        }
        umlDescr.append("\n");
        Method[] method = clazz.getDeclaredMethods();
        Arrays.sort(method, (m1, m2) -> m1.toString().compareTo(m2.toString()));
        for (int i = 0; i < method.length; ++i) {
            if (method[i].getName().indexOf("$") < 0) {
                int mods = method[i].getModifiers();
                if (getVisibilityLevel(mods) >= visibilityLevel) {
                    if (!clazz.isInterface()) { // everything's public+abstract anyway
                        addModifierDescr(mods, umlDescr, true);
                    }
                    addMethodDescription(method[i], umlDescr, wellKnown);
                }
            }
        }
        return umlDescr.toString();
    }

    private static void addMethodDescription(
        Method method, StringBuffer umlDescr, String[] wellKnown)
    {
        umlDescr.append(method.getName()).append("(");
        Class<?>[] paramTypes = method.getParameterTypes();
        for (int p = 0; p < paramTypes.length; ++p) {
            umlDescr.append(Types.typeToString(paramTypes[p], wellKnown));
            if (p < paramTypes.length - 1) {
                umlDescr.append(", ");
            }
        }
        umlDescr.append(")");
        Class<?> returnType = method.getReturnType();
        if (returnType != Void.TYPE) {
            umlDescr.append(": ").append(Types.typeToString(returnType, wellKnown));
        }
        umlDescr.append("\n");
    }

    private Method findSetMethod(String featureStr, Class<?> featureType) {
        // look for appropriate set method:
        Method setMethod = null;
        if (_canInstantiate) {
            String setStr = "set" + StringUtil.firstToUpperCase(featureStr);
            try {
                setMethod = _javaClass.getMethod(setStr, new Class<?>[] { featureType });
                if (Modifier.isStatic(setMethod.getModifiers())
                    || setMethod.getReturnType() != Void.TYPE) {
                    // or should we allow a returned object?
                    setMethod = null;
                }
            } catch (Throwable e1) {
                // We also continue if there is no appropriate set method.
            }
        }
        return setMethod;
    }

    private Object findIndexFeature(
        Method getMethod, String featureStr, Class<?> featureType, UpdatableSet usedMethods)
    {
        //,Object sample) {
        if (featureType.isPrimitive()) {
            return null;
        }
        Object javaType = null;
        try {
            // look for "...Count"-method (mandatory):
            Method countMethod = _javaClass.getMethod(getMethod.getName() + "Count", NOPARAMDEF);
            if (!Modifier.isStatic(countMethod.getModifiers())
                && countMethod.getReturnType() == Integer.TYPE) {
                //&& checkResult(featureStr,countMethod,sample)) {
                usedMethods.include(countMethod);
                usedMethods.include(getMethod);
                if (_isClass) {
                    // look for "add..."-method (optional):
                    Method addMethod = null;
                    try {
                        addMethod = _javaClass.getMethod(
                            "add" + StringUtil.firstToUpperCase(featureStr),
                            new Class<?>[] { featureType });
                    } catch (Throwable t1) {
                        try {
                            addMethod = _javaClass.getMethod("add", new Class<?>[] { featureType });
                        } catch (Throwable t2) {
                            //logger.debug("AddMethod add"+StringUtil.firstToUpperCase(featureStr)+
                            //         "not found: "+t1);
                            //logger.debug("AddMethod add not found: "+t2);
                        }
                    }
                    if (addMethod != null) {
                        usedMethods.include(addMethod);
                    }


                    //logger.debug("Found addMethod:\n"+addMethod);
                    javaType = new IndexFeature(countMethod, getMethod, addMethod);
                } else {
                    javaType = Array.newInstance(featureType, 0).getClass();
                }
            }
        } catch (Throwable t0) {
            javaType = null;
        }
        return javaType;
    }
}