/*
 * Copyright (c) 1994, 2019, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package sun.tools.java;

import java.io.IOException;
import java.io.DataInputStream;
import java.io.OutputStream;
import java.io.DataOutputStream;
import java.io.ByteArrayInputStream;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;

/**
 * WARNING: The contents of this source file are not part of any
 * supported API.  Code that depends on them does so at its own risk:
 * they are subject to change or removal without notice.
 */
@SuppressWarnings("deprecation")
public final
class BinaryClass extends ClassDefinition implements Constants {
    BinaryConstantPool cpool;
    BinaryAttribute atts;
    Vector<ClassDeclaration> dependencies;
    private boolean haveLoadedNested = false;

    /**
     * Constructor
     */
    public BinaryClass(Object source, ClassDeclaration declaration, int modifiers,
                           ClassDeclaration superClass, ClassDeclaration interfaces[],
                           Vector<ClassDeclaration> dependencies) {
        super(source, 0, declaration, modifiers, null, null);
        this.dependencies = dependencies;
        this.superClass = superClass;
        this.interfaces = interfaces;
    }

    /**
     * Flags used by basicCheck() to avoid duplicate calls.
     * (Part of fix for 4105911)
     */
    private boolean basicCheckDone = false;
    private boolean basicChecking = false;

    /**
     * Ready a BinaryClass for further checking.  Note that, until recently,
     * BinaryClass relied on the default basicCheck() provided by
     * ClassDefinition.  The definition here has been added to ensure that
     * the information generated by collectInheritedMethods is available
     * for BinaryClasses.
     */
    protected void basicCheck(Environment env) throws ClassNotFound {
        if (tracing) env.dtEnter("BinaryClass.basicCheck: " + getName());

        // We need to guard against duplicate calls to basicCheck().  They
        // can lead to calling collectInheritedMethods() for this class
        // from within a previous call to collectInheritedMethods() for
        // this class.  That is not allowed.
        // (Part of fix for 4105911)
        if (basicChecking || basicCheckDone) {
            if (tracing) env.dtExit("BinaryClass.basicCheck: OK " + getName());
            return;
        }

        if (tracing) env.dtEvent("BinaryClass.basicCheck: CHECKING " + getName());
        basicChecking = true;

        super.basicCheck(env);

        // Collect inheritance information.
        if (doInheritanceChecks) {
            collectInheritedMethods(env);
        }

        basicCheckDone = true;
        basicChecking = false;
        if (tracing) env.dtExit("BinaryClass.basicCheck: " + getName());
    }

    /**
     * Load a binary class
     */
    public static BinaryClass load(Environment env, DataInputStream in) throws IOException {
        return load(env, in, ~(ATT_CODE|ATT_ALLCLASSES));
    }

    public static BinaryClass load(Environment env,
                                   DataInputStream in, int mask) throws IOException {
        // Read the header
        int magic = in.readInt();                    // JVM 4.1 ClassFile.magic
        if (magic != JAVA_MAGIC) {
            throw new ClassFormatError("wrong magic: " + magic + ", expected " + JAVA_MAGIC);
        }
        int minor_version = in.readUnsignedShort();  // JVM 4.1 ClassFile.minor_version
        int version = in.readUnsignedShort();        // JVM 4.1 ClassFile.major_version
        if (version < JAVA_MIN_SUPPORTED_VERSION) {
            throw new ClassFormatError(
                           sun.tools.javac.Main.getText(
                               "javac.err.version.too.old",
                               String.valueOf(version)));
        } else if ((version >= JAVA_MIN_PREVIEW_MAJOR_VERSION)
                     && (minor_version == JAVA_PREVIEW_MINOR_VERSION)) {
            // reject all class files that have preview features enabled
            throw new ClassFormatError(
                           sun.tools.javac.Main.getText(
                               "javac.err.version.preview",
                               version+"."+minor_version));
        } else if ((version > JAVA_MAX_SUPPORTED_VERSION)
                     || (version == JAVA_MAX_SUPPORTED_VERSION
                  && minor_version > JAVA_MAX_SUPPORTED_MINOR_VERSION)) {
            throw new ClassFormatError(
                           sun.tools.javac.Main.getText(
                               "javac.err.version.too.recent",
                               version+"."+minor_version));
        }

        // Read the constant pool
        BinaryConstantPool cpool = new BinaryConstantPool(in);

        // The dependencies of this class
        Vector<ClassDeclaration> dependencies = cpool.getDependencies(env);

        // Read modifiers
        int classMod = in.readUnsignedShort() & ACCM_CLASS;  // JVM 4.1 ClassFile.access_flags

        // Read the class name - from JVM 4.1 ClassFile.this_class
        ClassDeclaration classDecl = cpool.getDeclaration(env, in.readUnsignedShort());

        // Read the super class name (may be null) - from JVM 4.1 ClassFile.super_class
        ClassDeclaration superClassDecl = cpool.getDeclaration(env, in.readUnsignedShort());

        // Read the interface names - from JVM 4.1 ClassFile.interfaces_count
        ClassDeclaration interfaces[] = new ClassDeclaration[in.readUnsignedShort()];
        for (int i = 0 ; i < interfaces.length ; i++) {
            // JVM 4.1 ClassFile.interfaces[]
            interfaces[i] = cpool.getDeclaration(env, in.readUnsignedShort());
        }

        // Allocate the class
        BinaryClass c = new BinaryClass(null, classDecl, classMod, superClassDecl,
                                        interfaces, dependencies);
        c.cpool = cpool;

        // Add any additional dependencies
        c.addDependency(superClassDecl);

        // Read the fields
        int nfields = in.readUnsignedShort();  // JVM 4.1 ClassFile.fields_count
        for (int i = 0 ; i < nfields ; i++) {
            // JVM 4.5 field_info.access_flags
            int fieldMod = in.readUnsignedShort() & ACCM_FIELD;
            // JVM 4.5 field_info.name_index
            Identifier fieldName = cpool.getIdentifier(in.readUnsignedShort());
            // JVM 4.5 field_info.descriptor_index
            Type fieldType = cpool.getType(in.readUnsignedShort());
            BinaryAttribute atts = BinaryAttribute.load(in, cpool, mask);
            c.addMember(new BinaryMember(c, fieldMod, fieldType, fieldName, atts));
        }

        // Read the methods
        int nmethods = in.readUnsignedShort();  // JVM 4.1 ClassFile.methods_count
        for (int i = 0 ; i < nmethods ; i++) {
            // JVM 4.6 method_info.access_flags
            int methMod = in.readUnsignedShort() & ACCM_METHOD;
            // JVM 4.6 method_info.name_index
            Identifier methName = cpool.getIdentifier(in.readUnsignedShort());
            // JVM 4.6 method_info.descriptor_index
            Type methType = cpool.getType(in.readUnsignedShort());
            BinaryAttribute atts = BinaryAttribute.load(in, cpool, mask);
            c.addMember(new BinaryMember(c, methMod, methType, methName, atts));
        }

        // Read the class attributes
        c.atts = BinaryAttribute.load(in, cpool, mask);

        // See if the SourceFile is known
        byte data[] = c.getAttribute(idSourceFile);
        if (data != null) {
            DataInputStream dataStream = new DataInputStream(new ByteArrayInputStream(data));
            // JVM 4.7.2 SourceFile_attribute.sourcefile_index
            c.source = cpool.getString(dataStream.readUnsignedShort());
        }

        // See if the Documentation is know
        data = c.getAttribute(idDocumentation);
        if (data != null) {
            c.documentation = new DataInputStream(new ByteArrayInputStream(data)).readUTF();
        }

        // Was it compiled as deprecated?
        if (c.getAttribute(idDeprecated) != null) {
            c.modifiers |= M_DEPRECATED;
        }

        // Was it synthesized by the compiler?
        if (c.getAttribute(idSynthetic) != null) {
            c.modifiers |= M_SYNTHETIC;
        }

        return c;
    }

    /**
     * Called when an environment ties a binary definition to a declaration.
     * At this point, auxiliary definitions may be loaded.
     */

    public void loadNested(Environment env) {
        loadNested(env, 0);
    }

    public void loadNested(Environment env, int flags) {
        // Sanity check.
        if (haveLoadedNested) {
            // Duplicate calls most likely should not occur, but they do
            // in javap.  Be tolerant of them for the time being.
            // throw new CompilerError("multiple loadNested");
            if (tracing) env.dtEvent("loadNested: DUPLICATE CALL SKIPPED");
            return;
        }
        haveLoadedNested = true;
        // Read class-nesting information.
        try {
            byte data[];
            data = getAttribute(idInnerClasses);
            if (data != null) {
                initInnerClasses(env, data, flags);
            }
        } catch (IOException ee) {
            // The inner classes attribute is not well-formed.
            // It may, for example, contain no data.  Report this.
            // We used to throw a CompilerError here (bug 4095108).
            env.error(0, "malformed.attribute", getClassDeclaration(),
                      idInnerClasses);
            if (tracing)
                env.dtEvent("loadNested: MALFORMED ATTRIBUTE (InnerClasses)");
        }
    }

    private void initInnerClasses(Environment env,
                                  byte data[],
                                  int flags) throws IOException {
        DataInputStream ds = new DataInputStream(new ByteArrayInputStream(data));
        int nrec = ds.readUnsignedShort();  // InnerClasses_attribute.number_of_classes
        for (int i = 0; i < nrec; i++) {
            // For each inner class name transformation, we have a record
            // with the following fields:
            //
            //    u2 inner_class_info_index;   // CONSTANT_Class_info index
            //    u2 outer_class_info_index;   // CONSTANT_Class_info index
            //    u2 inner_name_index;         // CONSTANT_Utf8_info index
            //    u2 inner_class_access_flags; // access_flags bitmask
            //
            // The spec states that outer_class_info_index is 0 iff
            // the inner class is not a member of its enclosing class (i.e.
            // it is a local or anonymous class).  The spec also states
            // that if a class is anonymous then inner_name_index should
            // be 0.
            //
            // Prior to jdk1.2, javac did not implement the spec.  Instead
            // it <em>always</em> set outer_class_info_index to the
            // enclosing outer class and if the class was anonymous,
            // it set inner_name_index to be the index of a CONSTANT_Utf8
            // entry containing the null string "" (idNull).  This code is
            // designed to handle either kind of class file.
            //
            // See also the compileClass() method in SourceClass.java.

            // Read in the inner_class_info
            // InnerClasses_attribute.classes.inner_class_info_index
            int inner_index = ds.readUnsignedShort();
            // could check for zero.
            ClassDeclaration inner = cpool.getDeclaration(env, inner_index);

            // Read in the outer_class_info.  Note that the index will be
            // zero if the class is "not a member".
            ClassDeclaration outer = null;
            // InnerClasses_attribute.classes.outer_class_info_index
            int outer_index = ds.readUnsignedShort();
            if (outer_index != 0) {
                outer = cpool.getDeclaration(env, outer_index);
            }

            // Read in the inner_name_index.  This may be zero.  An anonymous
            // class will either have an inner_nm_index of zero (as the spec
            // dictates) or it will have an inner_nm of idNull (for classes
            // generated by pre-1.2 compilers).  Handle both.
            Identifier inner_nm = idNull;
            // InnerClasses_attribute.classes.inner_name_index
            int inner_nm_index = ds.readUnsignedShort();
            if (inner_nm_index != 0) {
                inner_nm = Identifier.lookup(cpool.getString(inner_nm_index));
            }

            // Read in the modifiers for the inner class.
            // InnerClasses_attribute.classes.inner_name_index
            int mods = ds.readUnsignedShort();

            // Is the class accessible?
            // The old code checked for
            //
            //    (!inner_nm.equals(idNull) && (mods & M_PRIVATE) == 0)
            //
            // which we will preserve to keep it working for class files
            // generated by 1.1 compilers.  In addition we check for
            //
            //    (outer != null)
            //
            // as an additional check that only makes sense with 1.2
            // generated files.  Note that it is entirely possible that
            // the M_PRIVATE bit is always enough.  We are being
            // conservative here.
            //
            // The ATT_ALLCLASSES flag causes the M_PRIVATE modifier
            // to be ignored, and is used by tools such as 'javap' that
            // wish to examine all classes regardless of the normal access
            // controls that apply during compilation.  Note that anonymous
            // and local classes are still not considered accessible, though
            // named local classes in jdk1.1 may slip through.  Note that
            // this accessibility test is an optimization, and it is safe to
            // err on the side of greater accessibility.
            boolean accessible =
                (outer != null) &&
                (!inner_nm.equals(idNull)) &&
                ((mods & M_PRIVATE) == 0 ||
                 (flags & ATT_ALLCLASSES) != 0);

            // The reader should note that there has been a significant change
            // in the way that the InnerClasses attribute is being handled.
            // In particular, previously the compiler called initInner() for
            // <em>every</em> inner class.  Now the compiler does not call
            // initInner() if the inner class is inaccessible.  This means
            // that inaccessible inner classes don't have any of the processing
            // from initInner() done for them: fixing the access flags,
            // setting outerClass, setting outerMember in their outerClass,
            // etc.  We believe this is fine: if the class is inaccessible
            // and binary, then everyone who needs to see its internals
            // has already been compiled.  Hopefully.

            if (accessible) {
                Identifier nm =
                    Identifier.lookupInner(outer.getName(), inner_nm);

                // Tell the type module about the nesting relation:
                Type.tClass(nm);

                if (inner.equals(getClassDeclaration())) {
                    // The inner class in the record is this class.
                    try {
                        ClassDefinition outerClass = outer.getClassDefinition(env);
                        initInner(outerClass, mods);
                    } catch (ClassNotFound e) {
                        // report the error elsewhere
                    }
                } else if (outer.equals(getClassDeclaration())) {
                    // The outer class in the record is this class.
                    try {
                        ClassDefinition innerClass =
                            inner.getClassDefinition(env);
                        initOuter(innerClass, mods);
                    } catch (ClassNotFound e) {
                        // report the error elsewhere
                    }
                }
            }
        }
    }

    private void initInner(ClassDefinition outerClass, int mods) {
        if (getOuterClass() != null)
            return;             // already done
        /******
        // Maybe set static, protected, or private.
        if ((modifiers & M_PUBLIC) != 0)
            mods &= M_STATIC;
        else
            mods &= M_PRIVATE | M_PROTECTED | M_STATIC;
        modifiers |= mods;
        ******/
        // For an inner class, the class access may have been weakened
        // from that originally declared the source.  We must take the
        // actual access permissions against which we check any source
        // we are currently compiling from the InnerClasses attribute.
        // We attempt to guard here against bogus combinations of modifiers.
        if ((mods & M_PRIVATE) != 0) {
            // Private cannot be combined with public or protected.
            mods &= ~(M_PUBLIC | M_PROTECTED);
        } else if ((mods & M_PROTECTED) != 0) {
            // Protected cannot be combined with public.
            mods &= ~M_PUBLIC;
        }
        if ((mods & M_INTERFACE) != 0) {
            // All interfaces are implicitly abstract.
            // All interfaces that are members of a type are implicitly static.
            mods |= (M_ABSTRACT | M_STATIC);
        }
        if (outerClass.isInterface()) {
            // All types that are members of interfaces are implicitly
            // public and static.
            mods |= (M_PUBLIC | M_STATIC);
            mods &= ~(M_PRIVATE | M_PROTECTED);
        }
        modifiers = mods;

        setOuterClass(outerClass);

        for (MemberDefinition field = getFirstMember();
             field != null;
             field = field.getNextMember()) {
            if (field.isUplevelValue()
                    && outerClass.getType().equals(field.getType())
                    && field.getName().toString().startsWith(prefixThis)) {
                setOuterMember(field);
            }
        }
    }

    private void initOuter(ClassDefinition innerClass, int mods) {
        if (innerClass instanceof BinaryClass)
            ((BinaryClass)innerClass).initInner(this, mods);
        addMember(new BinaryMember(innerClass));
    }

    /**
     * Write the class out to a given stream.  This function mirrors the loader.
     */
    public void write(Environment env, OutputStream out) throws IOException {
        DataOutputStream data = new DataOutputStream(out);

        // write out the header
        data.writeInt(JAVA_MAGIC);
        data.writeShort(env.getMinorVersion());
        data.writeShort(env.getMajorVersion());

        // Write out the constant pool
        cpool.write(data, env);

        // Write class information
        data.writeShort(getModifiers() & ACCM_CLASS);
        data.writeShort(cpool.indexObject(getClassDeclaration(), env));
        data.writeShort((getSuperClass() != null)
                        ? cpool.indexObject(getSuperClass(), env) : 0);
        data.writeShort(interfaces.length);
        for (int i = 0 ; i < interfaces.length ; i++) {
            data.writeShort(cpool.indexObject(interfaces[i], env));
        }

        // count the fields and the methods
        int fieldCount = 0, methodCount = 0;
        for (MemberDefinition f = firstMember; f != null; f = f.getNextMember())
            if (f.isMethod()) methodCount++; else fieldCount++;

        // write out each the field count, and then each field
        data.writeShort(fieldCount);
        for (MemberDefinition f = firstMember; f != null; f = f.getNextMember()) {
            if (!f.isMethod()) {
                data.writeShort(f.getModifiers() & ACCM_FIELD);
                String name = f.getName().toString();
                String signature = f.getType().getTypeSignature();
                data.writeShort(cpool.indexString(name, env));
                data.writeShort(cpool.indexString(signature, env));
                BinaryAttribute.write(((BinaryMember)f).atts, data, cpool, env);
            }
        }

        // write out each method count, and then each method
        data.writeShort(methodCount);
        for (MemberDefinition f = firstMember; f != null; f = f.getNextMember()) {
            if (f.isMethod()) {
                data.writeShort(f.getModifiers() & ACCM_METHOD);
                String name = f.getName().toString();
                String signature = f.getType().getTypeSignature();
                data.writeShort(cpool.indexString(name, env));
                data.writeShort(cpool.indexString(signature, env));
                BinaryAttribute.write(((BinaryMember)f).atts, data, cpool, env);
            }
        }

        // write out the class attributes
        BinaryAttribute.write(atts, data, cpool, env);
        data.flush();
    }

    /**
     * Get the dependencies
     */
    public Enumeration<ClassDeclaration> getDependencies() {
        return dependencies.elements();
    }

    /**
     * Add a dependency
     */
    public void addDependency(ClassDeclaration c) {
        if ((c != null) && !dependencies.contains(c)) {
            dependencies.addElement(c);
        }
    }

    /**
     * Get the constant pool
     */
    public BinaryConstantPool getConstants() {
        return cpool;
    }

    /**
     * Get a class attribute
     */
    public byte[] getAttribute(Identifier name) {
        for (BinaryAttribute att = atts ; att != null ; att = att.next) {
            if (att.name.equals(name)) {
                return att.data;
            }
        }
        return null;
    }
}
