/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package java.lang.reflect;

import java.lang.annotation.Annotation;
import java.util.Hashtable;
import org.apache.harmony.kernel.vm.ReflectionAccess;
import org.apache.harmony.kernel.vm.StringUtils;

/**
 * {@code AccessibleObject} is the superclass of all member reflection classes
 * (Field, Constructor, Method). AccessibleObject provides the ability to toggle
 * a flag controlling access checks for these objects. By default, accessing a
 * member (for example, setting a field or invoking a method) checks the
 * validity of the access (for example, invoking a private method from outside
 * the defining class is prohibited) and throws IllegalAccessException if the
 * operation is not permitted. If the accessible flag is set to true, these
 * checks are omitted. This allows privileged code, such as Java object
 * serialization, object inspectors, and debuggers to have complete access to
 * objects.
 *
 * @see Field
 * @see Constructor
 * @see Method
 * @see ReflectPermission
 */
public class AccessibleObject implements AnnotatedElement {

    // If true, object is accessible, bypassing normal security checks
    boolean flag = false;

    /**
     * one dimensional array
     */
    private static final String DIMENSION_1 = "[]";

    /**
     * two dimensional array
     */
    private static final String DIMENSION_2 = "[][]";

    /**
     * three dimensional array
     */
    private static final String DIMENSION_3 = "[][][]";

    // Holds a mapping from Java type names to native type codes.
    static Hashtable<String, String> trans;

    static {
        trans = new Hashtable<String, String>(9);
        trans.put("byte", "B");
        trans.put("char", "C");
        trans.put("short", "S");
        trans.put("int", "I");
        trans.put("long", "J");
        trans.put("float", "F");
        trans.put("double", "D");
        trans.put("void", "V");
        trans.put("boolean", "Z");
    }

    /**
     * Attempts to set the value of the accessible flag for all the objects in
     * the array provided. Only one security check is performed. Setting this
     * flag to {@code false} will enable access checks, setting to {@code true}
     * will disable them. If there is a security manager, checkPermission is
     * called with a {@code ReflectPermission("suppressAccessChecks")}.
     *
     * @param objects
     *            the accessible objects
     * @param flag
     *            the new value for the accessible flag
     *
     * @throws SecurityException
     *             if the request is denied
     *
     * @see #setAccessible(boolean)
     * @see ReflectPermission
     */
    public static void setAccessible(AccessibleObject[] objects, boolean flag)
            throws SecurityException {
        SecurityManager smgr = System.getSecurityManager();
        if (smgr != null) {
            smgr.checkPermission(new ReflectPermission("suppressAccessChecks"));
        }

        synchronized(AccessibleObject.class) {
            for (int i = 0; i < objects.length; i++) {
                objects[i].flag = flag;
            }
        }
    }

    /**
     * Constructs a new {@code AccessibleObject} instance. {@code
     * AccessibleObject} instances can only be constructed by the virtual
     * machine.
     */
    protected AccessibleObject() {
        super();
    }

    /**
     * Indicates whether this object is accessible without security checks being
     * performed. Returns the accessible flag.
     *
     * @return {@code true} if this object is accessible without security
     *         checks, {@code false} otherwise
     */
    public boolean isAccessible() {
        return flag;
    }

    /**
     * Attempts to set the value of the accessible flag. Setting this flag to
     * {@code false} will enable access checks, setting to {@code true} will
     * disable them. If there is a security manager, checkPermission is called
     * with a {@code ReflectPermission("suppressAccessChecks")}.
     *
     * @param flag
     *            the new value for the accessible flag
     *
     * @throws SecurityException
     *             if the request is denied
     *
     * @see ReflectPermission
     */
    public void setAccessible(boolean flag) throws SecurityException {
        SecurityManager smgr = System.getSecurityManager();
        if (smgr != null) {
            smgr.checkPermission(new ReflectPermission("suppressAccessChecks"));
        }

        this.flag = flag;
    }

    /**
     * Sets the accessible flag on this instance without doing any checks.
     *
     * @param flag
     *            the new value for the accessible flag
     */
    /*package*/ void setAccessibleNoCheck(boolean flag) {
        this.flag = flag;
    }

    public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
        return getAnnotation(annotationType) != null;
    }

    public Annotation[] getDeclaredAnnotations() {
        throw new RuntimeException("subclass must override this method");
    }

    public Annotation[] getAnnotations() {
        // for all but Class, getAnnotations == getDeclaredAnnotations
        return getDeclaredAnnotations();
    }

    /* slow, but works for all sub-classes */
    public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
        if (annotationType == null) {
            throw new NullPointerException();
        }
        Annotation[] annos = getAnnotations();
        for (int i = annos.length-1; i >= 0; --i) {
            if (annos[i].annotationType() == annotationType) {
                return (T) annos[i];
            }
        }
        return null;
    }

    /**
     * Returns the signature for a class. This is the kind of signature used
     * internally by the JVM, with one-character codes representing the basic
     * types. It is not suitable for printing.
     *
     * @param clazz
     *            the class for which a signature is required
     *
     * @return The signature as a string
     */
    String getSignature(Class<?> clazz) {
        String result = "";
        String nextType = clazz.getName();

        if(trans.containsKey(nextType)) {
            result = trans.get(nextType);
        } else {
            if(clazz.isArray()) {
                result = "[" + getSignature(clazz.getComponentType());
            } else {
                result = "L" + nextType + ";";
            }
        }
        return result;
    }

    /**
     * Returns a printable String consisting of the canonical names of the
     * classes contained in an array. The form is that used in parameter and
     * exception lists, that is, the class or type names are separated by
     * commas.
     *
     * @param types
     *            the array of classes
     *
     * @return The String of names
     */
    String toString(Class<?>[] types) {
        StringBuilder result = new StringBuilder();

        if (types.length != 0) {
            result.append(types[0].getCanonicalName());
            for (int i = 1; i < types.length; i++) {
                result.append(',');
                result.append(types[i].getCanonicalName());
            }
        }

        return result.toString();
    }

    /**
     * Gets the Signature attribute for this instance. Returns {@code null}
     * if not found.
     */
    /*package*/ String getSignatureAttribute() {
        /*
         * Note: This method would have been declared abstract, but the
         * standard API lists this class as concrete.
         */
        throw new UnsupportedOperationException();
    }

    /**
     * Retrieve the signature attribute from an arbitrary class.  This is
     * the same as Class.getSignatureAttribute(), but it can be used from
     * the java.lang.reflect package.
     */
    /*package*/ static String getClassSignatureAttribute(Class clazz) {
        Object[] annotation = getClassSignatureAnnotation(clazz);

        if (annotation == null) {
            return null;
        }

        return StringUtils.combineStrings(annotation);
    }

    /**
     * Retrieve the signature annotation from an arbitrary class.  This is
     * the same as Class.getSignatureAttribute(), but it can be used from
     * the java.lang.reflect package.
     */
    private static native Object[] getClassSignatureAnnotation(Class clazz);

    /**
     * Gets the unique instance of {@link ReflectionAccessImpl}.
     *
     * @return non-null; the unique instance
     */
    static /*package*/ ReflectionAccess getReflectionAccess() {
        return ReflectionAccessImpl.THE_ONE;
    }


    /**
     * Appends the specified class name to the buffer. The class may represent
     * a simple type, a reference type or an array type.
     *
     * @param sb buffer
     * @param obj the class which name should be appended to the buffer
     *
     * @throws NullPointerException if any of the arguments is null
     */
    void appendArrayType(StringBuilder sb, Class<?> obj) {
        if (!obj.isArray()) {
            sb.append(obj.getName());
            return;
        }
        int dimensions = 1;
        Class simplified = obj.getComponentType();
        obj = simplified;
        while (simplified.isArray()) {
            obj = simplified;
            dimensions++;
        }
        sb.append(obj.getName());
        switch (dimensions) {
        case 1:
            sb.append(DIMENSION_1);
            break;
        case 2:
            sb.append(DIMENSION_2);
            break;
        case 3:
            sb.append(DIMENSION_3);
            break;
        default:
            for (; dimensions > 0; dimensions--) {
                sb.append(DIMENSION_1);
            }
        }
    }

    /**
     * Appends names of the specified array classes to the buffer. The array
     * elements may represent a simple type, a reference type or an array type.
     * Output format: java.lang.Object[], java.io.File, void
     *
     * @param sb buffer
     * @param objs array of classes to print the names
     *
     * @throws NullPointerException if any of the arguments is null
     */
    void appendArrayType(StringBuilder sb, Class[] objs) {
        if (objs.length > 0) {
            appendArrayType(sb, objs[0]);
            for (int i = 1; i < objs.length; i++) {
                sb.append(',');
                appendArrayType(sb, objs[i]);
            }
        }
    }

    /**
     * Appends names of the specified array classes to the buffer. The array
     * elements may represent a simple type, a reference type or an array type.
     * Output format: java.lang.Object[], java.io.File, void
     *
     * @param sb buffer
     * @param objs array of classes to print the names
     *
     * @throws NullPointerException if any of the arguments is null
     */
    void appendArrayGenericType(StringBuilder sb, Type[] objs) {
        if (objs.length > 0) {
            appendGenericType(sb, objs[0]);
            for (int i = 1; i < objs.length; i++) {
                sb.append(',');
                appendGenericType(sb, objs[i]);
            }
        }
    }

    /**
     * Appends the generic type representation to the buffer.
     *
     * @param sb buffer
     * @param obj the generic type which representation should be appended to the buffer
     *
     * @throws NullPointerException if any of the arguments is null
     */
    void appendGenericType(StringBuilder sb, Type obj) {
        if (obj instanceof TypeVariable) {
            sb.append(((TypeVariable)obj).getName());
        } else if (obj instanceof ParameterizedType) {
            sb.append(obj.toString());
        } else if (obj instanceof GenericArrayType) { //XXX: is it a working branch?
            Type simplified = ((GenericArrayType)obj).getGenericComponentType();
            appendGenericType(sb, simplified);
            sb.append("[]");
        } else if (obj instanceof Class) {
            Class c = ((Class<?>)obj);
            if (c.isArray()){
                String as[] = c.getName().split("\\[");
                int len = as.length-1;
                if (as[len].length() > 1){
                    sb.append(as[len].substring(1, as[len].length()-1));
                } else {
                    char ch = as[len].charAt(0);
                    if (ch == 'I')
                        sb.append("int");
                    else if (ch == 'B')
                        sb.append("byte");
                    else if (ch == 'J')
                        sb.append("long");
                    else if (ch == 'F')
                        sb.append("float");
                    else if (ch == 'D')
                        sb.append("double");
                    else if (ch == 'S')
                        sb.append("short");
                    else if (ch == 'C')
                        sb.append("char");
                    else if (ch == 'Z')
                        sb.append("boolean");
                    else if (ch == 'V') //XXX: is it a working branch?
                        sb.append("void");
                }
                for (int i = 0; i < len; i++){
                    sb.append("[]");
                }
            } else {
                sb.append(c.getName());
            }
        }
    }

    /**
     * Appends names of the specified array classes to the buffer. The array
     * elements may represent a simple type, a reference type or an array type.
     * In case if the specified array element represents an array type its
     * internal will be appended to the buffer.
     * Output format: [Ljava.lang.Object;, java.io.File, void
     *
     * @param sb buffer
     * @param objs array of classes to print the names
     *
     * @throws NullPointerException if any of the arguments is null
     */
    void appendSimpleType(StringBuilder sb, Class<?>[] objs) {
        if (objs.length > 0) {
            sb.append(objs[0].getName());
            for (int i = 1; i < objs.length; i++) {
                sb.append(',');
                sb.append(objs[i].getName());
            }
        }
    }
}
