/*
 * Copyright 2004-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package sun.management;
import java.lang.management.MemoryUsage;
import java.lang.management.MemoryNotificationInfo;
import java.lang.management.MonitorInfo;
import java.lang.management.LockInfo;
import java.lang.management.ThreadInfo;
import java.lang.reflect.*;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.io.InvalidObjectException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import javax.management.*;
import javax.management.openmbean.*;
import static javax.management.openmbean.SimpleType.*;
import com.sun.management.VMOption;

/**
 * A mapped mxbean type maps a Java type to an open type.
 * Only the following Java types are mappable
 * (currently required by the platform MXBeans):
 *   1. Primitive types
 *   2. Wrapper classes such java.lang.Integer, etc
 *   3. Classes with only getter methods and with a static "from" method
 *      that takes a CompositeData argument.
 *   4. E[] where E is a type of 1-4 (can be multi-dimensional array)
 *   5. List<E> where E is a type of 1-3
 *   6. Map<K, V> where K and V are a type of 1-4
 *
 * OpenDataException will be thrown if a Java type is not supported.
 */
// Suppress unchecked cast warnings at line 442, 523 and 546
// Suppress unchecked calls at line 235, 284, 380 and 430.
@SuppressWarnings("unchecked")
public abstract class MappedMXBeanType {
    private static final WeakHashMap<Type,MappedMXBeanType> convertedTypes =
        new WeakHashMap<Type,MappedMXBeanType>();

    boolean  isBasicType = false;
    OpenType openType = inProgress;
    Class    mappedTypeClass;

    static synchronized MappedMXBeanType newMappedType(Type javaType)
            throws OpenDataException {

        MappedMXBeanType mt = null;
        if (javaType instanceof Class) {
            final Class c = (Class) javaType;
            if (c.isEnum()) {
                mt = new EnumMXBeanType(c);
            } else if (c.isArray()) {
                mt = new ArrayMXBeanType(c);
            } else {
                mt = new CompositeDataMXBeanType(c);
            }
        } else if (javaType instanceof ParameterizedType) {
            final ParameterizedType pt = (ParameterizedType) javaType;
            final Type rawType = pt.getRawType();
            if (rawType instanceof Class) {
                final Class rc = (Class) rawType;
                if (rc == List.class) {
                    mt = new ListMXBeanType(pt);
                } else if (rc == Map.class) {
                    mt = new MapMXBeanType(pt);
                }
            }
        } else if (javaType instanceof GenericArrayType) {
           final GenericArrayType t = (GenericArrayType) javaType;
           mt = new GenericArrayMXBeanType(t);
        }
        // No open type mapped for the javaType
        if (mt == null) {
            throw new OpenDataException(javaType +
                " is not a supported MXBean type.");
        }
        convertedTypes.put(javaType, mt);
        return mt;
    }

    // basic types do not require data mapping
    static synchronized MappedMXBeanType newBasicType(Class c, OpenType ot)
            throws OpenDataException {
        MappedMXBeanType mt = new BasicMXBeanType(c, ot);
        convertedTypes.put(c, mt);
        return mt;
    }

    static synchronized MappedMXBeanType getMappedType(Type t)
            throws OpenDataException {
        MappedMXBeanType mt = convertedTypes.get(t);
        if (mt == null) {
            mt = newMappedType(t);
        }

        if (mt.getOpenType() instanceof InProgress) {
            throw new OpenDataException("Recursive data structure");
        }
        return mt;
    }

    // Convert a class to an OpenType
    public static synchronized OpenType toOpenType(Type t)
            throws OpenDataException {
        MappedMXBeanType mt = getMappedType(t);
        return mt.getOpenType();
    }

    public static Object toJavaTypeData(Object openData, Type t)
            throws OpenDataException, InvalidObjectException {
        if (openData == null) {
            return null;
        }
        MappedMXBeanType mt = getMappedType(t);
        return mt.toJavaTypeData(openData);
    }

    public static Object toOpenTypeData(Object data, Type t)
            throws OpenDataException {
        if (data == null) {
            return null;
        }
        MappedMXBeanType mt = getMappedType(t);
        return mt.toOpenTypeData(data);
    }

    // Return the mapped open type
    OpenType getOpenType() {
        return openType;
    }

    boolean isBasicType() {
        return isBasicType;
    }

    // Return the type name of the mapped open type
    // For primitive types, the type name is the same as the javaType
    // but the mapped open type is the wrapper class
    String getTypeName() {
        return getMappedTypeClass().getName();
    }

    // Return the mapped open type
    Class getMappedTypeClass() {
        return mappedTypeClass;
    }

    abstract Type getJavaType();

    // return name of the class or the generic type
    abstract String getName();

    abstract Object toOpenTypeData(Object javaTypeData)
        throws OpenDataException;

    abstract Object toJavaTypeData(Object openTypeData)
        throws OpenDataException, InvalidObjectException;

    // Basic Types - Classes that do not require data conversion
    //               including primitive types and all SimpleType
    //
    //   Mapped open type: SimpleType for corresponding basic type
    //
    // Data Mapping:
    //   T <-> T (no conversion)
    //
    static class BasicMXBeanType extends MappedMXBeanType {
        final Class basicType;
        BasicMXBeanType(Class c, OpenType openType) {
            this.basicType = c;
            this.openType = openType;
            this.mappedTypeClass = c;
            this.isBasicType = true;
        }

        Type getJavaType() {
            return basicType;
        }

        String getName() {
            return basicType.getName();
        }

        Object toOpenTypeData(Object data) throws OpenDataException {
            return data;
        }

        Object toJavaTypeData(Object data)
            throws OpenDataException, InvalidObjectException {

            return data;
        }
    }


    // Enum subclasses
    //   Mapped open type - String
    //
    // Data Mapping:
    //   Enum <-> enum's name
    //
    static class EnumMXBeanType extends MappedMXBeanType {
        final Class enumClass;
        EnumMXBeanType(Class c) {
            this.enumClass = c;
            this.openType = STRING;
            this.mappedTypeClass = String.class;
        }

        Type getJavaType() {
            return enumClass;
        }

        String getName() {
            return enumClass.getName();
        }

        Object toOpenTypeData(Object data) throws OpenDataException {
            return ((Enum) data).name();
        }

        Object toJavaTypeData(Object data)
            throws OpenDataException, InvalidObjectException {

            try {
                return Enum.valueOf(enumClass, (String) data);
            } catch (IllegalArgumentException e) {
                // missing enum constants
                final InvalidObjectException ioe =
                    new InvalidObjectException("Enum constant named " +
                    (String) data + " is missing");
                ioe.initCause(e);
                throw ioe;
            }
        }
    }

    // Array E[]
    //   Mapped open type - Array with element of OpenType for E
    //
    // Data Mapping:
    //   E[] <-> openTypeData(E)[]
    //
    static class ArrayMXBeanType extends MappedMXBeanType {
        final Class arrayClass;
        protected MappedMXBeanType componentType;
        protected MappedMXBeanType baseElementType;

        ArrayMXBeanType(Class c) throws OpenDataException {
            this.arrayClass = c;
            this.componentType = getMappedType(c.getComponentType());

            StringBuilder className = new StringBuilder();
            Class et = c;
            int dim;
            for (dim = 0; et.isArray(); dim++) {
                className.append('[');
                et = et.getComponentType();
            }
            baseElementType = getMappedType(et);
            if (et.isPrimitive()) {
                className = new StringBuilder(c.getName());
            } else {
                className.append("L" + baseElementType.getTypeName() + ";");
            }
            try {
                mappedTypeClass = Class.forName(className.toString());
            } catch (ClassNotFoundException e) {
                final OpenDataException ode =
                    new OpenDataException("Cannot obtain array class");
                ode.initCause(e);
                throw ode;
            }

            openType = new ArrayType(dim, baseElementType.getOpenType());
        }

        protected ArrayMXBeanType() {
            arrayClass = null;
        };

        Type getJavaType() {
            return arrayClass;
        }

        String getName() {
            return arrayClass.getName();
        }

        Object toOpenTypeData(Object data) throws OpenDataException {
            // If the base element type is a basic type
            // return the data as no conversion is needed.
            // Primitive types are not converted to wrappers.
            if (baseElementType.isBasicType()) {
                return data;
            }

            final Object[] array = (Object[]) data;
            final Object[] openArray = (Object[])
                Array.newInstance(componentType.getMappedTypeClass(),
                                  array.length);
            int i = 0;
            for (Object o : array) {
                if (o == null) {
                    openArray[i] = null;
                } else {
                    openArray[i] = componentType.toOpenTypeData(o);
                }
                i++;
            }
            return openArray;
        }


        Object toJavaTypeData(Object data)
            throws OpenDataException, InvalidObjectException {

            // If the base element type is a basic type
            // return the data as no conversion is needed.
            if (baseElementType.isBasicType()) {
                return data;
            }

            final Object[] openArray = (Object[]) data;
            final Object[] array = (Object[])
                Array.newInstance((Class) componentType.getJavaType(),
                                  openArray.length);
            int i = 0;
            for (Object o : openArray) {
                if (o == null) {
                    array[i] = null;
                } else {
                    array[i] = componentType.toJavaTypeData(o);
                }
                i++;
            }
            return array;
        }

    }

    static class GenericArrayMXBeanType extends ArrayMXBeanType {
        final GenericArrayType gtype;
        GenericArrayMXBeanType(GenericArrayType gat) throws OpenDataException {
            this.gtype = gat;
            this.componentType = getMappedType(gat.getGenericComponentType());

            StringBuilder className = new StringBuilder();
            Type elementType = gat;
            int dim;
            for (dim = 0; elementType instanceof GenericArrayType; dim++) {
                className.append('[');
                GenericArrayType et = (GenericArrayType) elementType;
                elementType = et.getGenericComponentType();
            }
            baseElementType = getMappedType(elementType);
            if (elementType instanceof Class && ((Class) elementType).isPrimitive()) {
                className = new StringBuilder(gat.toString());
            } else {
                className.append("L" + baseElementType.getTypeName() + ";");
            }
            try {
                mappedTypeClass = Class.forName(className.toString());
            } catch (ClassNotFoundException e) {
                final OpenDataException ode =
                    new OpenDataException("Cannot obtain array class");
                ode.initCause(e);
                throw ode;
            }

            openType = new ArrayType(dim, baseElementType.getOpenType());
        }

        Type getJavaType() {
            return gtype;
        }

        String getName() {
            return gtype.toString();
        }
    }

    // List<E>
    //   Mapped open type - Array with element of OpenType for E
    //
    // Data Mapping:
    //   List<E> <-> openTypeData(E)[]
    //
    static class ListMXBeanType extends MappedMXBeanType {
        final ParameterizedType javaType;
        final MappedMXBeanType paramType;
        final String typeName;

        ListMXBeanType(ParameterizedType pt) throws OpenDataException {
            this.javaType = pt;

            final Type[] argTypes = pt.getActualTypeArguments();
            assert(argTypes.length == 1);

            if (!(argTypes[0] instanceof Class)) {
                throw new OpenDataException("Element Type for " + pt +
                   " not supported");
            }
            final Class et = (Class) argTypes[0];
            if (et.isArray()) {
                throw new OpenDataException("Element Type for " + pt +
                   " not supported");
            }
            paramType = getMappedType(et);
            typeName = "List<" + paramType.getName() + ">";

            try {
                mappedTypeClass = Class.forName(
                    "[L" + paramType.getTypeName() + ";");
            } catch (ClassNotFoundException e) {
                final OpenDataException ode =
                    new OpenDataException("Array class not found");
                ode.initCause(e);
                throw ode;
            }
            openType = new ArrayType(1, paramType.getOpenType());
        }

        Type getJavaType() {
            return javaType;
        }

        String getName() {
            return typeName;
        }

        Object toOpenTypeData(Object data) throws OpenDataException {
            final List<Object> list = (List<Object>) data;

            final Object[] openArray = (Object[])
                Array.newInstance(paramType.getMappedTypeClass(),
                                  list.size());
            int i = 0;
            for (Object o : list) {
                openArray[i++] = paramType.toOpenTypeData(o);
            }
            return openArray;
        }

        Object toJavaTypeData(Object data)
            throws OpenDataException, InvalidObjectException {

            final Object[] openArray = (Object[]) data;
            List<Object> result = new ArrayList<Object>(openArray.length);
            for (Object o : openArray) {
                result.add(paramType.toJavaTypeData(o));
            }
            return result;
        }
    }

    private static final String KEY   = "key";
    private static final String VALUE = "value";
    private static final String[] mapIndexNames = {KEY};
    private static final String[] mapItemNames = {KEY, VALUE};

    // Map<K,V>
    //   Mapped open type - TabularType with row type:
    //                        CompositeType:
    //                          "key"   of openDataType(K)
    //                          "value" of openDataType(V)
    //                        "key" is the index name
    //
    // Data Mapping:
    //   Map<K,V> <-> TabularData
    //
    static class MapMXBeanType extends MappedMXBeanType {
        final ParameterizedType javaType;
        final MappedMXBeanType keyType;
        final MappedMXBeanType valueType;
        final String typeName;

        MapMXBeanType(ParameterizedType pt) throws OpenDataException {
            this.javaType = pt;

            final Type[] argTypes = pt.getActualTypeArguments();
            assert(argTypes.length == 2);
            this.keyType = getMappedType(argTypes[0]);
            this.valueType = getMappedType(argTypes[1]);


            // FIXME: generate typeName for generic
            typeName = "Map<" + keyType.getName() + "," +
                                valueType.getName() + ">";
            final OpenType[] mapItemTypes = new OpenType[] {
                                                keyType.getOpenType(),
                                                valueType.getOpenType(),
                                            };
            final CompositeType rowType =
                new CompositeType(typeName,
                                  typeName,
                                  mapItemNames,
                                  mapItemNames,
                                  mapItemTypes);

            openType = new TabularType(typeName, typeName, rowType, mapIndexNames);
            mappedTypeClass = javax.management.openmbean.TabularData.class;
        }

        Type getJavaType() {
            return javaType;
        }

        String getName() {
            return typeName;
        }

        Object toOpenTypeData(Object data) throws OpenDataException {
            final Map<Object,Object> map = (Map<Object,Object>) data;
            final TabularType tabularType = (TabularType) openType;
            final TabularData table = new TabularDataSupport(tabularType);
            final CompositeType rowType = tabularType.getRowType();

            for (Map.Entry entry : map.entrySet()) {
                final Object key = keyType.toOpenTypeData(entry.getKey());
                final Object value = valueType.toOpenTypeData(entry.getValue());
                final CompositeData row =
                    new CompositeDataSupport(rowType,
                                             mapItemNames,
                                             new Object[] {key, value});
                table.put(row);
            }
            return table;
        }

        Object toJavaTypeData(Object data)
            throws OpenDataException, InvalidObjectException {

            final TabularData td = (TabularData) data;

            Map<Object, Object> result = new HashMap<Object, Object>();
            for (CompositeData row : (Collection<CompositeData>) td.values()) {
                Object key = keyType.toJavaTypeData(row.get(KEY));
                Object value = valueType.toJavaTypeData(row.get(VALUE));
                result.put(key, value);
            }
            return result;
        }
    }

    private static final Class<?> COMPOSITE_DATA_CLASS =
        javax.management.openmbean.CompositeData.class;

    // Classes that have a static from method
    //   Mapped open type - CompositeData
    //
    // Data Mapping:
    //   Classes <-> CompositeData
    //
    // The name and type of items for a class are identified from
    // the getter methods. For example, a class defines a method:
    //
    //    public FooType getFoo();
    //
    // The composite data view for this class will contain one
    // item entry for a "foo" attribute and the item type is
    // one of the open types defined in the OpenType class that
    // can be determined in the following manner:
    // o If FooType is a primitive type, the item type a wrapper
    //   class for the corresponding primitive type (such as
    //   Integer, Long, Boolean, etc).
    // o If FooType is of type CompositeData or TabularData,
    //   the item type is FooType.
    // o If FooType is an Enum, the item type is a String and
    //   the value is the name of the enum constant.
    // o If FooType is a class or an interface other than the above,
    //   the item type is CompositeData. The same convention
    //   can be recursively applied to the FooType class when
    //   constructing the composite data for the "foo" attribute.
    // o If FooType is an array, the item type is an array and
    //   its element type is determined as described above.
    //
    static class CompositeDataMXBeanType extends MappedMXBeanType {
        final Class<?> javaClass;
        final boolean isCompositeData;
        Method fromMethod = null;

        CompositeDataMXBeanType(Class c) throws OpenDataException {
            this.javaClass = c;
            this.mappedTypeClass = COMPOSITE_DATA_CLASS;

            // check if a static from method exists
            try {
                fromMethod = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
                        public Method run() throws NoSuchMethodException {
                            return javaClass.getMethod("from", COMPOSITE_DATA_CLASS);
                        }
                    });
            } catch (PrivilegedActionException e) {
                // ignore NoSuchMethodException since we allow classes
                // that has no from method to be embeded in another class.
            }

            if (COMPOSITE_DATA_CLASS.isAssignableFrom(c)) {
                // c implements CompositeData - set openType to null
                // defer generating the CompositeType
                // until the object is constructed
                this.isCompositeData = true;
                this.openType = null;
            } else {
                this.isCompositeData = false;

                // Make a CompositeData containing all the getters
                final Method[] methods =
                    AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
                        public Method[] run() {
                            return javaClass.getMethods();
                        }
                    });
                final List<String> names = new ArrayList<String>();
                final List<OpenType> types = new ArrayList<OpenType>();

                /* Select public methods that look like "T getX()" or "boolean
                   isX()", where T is not void and X is not the empty
                   string.  Exclude "Class getClass()" inherited from Object.  */
                for (int i = 0; i < methods.length; i++) {
                    final Method method = methods[i];
                    final String name = method.getName();
                    final Type type = method.getGenericReturnType();
                    final String rest;
                    if (name.startsWith("get")) {
                        rest = name.substring(3);
                    } else if (name.startsWith("is") &&
                               type instanceof Class &&
                               ((Class) type) == boolean.class) {
                        rest = name.substring(2);
                    } else {
                        // ignore non-getter methods
                        continue;
                    }

                    if (rest.equals("") ||
                        method.getParameterTypes().length > 0 ||
                        type == void.class ||
                        rest.equals("Class")) {

                        // ignore non-getter methods
                        continue;
                    }
                    names.add(decapitalize(rest));
                    types.add(toOpenType(type));
                }

                final String[] nameArray = names.toArray(new String[0]);
                openType = new CompositeType(c.getName(),
                                             c.getName(),
                                             nameArray, // field names
                                             nameArray, // field descriptions
                                             types.toArray(new OpenType[0]));
            }
        }

        Type getJavaType() {
            return javaClass;
        }

        String getName() {
            return javaClass.getName();
        }

        Object toOpenTypeData(Object data) throws OpenDataException {
            if (data instanceof MemoryUsage) {
                return MemoryUsageCompositeData.toCompositeData((MemoryUsage) data);
            }

            if (data instanceof ThreadInfo) {
                return ThreadInfoCompositeData.toCompositeData((ThreadInfo) data);
            }

            if (data instanceof LockInfo) {
                if (data instanceof java.lang.management.MonitorInfo) {
                    return MonitorInfoCompositeData.toCompositeData((MonitorInfo) data);
                }
                return LockDataConverter.toLockInfoCompositeData((LockInfo) data);
            }

            if (data instanceof MemoryNotificationInfo) {
                return MemoryNotifInfoCompositeData.
                    toCompositeData((MemoryNotificationInfo) data);
            }

            if (data instanceof VMOption) {
                return VMOptionCompositeData.toCompositeData((VMOption) data);
            }

            if (isCompositeData) {
                // Classes that implement CompositeData
                //
                // construct a new CompositeDataSupport object
                // so that no other classes are sent over the wire
                CompositeData cd = (CompositeData) data;
                CompositeType ct = cd.getCompositeType();
                String[] itemNames = (String[]) ct.keySet().toArray(new String[0]);
                Object[] itemValues = cd.getAll(itemNames);
                return new CompositeDataSupport(ct, itemNames, itemValues);
            }

            throw new OpenDataException(javaClass.getName() +
                " is not supported for platform MXBeans");
        }

        Object toJavaTypeData(Object data)
            throws OpenDataException, InvalidObjectException {

            if (fromMethod == null) {
                throw new AssertionError("Does not support data conversion");
            }

            try {
                return fromMethod.invoke(null, data);
            } catch (IllegalAccessException e) {
                // should never reach here
                throw new AssertionError(e);
            } catch (InvocationTargetException e) {
                final OpenDataException ode =
                    new OpenDataException("Failed to invoke " +
                        fromMethod.getName() + " to convert CompositeData " +
                        " to " + javaClass.getName());
                ode.initCause(e);
                throw ode;
            }
        }
    }

    private static class InProgress extends OpenType {
        private static final String description =
                  "Marker to detect recursive type use -- internal use only!";

        InProgress() throws OpenDataException {
            super("java.lang.String", "java.lang.String", description);
        }

        public String toString() {
            return description;
        }

        public int hashCode() {
            return 0;
        }

        public boolean equals(Object o) {
            return false;
        }

        public boolean isValue(Object o) {
            return false;
        }
        private static final long serialVersionUID = -3413063475064374490L;
    }
    private static final OpenType inProgress;
    static {
        OpenType t;
        try {
            t = new InProgress();
        } catch (OpenDataException e) {
            // Should not reach here
            throw new AssertionError(e);
        }
        inProgress = t;
    }

    private static final OpenType[] simpleTypes = {
        BIGDECIMAL, BIGINTEGER, BOOLEAN, BYTE, CHARACTER, DATE,
        DOUBLE, FLOAT, INTEGER, LONG, OBJECTNAME, SHORT, STRING,
        VOID,
    };
    static {
        try {
            for (int i = 0; i < simpleTypes.length; i++) {
                final OpenType t = simpleTypes[i];
                Class c;
                try {
                    c = Class.forName(t.getClassName(), false,
                                      String.class.getClassLoader());
                    MappedMXBeanType.newBasicType(c, t);
                } catch (ClassNotFoundException e) {
                    // the classes that these predefined types declare
                    // must exist!
                    throw new AssertionError(e);
                } catch (OpenDataException e) {
                    throw new AssertionError(e);
                }

                if (c.getName().startsWith("java.lang.")) {
                    try {
                        final Field typeField = c.getField("TYPE");
                        final Class primitiveType = (Class) typeField.get(null);
                        MappedMXBeanType.newBasicType(primitiveType, t);
                    } catch (NoSuchFieldException e) {
                        // OK: must not be a primitive wrapper
                    } catch (IllegalAccessException e) {
                        // Should not reach here
                       throw new AssertionError(e);
                    }
                }
            }
        } catch (OpenDataException e) {
            throw new AssertionError(e);
        }
    }

    /**
     * Utility method to take a string and convert it to normal Java variable
     * name capitalization.  This normally means converting the first
     * character from upper case to lower case, but in the (unusual) special
     * case when there is more than one character and both the first and
     * second characters are upper case, we leave it alone.
     * <p>
     * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
     * as "URL".
     *
     * @param  name The string to be decapitalized.
     * @return  The decapitalized version of the string.
     */
    private static String decapitalize(String name) {
        if (name == null || name.length() == 0) {
            return name;
        }
        if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
                        Character.isUpperCase(name.charAt(0))){
            return name;
        }
        char chars[] = name.toCharArray();
        chars[0] = Character.toLowerCase(chars[0]);
        return new String(chars);
    }

}
