Package org.cojen.classfile

Source Code of org.cojen.classfile.CodeBuilder

/*
*  Copyright 2004-2010 Brian S O'Neill
*
*  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 org.cojen.classfile;

import org.cojen.classfile.attribute.CodeAttr;
import org.cojen.classfile.constant.ConstantClassInfo;
import org.cojen.classfile.constant.ConstantFieldInfo;

/**
* This class is used as an aid in generating code for a method.
* It controls the max stack, local variable allocation, labels and bytecode.
*
* @author Brian S O'Neill
*/
public class CodeBuilder extends AbstractCodeAssembler implements CodeBuffer, CodeAssembler {
    private final CodeAttr mCodeAttr;
    private final ClassFile mClassFile;
    private final ConstantPool mCp;

    private final InstructionList mInstructions;

    private final LocalVariable mThisReference;
    private final LocalVariable[] mParameters;

    private final int mTarget;

    private final boolean mSaveLineNumberInfo;
    private final boolean mSaveLocalVariableInfo;

    /**
     * Construct a CodeBuilder for the CodeAttr of the given MethodInfo. The
     * CodeBuffer for the CodeAttr is automatically set to this CodeBuilder.
     */
    public CodeBuilder(MethodInfo info) {
        this(info, true, false);
    }

    /**
     * Construct a CodeBuilder for the CodeAttr of the given MethodInfo. The
     * CodeBuffer for the CodeAttr is automatically set to this CodeBuilder.
     *
     * @param saveLineNumberInfo When set false, all calls to mapLineNumber
     * are ignored. By default, this value is true.
     * @param saveLocalVariableInfo When set true, all local variable
     * usage information is saved in the ClassFile. By default, this value
     * is false.
     * @see #mapLineNumber
     */
    public CodeBuilder(MethodInfo info, boolean saveLineNumberInfo,
                       boolean saveLocalVariableInfo) {

        String target = info.getClassFile().getTarget();
        if ("1.0".equals(target)) {
            mTarget = 0x00010000;
        } else if ("1.1".equals(target)) {
            mTarget = 0x00010001;
        } else if ("1.2".equals(target)) {
            mTarget = 0x00010002;
        } else if ("1.3".equals(target)) {
            mTarget = 0x00010003;
        } else if ("1.4".equals(target)) {
            mTarget = 0x00010004;
        } else if ("1.5".equals(target)) {
            mTarget = 0x00010005;
        } else if ("1.6".equals(target)) {
            mTarget = 0x00010006;
        } else {
            mTarget = 0x00010000;
        }

        mCodeAttr = info.getCodeAttr();
        mClassFile = info.getClassFile();
        mCp = mClassFile.getConstantPool();
        mInstructions = new InstructionList(saveLocalVariableInfo);

        mCodeAttr.setCodeBuffer(this);

        mSaveLineNumberInfo = saveLineNumberInfo;
        mSaveLocalVariableInfo = saveLocalVariableInfo;

        // Create LocalVariable references for "this" reference and other
        // passed in parameters.

        LocalVariable localVar;

        if (info.getModifiers().isStatic()) {
            mThisReference = null;
        } else {
            localVar = mInstructions.createLocalParameter("this", mClassFile.getType());
            mThisReference = localVar;

            if (saveLocalVariableInfo) {
                mCodeAttr.localVariableUse(localVar);
            }
        }

        TypeDesc[] paramTypes = info.getMethodDescriptor().getParameterTypes();
        int paramSize = paramTypes.length;

        mParameters = new LocalVariable[paramSize];

        for (int i = 0; i<paramTypes.length; i++) {
            localVar = mInstructions.createLocalParameter(null, paramTypes[i]);
            mParameters[i] = localVar;
            if (saveLocalVariableInfo) {
                mCodeAttr.localVariableUse(localVar);
            }
        }
    }

    public int getMaxStackDepth() {
        return mInstructions.getMaxStackDepth();
    }

    public int getMaxLocals() {
        return mInstructions.getMaxLocals();
    }

    public byte[] getByteCodes() {
        return mInstructions.getByteCodes();
    }

    public ExceptionHandler[] getExceptionHandlers() {
        return mInstructions.getExceptionHandlers();
    }

    /**
     * @param pushed type of argument pushed to operand stack after instruction
     * executes; pass TypeDesc.VOID if nothing
     */
    private void addInstruction(int stackAdjust, TypeDesc pushed, byte opcode) {
        mInstructions.new SimpleInstruction(stackAdjust, pushed, new byte[] {opcode});
    }

    /**
     * @param pushed type of argument pushed to operand stack after instruction
     * executes; pass TypeDesc.VOID if nothing
     */
    private void addInstruction(int stackAdjust, TypeDesc pushed, byte opcode, byte operand) {
        mInstructions.new SimpleInstruction(stackAdjust, pushed, new byte[] {opcode, operand});
    }

    /**
     * @param pushed type of argument pushed to operand stack after instruction
     * executes; pass TypeDesc.VOID if nothing
     */
    private void addInstruction(int stackAdjust, TypeDesc pushed, byte opcode, short operand) {
        mInstructions.new SimpleInstruction
            (stackAdjust, pushed,
             new byte[] {opcode, (byte)(operand >> 8), (byte)operand});
    }

    /**
     * @param pushed type of argument pushed to operand stack after instruction
     * executes; pass TypeDesc.VOID if nothing
     */
    private void addInstruction(int stackAdjust, TypeDesc pushed, byte opcode,
                                ConstantInfo operand) {
        mInstructions.new ConstantOperandInstruction
            (stackAdjust, pushed,
             // The zeros get filled in later, when the ConstantInfo index is
             // resolved.
             new byte[] {opcode, (byte)0, (byte)0}, operand);
    }

    private String getClassName(TypeDesc classDesc) throws IllegalArgumentException {
        if (classDesc.isPrimitive()) {
            throw new IllegalArgumentException("Primitive type not allowed");
        }
        if (classDesc.isArray()) {
            throw new IllegalArgumentException("Array type not allowed");
        }
        return classDesc.getRootName();
    }

    public int getParameterCount() {
        return mParameters.length;
    }

    public LocalVariable getParameter(int index) {
        return mParameters[index];
    }

    public LocalVariable createLocalVariable(String name, TypeDesc type) {
        LocalVariable localVar = mInstructions.createLocalVariable(name, type);

        if (mSaveLocalVariableInfo) {
            mCodeAttr.localVariableUse(localVar);
        }

        return localVar;
    }

    public Label createLabel() {
        return mInstructions.new LabelInstruction();
    }

    public void exceptionHandler(Location startLocation,
                                 Location endLocation,
                                 String catchClassName)
    {
        if (!(startLocation instanceof InstructionList.LabelInstruction)) {
            throw new IllegalArgumentException("Start location is not a label instruction");
        }
        if (!(endLocation instanceof InstructionList.LabelInstruction)) {
            throw new IllegalArgumentException("End location is not a label instruction");
        }

        Location catchLocation = createLabel().setLocation();

        ConstantClassInfo catchClass;
        if (catchClassName == null) {
            catchClass = null;
        } else {
            catchClass = mCp.addConstantClass(catchClassName);
        }

        ExceptionHandler<InstructionList.LabelInstruction> handler =
            new ExceptionHandler<InstructionList.LabelInstruction>
            ((InstructionList.LabelInstruction)startLocation,
             (InstructionList.LabelInstruction)endLocation,
             (InstructionList.LabelInstruction)catchLocation,
             catchClass);

        mInstructions.addExceptionHandler(handler);
    }
   
    public void mapLineNumber(int lineNumber) {
        if (mSaveLineNumberInfo) {
            mCodeAttr.mapLineNumber(createLabel().setLocation(), lineNumber);
        }
    }

    // load-constant-to-stack style instructions

    public void loadNull() {
        addInstruction(1, null, Opcode.ACONST_NULL);
    }

    public void loadConstant(String value) {
        if (value == null) {
            loadNull();
            return;
        }

        int strlen = value.length();

        if (strlen <= (65535 / 3)) {
            // Guaranteed to fit in a Java UTF encoded string.
            ConstantInfo info = mCp.addConstantString(value);
            mInstructions.new LoadConstantInstruction(1, TypeDesc.STRING, info);
            return;
        }

        // Compute actual UTF length.

        int utflen = 0;

        for (int i=0; i<strlen; i++) {
            int c = value.charAt(i);
            if ((c >= 0x0001) && (c <= 0x007F)) {
                utflen++;
            } else if (c > 0x07FF) {
                utflen += 3;
            } else {
                utflen += 2;
            }
        }

        if (utflen <= 65535) {
            ConstantInfo info = mCp.addConstantString(value);
            mInstructions.new LoadConstantInstruction(1, TypeDesc.STRING, info);
            return;
        }

        // Break string up into chunks and construct in a StringBuffer.

        TypeDesc stringBufferDesc;
        if (mTarget >= 0x00010005) {
            stringBufferDesc = TypeDesc.forClass("java.lang.StringBuilder");
        } else {
            stringBufferDesc = TypeDesc.forClass(StringBuffer.class);
        }
       
        TypeDesc intDesc = TypeDesc.INT;
        TypeDesc stringDesc = TypeDesc.STRING;
        TypeDesc[] stringParam = new TypeDesc[] {stringDesc};
       
        newObject(stringBufferDesc);
        dup();
        loadConstant(strlen);
        invokeConstructor(stringBufferDesc, new TypeDesc[] {intDesc});
       
        int beginIndex;
        int endIndex = 0;
       
        while (endIndex < strlen) {
            beginIndex = endIndex;

            // Make each chunk as large as possible.
            utflen = 0;
            for (; endIndex < strlen; endIndex++) {
                int c = value.charAt(endIndex);
                int size;
                if ((c >= 0x0001) && (c <= 0x007F)) {
                    size = 1;
                } else if (c > 0x07FF) {
                    size = 3;
                } else {
                    size = 2;
                }

                if ((utflen + size) > 65535) {
                    break;
                } else {
                    utflen += size;
                }
            }

            String substr = value.substring(beginIndex, endIndex);

            ConstantInfo info = mCp.addConstantString(substr);
            mInstructions.new LoadConstantInstruction(1, TypeDesc.STRING, info);

            invokeVirtual(stringBufferDesc, "append",
                          stringBufferDesc, stringParam);
        }
       
        invokeVirtual(stringBufferDesc, "toString", stringDesc, null);
    }

    public void loadConstant(TypeDesc type) throws IllegalStateException {
        if (type == null) {
            loadNull();
            return;
        }

        if (type.isPrimitive()) {
            if (mTarget < 0x00010001) {
                throw new IllegalStateException
                    ("Loading constant primitive classes not supported below target version 1.1");
            }
            loadStaticField(type.toObjectType(), "TYPE", TypeDesc.forClass(Class.class));
        } else {
            if (mTarget < 0x00010005) {
                throw new IllegalStateException
                    ("Loading constant object classes not supported below target version 1.5");
            }
            ConstantInfo info = mCp.addConstantClass(type);
            mInstructions.new LoadConstantInstruction(1, TypeDesc.forClass(Class.class), info);
        }
    }

    public void loadConstant(boolean value) {
        loadConstant(value ? 1 : 0);
    }

    public void loadConstant(int value) {
        if (-1 <= value && value <= 5) {
            byte op;

            switch(value) {
            case -1:
                op = Opcode.ICONST_M1;
                break;
            case 0:
                op = Opcode.ICONST_0;
                break;
            case 1:
                op = Opcode.ICONST_1;
                break;
            case 2:
                op = Opcode.ICONST_2;
                break;
            case 3:
                op = Opcode.ICONST_3;
                break;
            case 4:
                op = Opcode.ICONST_4;
                break;
            case 5:
                op = Opcode.ICONST_5;
                break;
            default:
                op = Opcode.NOP;
            }

            addInstruction(1, TypeDesc.INT, op);
        } else if (-128 <= value && value <= 127) {
            addInstruction(1, TypeDesc.INT, Opcode.BIPUSH, (byte)value);
        } else if (-32768 <= value && value <= 32767) {
            addInstruction(1, TypeDesc.INT, Opcode.SIPUSH, (short)value);
        } else {
            ConstantInfo info = mCp.addConstantInteger(value);
            mInstructions.new LoadConstantInstruction(1, TypeDesc.INT, info);
        }
    }

    public void loadConstant(long value) {
        if (value == 0) {
            addInstruction(2, TypeDesc.LONG, Opcode.LCONST_0);
        } else if (value == 1) {
            addInstruction(2, TypeDesc.LONG, Opcode.LCONST_1);
        } else {
            ConstantInfo info = mCp.addConstantLong(value);
            mInstructions.new LoadConstantInstruction(2, TypeDesc.LONG, info, true);
        }
    }

    public void loadConstant(float value) {
        if (value == 0) {
            addInstruction(1, TypeDesc.FLOAT, Opcode.FCONST_0);
        } else if (value == 1) {
            addInstruction(1, TypeDesc.FLOAT, Opcode.FCONST_1);
        } else if (value == 2) {
            addInstruction(1, TypeDesc.FLOAT, Opcode.FCONST_2);
        } else {
            ConstantInfo info = mCp.addConstantFloat(value);
            mInstructions.new LoadConstantInstruction(1, TypeDesc.FLOAT, info);
        }
    }

    public void loadConstant(double value) {
        if (value == 0) {
            addInstruction(2, TypeDesc.DOUBLE, Opcode.DCONST_0);
        } else if (value == 1) {
            addInstruction(2, TypeDesc.DOUBLE, Opcode.DCONST_1);
        } else {
            ConstantInfo info = mCp.addConstantDouble(value);
            mInstructions.new LoadConstantInstruction(2, TypeDesc.DOUBLE, info, true);
        }
    }

    // load-local-to-stack style instructions

    public void loadLocal(LocalVariable local) {
        if (local == null) {
            throw new IllegalArgumentException("No local variable specified");
        }
        int stackAdjust = local.getType().isDoubleWord() ? 2 : 1;
        mInstructions.new LoadLocalInstruction(stackAdjust, local);
    }

    public void loadThis() {
        if (mThisReference != null) {
            loadLocal(mThisReference);
        } else {
            throw new IllegalStateException
                ("Attempt to load \"this\" reference in a static method");
        }
    }

    // store-from-stack-to-local style instructions

    public void storeLocal(LocalVariable local) {
        if (local == null) {
            throw new IllegalArgumentException("No local variable specified");
        }
        int stackAdjust = local.getType().isDoubleWord() ? -2 : -1;
        mInstructions.new StoreLocalInstruction(stackAdjust, local);
    }

    // load-to-stack-from-array style instructions

    public void loadFromArray(TypeDesc type) {
        byte op;
        int stackAdjust = -1;

        switch (type.getTypeCode()) {
        case TypeDesc.INT_CODE:
            op = Opcode.IALOAD;
            break;
        case TypeDesc.BOOLEAN_CODE:
        case TypeDesc.BYTE_CODE:
            op = Opcode.BALOAD;
            break;
        case TypeDesc.SHORT_CODE:
            op = Opcode.SALOAD;
            break;
        case TypeDesc.CHAR_CODE:
            op = Opcode.CALOAD;
            break;
        case TypeDesc.FLOAT_CODE:
            op = Opcode.FALOAD;
            break;
        case TypeDesc.LONG_CODE:
            stackAdjust = 0;
            op = Opcode.LALOAD;
            break;
        case TypeDesc.DOUBLE_CODE:
            stackAdjust = 0;
            op = Opcode.DALOAD;
            break;
        default:
            op = Opcode.AALOAD;
            break;
        }

        addInstruction(stackAdjust, type, op);
    }

    // store-to-array-from-stack style instructions

    public void storeToArray(TypeDesc type) {
        byte op;
        int stackAdjust = -3;

        switch (type.getTypeCode()) {
        case TypeDesc.INT_CODE:
            op = Opcode.IASTORE;
            break;
        case TypeDesc.BOOLEAN_CODE:
        case TypeDesc.BYTE_CODE:
            op = Opcode.BASTORE;
            break;
        case TypeDesc.SHORT_CODE:
            op = Opcode.SASTORE;
            break;
        case TypeDesc.CHAR_CODE:
            op = Opcode.CASTORE;
            break;
        case TypeDesc.FLOAT_CODE:
            op = Opcode.FASTORE;
            break;
        case TypeDesc.LONG_CODE:
            stackAdjust = -4;
            op = Opcode.LASTORE;
            break;
        case TypeDesc.DOUBLE_CODE:
            stackAdjust = -4;
            op = Opcode.DASTORE;
            break;
        default:
            op = Opcode.AASTORE;
            break;
        }

        addInstruction(stackAdjust, TypeDesc.VOID, op);
    }

    // load-field-to-stack style instructions

    public void loadField(String fieldName,
                          TypeDesc type) {
        getfield(0, Opcode.GETFIELD, constantField(fieldName, type), type);
    }

    public void loadField(String className,
                          String fieldName,
                          TypeDesc type) {

        getfield(0, Opcode.GETFIELD,
                 mCp.addConstantField(className, fieldName, type),
                 type);
    }

    public void loadField(TypeDesc classDesc,
                          String fieldName,
                          TypeDesc type) {

        loadField(getClassName(classDesc), fieldName, type);
    }

    public void loadStaticField(String fieldName,
                                TypeDesc type) {

        getfield(1, Opcode.GETSTATIC, constantField(fieldName, type), type);
    }

    public void loadStaticField(String className,
                                String fieldName,
                                TypeDesc type) {

        getfield(1, Opcode.GETSTATIC,
                 mCp.addConstantField(className, fieldName, type),
                 type);
    }

    public void loadStaticField(TypeDesc classDesc,
                                String fieldName,
                                TypeDesc type) {

        loadStaticField(getClassName(classDesc), fieldName, type);
    }

    private void getfield(int stackAdjust, byte opcode, ConstantInfo info, TypeDesc type) {
        if (type.isDoubleWord()) {
            stackAdjust++;
        }
        addInstruction(stackAdjust, type, opcode, info);
    }

    private ConstantFieldInfo constantField(String fieldName, TypeDesc type) {
        return mCp.addConstantField(mClassFile.getClassName(), fieldName, type);
    }

    // store-to-field-from-stack style instructions

    public void storeField(String fieldName, TypeDesc type) {
        putfield(-1, Opcode.PUTFIELD, constantField(fieldName, type), type);
    }

    public void storeField(String className, String fieldName, TypeDesc type) {
        putfield(-1, Opcode.PUTFIELD,
                 mCp.addConstantField(className, fieldName, type),
                 type);
    }

    public void storeField(TypeDesc classDesc, String fieldName, TypeDesc type) {
        storeField(getClassName(classDesc), fieldName, type);
    }

    public void storeStaticField(String fieldName, TypeDesc type) {
        putfield(0, Opcode.PUTSTATIC, constantField(fieldName, type), type);
    }

    public void storeStaticField(String className, String fieldName, TypeDesc type) {
        putfield(0, Opcode.PUTSTATIC,
                 mCp.addConstantField(className, fieldName, type),
                 type);
    }

    public void storeStaticField(TypeDesc classDesc, String fieldName, TypeDesc type) {
        storeStaticField(getClassName(classDesc), fieldName, type);
    }

    private void putfield(int stackAdjust, byte opcode, ConstantInfo info, TypeDesc type) {
        if (type.isDoubleWord()) {
            stackAdjust -= 2;
        } else {
            stackAdjust--;
        }
        addInstruction(stackAdjust, TypeDesc.VOID, opcode, info);
    }

    // return style instructions

    public void returnVoid() {
        addInstruction(0, TypeDesc.VOID, Opcode.RETURN);
    }

    public void returnValue(TypeDesc type) {
        int stackAdjust = -1;
        byte op;

        switch (type.getTypeCode()) {
        case TypeDesc.INT_CODE:
        case TypeDesc.BOOLEAN_CODE:
        case TypeDesc.BYTE_CODE:
        case TypeDesc.SHORT_CODE:
        case TypeDesc.CHAR_CODE:
            op = Opcode.IRETURN;
            break;
        case TypeDesc.FLOAT_CODE:
            op = Opcode.FRETURN;
            break;
        case TypeDesc.LONG_CODE:
            stackAdjust = -2;
            op = Opcode.LRETURN;
            break;
        case TypeDesc.DOUBLE_CODE:
            stackAdjust = -2;
            op = Opcode.DRETURN;
            break;
        case TypeDesc.VOID_CODE:
            stackAdjust = 0;
            op = Opcode.RETURN;
            break;
        default:
            op = Opcode.ARETURN;
            break;
        }

        addInstruction(stackAdjust, TypeDesc.VOID, op);
    }

    // numerical conversion style instructions

    public void convert(TypeDesc fromType, TypeDesc toType) {
        convert(fromType, toType, CONVERT_FP_NORMAL);
    }

    public void convert(TypeDesc fromType, TypeDesc toType, int fpConvertMode) {
        if (fpConvertMode < 0 || fpConvertMode > CONVERT_FP_RAW_BITS) {
            throw new IllegalArgumentException("Illegal floating point conversion mode");
        }

        if (toType == TypeDesc.OBJECT) {
            if (fromType.isPrimitive()) {
                toType = fromType.toObjectType();
            } else {
                return;
            }
        }

        if (fromType == toType) {
            return;
        }

        TypeDesc fromPrimitiveType = fromType.toPrimitiveType();
        if (fromPrimitiveType == null) {
            if (!toType.isPrimitive()) {
                Class fromClass = fromType.toClass();
                if (fromClass != null) {
                    Class toClass = toType.toClass();
                    if (toClass != null && toClass.isAssignableFrom(fromClass)) {
                        return;
                    }
                }
            }
            throw invalidConversion(fromType, toType);
        }
        int fromTypeCode = fromPrimitiveType.getTypeCode();

        if (toType.toClass() == Number.class) {
            switch (fromTypeCode) {
            case TypeDesc.INT_CODE:
            case TypeDesc.BYTE_CODE:
            case TypeDesc.SHORT_CODE:
            case TypeDesc.LONG_CODE:
            case TypeDesc.FLOAT_CODE:
            case TypeDesc.DOUBLE_CODE:
                if (fromType.isPrimitive()) {
                    toType = fromType.toObjectType();
                } else {
                    return;
                }
            }
        }

        TypeDesc toPrimitiveType = toType.toPrimitiveType();
        if (toPrimitiveType == null) {
            throw invalidConversion(fromType, toType);
        }
        int toTypeCode = toPrimitiveType.getTypeCode();

        // Location is set at end, after doConversion.
        Label end = createLabel();

        doConversion: {
            int stackAdjust = 0;
            byte op;
           
            switch (fromTypeCode) {
            case TypeDesc.INT_CODE:
            case TypeDesc.BYTE_CODE:
            case TypeDesc.SHORT_CODE:
            case TypeDesc.CHAR_CODE:
            case TypeDesc.BOOLEAN_CODE:
                switch (toTypeCode) {
                case TypeDesc.BYTE_CODE:
                    op = (fromTypeCode == TypeDesc.BYTE_CODE) ?
                        Opcode.NOP : Opcode.I2B;
                    break;
                case TypeDesc.SHORT_CODE:
                    op = (fromTypeCode == TypeDesc.SHORT_CODE) ?
                        Opcode.NOP : Opcode.I2S;
                    break;
                case TypeDesc.CHAR_CODE:
                    op = (fromTypeCode == TypeDesc.CHAR_CODE) ?
                        Opcode.NOP : Opcode.I2C;
                    break;
                case TypeDesc.FLOAT_CODE:
                    op = Opcode.I2F;
                    break;
                case TypeDesc.LONG_CODE:
                    stackAdjust = 1;
                    op = Opcode.I2L;
                    break;
                case TypeDesc.DOUBLE_CODE:
                    stackAdjust = 1;
                    op = Opcode.I2D;
                    break;
                case TypeDesc.INT_CODE:
                    op = Opcode.NOP;
                    break;
                case TypeDesc.BOOLEAN_CODE:
                    if (!fromType.isPrimitive()) {
                        if (!toType.isPrimitive()) {
                            nullConvert(end);
                        }
                        unbox(fromType, fromPrimitiveType);
                    }
                    toBoolean(!toType.isPrimitive());
                    break doConversion;
                default:
                    throw invalidConversion(fromType, toType);
                }
                break;
               
            case TypeDesc.LONG_CODE:
                switch (toTypeCode) {
                case TypeDesc.INT_CODE:
                    stackAdjust = -1;
                    op = Opcode.L2I;
                    break;
                case TypeDesc.FLOAT_CODE:
                    stackAdjust = -1;
                    op = Opcode.L2F;
                    break;
                case TypeDesc.DOUBLE_CODE:
                    op = Opcode.L2D;
                    break;
                case TypeDesc.BYTE_CODE:
                case TypeDesc.CHAR_CODE:
                case TypeDesc.SHORT_CODE:
                    addInstruction(-1, TypeDesc.INT, Opcode.L2I);
                    convert(TypeDesc.INT, toPrimitiveType);
                    // fall through
                case TypeDesc.LONG_CODE:
                    op = Opcode.NOP;
                    break;
                case TypeDesc.BOOLEAN_CODE:
                    if (!fromType.isPrimitive()) {
                        if (!toType.isPrimitive()) {
                            nullConvert(end);
                        }
                        unbox(fromType, fromPrimitiveType);
                    }
                    loadConstant(0L);
                    math(Opcode.LCMP);
                    toBoolean(!toType.isPrimitive());
                    break doConversion;
                default:
                    throw invalidConversion(fromType, toType);
                }
                break;
               
            case TypeDesc.FLOAT_CODE:
                switch (toTypeCode) {
                case TypeDesc.INT_CODE:
                    op = Opcode.F2I;
                    break;
                case TypeDesc.LONG_CODE:
                    stackAdjust = 1;
                    op = Opcode.F2L;
                    break;
                case TypeDesc.DOUBLE_CODE:
                    stackAdjust = 1;
                    op = Opcode.F2D;
                    break;
                case TypeDesc.BYTE_CODE:
                case TypeDesc.CHAR_CODE:
                case TypeDesc.SHORT_CODE:
                    addInstruction(0, TypeDesc.INT, Opcode.F2I);
                    convert(TypeDesc.INT, toPrimitiveType);
                    // fall through
                case TypeDesc.FLOAT_CODE:
                    op = Opcode.NOP;
                    break;
                case TypeDesc.BOOLEAN_CODE:
                    if (!fromType.isPrimitive()) {
                        if (!toType.isPrimitive()) {
                            nullConvert(end);
                        }
                        unbox(fromType, fromPrimitiveType);
                    }
                    loadConstant(0.0f);
                    math(Opcode.FCMPG);
                    toBoolean(!toType.isPrimitive());
                    break doConversion;
                default:
                    throw invalidConversion(fromType, toType);
                }
                break;
               
            case TypeDesc.DOUBLE_CODE:
                switch (toTypeCode) {
                case TypeDesc.INT_CODE:
                    stackAdjust = -1;
                    op = Opcode.D2I;
                    break;
                case TypeDesc.FLOAT_CODE:
                    stackAdjust = -1;
                    op = Opcode.D2F;
                    break;
                case TypeDesc.LONG_CODE:
                    op = Opcode.D2L;
                    break;
                case TypeDesc.BYTE_CODE:
                case TypeDesc.CHAR_CODE:
                case TypeDesc.SHORT_CODE:
                    addInstruction(-1, TypeDesc.INT, Opcode.D2I);
                    convert(TypeDesc.INT, toPrimitiveType);
                    // fall through
                case TypeDesc.DOUBLE_CODE:
                    op = Opcode.NOP;
                    break;
                case TypeDesc.BOOLEAN_CODE:
                    if (!fromType.isPrimitive()) {
                        if (!toType.isPrimitive()) {
                            nullConvert(end);
                        }
                        unbox(fromType, fromPrimitiveType);
                    }
                    loadConstant(0.0d);
                    math(Opcode.DCMPG);
                    toBoolean(!toType.isPrimitive());
                    break doConversion;
                default:
                    throw invalidConversion(fromType, toType);
                }
                break;
               
            default:
                throw invalidConversion(fromType, toType);
            }
           
            if (!fromType.isPrimitive()) {
                if (!toType.isPrimitive()) {
                    nullConvert(end);
                }
                unbox(fromType, fromPrimitiveType);
            }

            if (toType.isPrimitive()) {
                if (op != Opcode.NOP) {
                    convertPrimitive(stackAdjust, op, fpConvertMode);
                }
            } else {
                if (op == Opcode.NOP) {
                    prebox(toPrimitiveType, toType);
                } else if (!fromPrimitiveType.isDoubleWord() &&
                           toPrimitiveType.isDoubleWord()) {
                    // Slight optimization here. Perform prebox on single word value,
                    // depending on what conversion is being applied.
                    prebox(fromPrimitiveType, toType);
                    convertPrimitive(stackAdjust, op, fpConvertMode);
                } else {
                    convertPrimitive(stackAdjust, op, fpConvertMode);
                    prebox(toPrimitiveType, toType);
                }
                box(toPrimitiveType, toType);
            }
        }

        end.setLocation();
    }

    /**
     * @param from must be an object type
     * @param to must be a primitive type
     */
    private void unbox(TypeDesc from, TypeDesc to) {
        String methodName;

        switch (to.getTypeCode()) {
        case TypeDesc.BOOLEAN_CODE:
            methodName = "booleanValue";
            break;
        case TypeDesc.CHAR_CODE:
            methodName = "charValue";
            break;
        case TypeDesc.FLOAT_CODE:
            methodName = "floatValue";
            break;
        case TypeDesc.DOUBLE_CODE:
            methodName = "doubleValue";
            break;
        case TypeDesc.BYTE_CODE:
            methodName = "byteValue";
            break;
        case TypeDesc.SHORT_CODE:
            methodName = "shortValue";
            break;
        case TypeDesc.INT_CODE:
            methodName = "intValue";
            break;
        case TypeDesc.LONG_CODE:
            methodName = "longValue";
            break;
        default:
            return;
        }

        invokeVirtual(from.getRootName(), methodName, to, null);
    }

    /**
     * @param from must be a primitive type
     * @param to must be an object type
     */
    private void prebox(TypeDesc from, TypeDesc to) {
        if (mTarget >= 0x00010005) {
            // No need to prebox since static valueOf method can be called
            // instead.
            return;
        }

        // Wouldn't it be cool if I could walk backwards in the instruction
        // list and insert the new-dup pair before the value to box was even
        // put on the stack?

        switch (from.getTypeCode()) {
        default:
            break;
        case TypeDesc.BOOLEAN_CODE:
            if (to.toPrimitiveType().getTypeCode() == TypeDesc.BOOLEAN_CODE) {
                break;
            }
            // fall through
        case TypeDesc.CHAR_CODE:
        case TypeDesc.FLOAT_CODE:
        case TypeDesc.BYTE_CODE:
        case TypeDesc.SHORT_CODE:
        case TypeDesc.INT_CODE:
            newObject(to);
            dupX1();
            swap();
            break;
        case TypeDesc.DOUBLE_CODE:
        case TypeDesc.LONG_CODE:
            newObject(to);
            dupX2();
            dupX2();
            pop();
            break;
        }
    }

    /**
     * @param from must be a primitive type
     * @param to must be an object type
     */
    private void box(TypeDesc from, TypeDesc to) {
        if (mTarget >= 0x00010005) {
            // Call the new valueOf method.
            invokeStatic(to.getRootName(), "valueOf", to, new TypeDesc[] {from});
            return;
        }

        switch (from.getTypeCode()) {
        case TypeDesc.BOOLEAN_CODE:
            toBoolean(true);
            break;
        case TypeDesc.CHAR_CODE:
        case TypeDesc.FLOAT_CODE:
        case TypeDesc.BYTE_CODE:
        case TypeDesc.SHORT_CODE:
        case TypeDesc.INT_CODE:
        case TypeDesc.DOUBLE_CODE:
        case TypeDesc.LONG_CODE:
            invokeConstructor(to.getRootName(), new TypeDesc[]{from});
            break;
        }
    }

    // Converts an int on the stack to a boolean.
    private void toBoolean(boolean box) {
        if (box && mTarget >= 0x00010004) {
            // Call the new valueOf method.
            invokeStatic("java.lang.Boolean", "valueOf",
                         TypeDesc.BOOLEAN.toObjectType(),
                         new TypeDesc[] {TypeDesc.BOOLEAN});
            return;
        }

        Label nonZero = createLabel();
        Label done = createLabel();
        ifZeroComparisonBranch(nonZero, "!=");
        if (box) {
            TypeDesc newType = TypeDesc.BOOLEAN.toObjectType();
            loadStaticField(newType.getRootName(), "FALSE", newType);
            branch(done);
            nonZero.setLocation();
            loadStaticField(newType.getRootName(), "TRUE", newType);
        } else {
            loadConstant(false);
            branch(done);
            nonZero.setLocation();
            loadConstant(true);
        }
        done.setLocation();
    }

    private void convertPrimitive(int stackAdjust, byte op, int fpConvertMode) {
        if (fpConvertMode != CONVERT_FP_NORMAL) {
            switch (op) {
            case Opcode.I2F:
                invokeStatic("java.lang.Float", "intBitsToFloat", TypeDesc.FLOAT,
                             new TypeDesc[] {TypeDesc.INT});
                return;

            case Opcode.L2D:
                invokeStatic("java.lang.Double", "longBitsToDouble", TypeDesc.DOUBLE,
                             new TypeDesc[] {TypeDesc.LONG});
                return;

            case Opcode.F2I:
                if (fpConvertMode == CONVERT_FP_RAW_BITS) {
                    invokeStatic("java.lang.Float", "floatToRawIntBits", TypeDesc.INT,
                                 new TypeDesc[] {TypeDesc.FLOAT});
                } else {
                    invokeStatic("java.lang.Float", "floatToIntBits", TypeDesc.INT,
                                 new TypeDesc[] {TypeDesc.FLOAT});
                }
                return;

            case Opcode.D2L:
                if (fpConvertMode == CONVERT_FP_RAW_BITS) {
                    invokeStatic("java.lang.Double", "doubleToRawLongBits", TypeDesc.LONG,
                                 new TypeDesc[] {TypeDesc.DOUBLE});
                } else {
                    invokeStatic("java.lang.Double", "doubleToLongBits", TypeDesc.LONG,
                                 new TypeDesc[] {TypeDesc.DOUBLE});
                }
                return;
            }
        }

        TypeDesc pushed;
        switch (op) {
        case Opcode.I2F:
            pushed = TypeDesc.FLOAT;
            break;
        case Opcode.L2D:
            pushed = TypeDesc.DOUBLE;
            break;
        case Opcode.F2I:
            pushed = TypeDesc.INT;
            break;
        case Opcode.D2L:
            pushed = TypeDesc.LONG;
            break;
        default:
            pushed = TypeDesc.VOID;
            break;
        }

        addInstruction(stackAdjust, pushed, op);
    }   

    // Check if from object is null. If so, no need to convert, and don't throw
    // a NullPointerException. Assumes that from type and to type are objects.
    private void nullConvert(Label end) {
        LocalVariable temp = createLocalVariable("temp", TypeDesc.OBJECT);
        storeLocal(temp);
        loadLocal(temp);
        Label notNull = createLabel();
        ifNullBranch(notNull, false);
        loadNull();
        branch(end);
        notNull.setLocation();
        loadLocal(temp);
    }

    private IllegalArgumentException invalidConversion
        (TypeDesc from, TypeDesc to)
    {
        throw new IllegalArgumentException
            ("Invalid conversion: " + from.getFullName() + " to " +
             to.getFullName());
    }

    // invocation style instructions

    public void invokeVirtual(String methodName,
                              TypeDesc ret,
                              TypeDesc[] params) {
        invokeVirtual(mClassFile.getClassName(), methodName, ret, params);
    }

    public void invokeVirtual(String className,
                              String methodName,
                              TypeDesc ret,
                              TypeDesc[] params) {
        mInstructions.new InvokeInstruction
            (Opcode.INVOKEVIRTUAL,
             mCp.addConstantMethod(className, methodName, ret, params),
             ret, params);
    }

    public void invokeVirtual(TypeDesc classDesc,
                              String methodName,
                              TypeDesc ret,
                              TypeDesc[] params) {
        invokeVirtual(getClassName(classDesc), methodName, ret, params);
    }

    public void invokeStatic(String methodName,
                             TypeDesc ret,
                             TypeDesc[] params) {
        invokeStatic(mClassFile.getClassName(), methodName, ret, params);
    }

    public void invokeStatic(String className,
                             String methodName,
                             TypeDesc ret,
                             TypeDesc[] params) {
        mInstructions.new InvokeInstruction
            (Opcode.INVOKESTATIC,
             mCp.addConstantMethod(className, methodName, ret, params),
             ret, params);
    }

    public void invokeStatic(TypeDesc classDesc,
                             String methodName,
                             TypeDesc ret,
                             TypeDesc[] params) {
        invokeStatic(getClassName(classDesc), methodName, ret, params);
    }

    public void invokeInterface(String className,
                                String methodName,
                                TypeDesc ret,
                                TypeDesc[] params) {
        mInstructions.new InvokeInstruction
            (Opcode.INVOKEINTERFACE,
             mCp.addConstantInterfaceMethod(className, methodName, ret, params),
             ret, params);
    }

    public void invokeInterface(TypeDesc classDesc,
                                String methodName,
                                TypeDesc ret,
                                TypeDesc[] params) {
        invokeInterface(getClassName(classDesc), methodName, ret, params);
    }

    public void invokePrivate(String methodName,
                              TypeDesc ret,
                              TypeDesc[] params) {
        mInstructions.new InvokeInstruction
            (Opcode.INVOKESPECIAL,
             mCp.addConstantMethod(mClassFile.getClassName(), methodName, ret, params),
             ret, params);
    }

    public void invokeSuper(String superClassName,
                            String methodName,
                            TypeDesc ret,
                            TypeDesc[] params) {
        mInstructions.new InvokeInstruction
            (Opcode.INVOKESPECIAL,
             mCp.addConstantMethod(superClassName, methodName, ret, params),
             ret, params);
    }

    public void invokeSuper(TypeDesc superClassDesc,
                            String methodName,
                            TypeDesc ret,
                            TypeDesc[] params) {
        invokeSuper(getClassName(superClassDesc), methodName, ret, params);
    }

    public void invokeConstructor(TypeDesc[] params) {
        invokeConstructor(mClassFile.getClassName(), mClassFile.getType(), params);
    }

    public void invokeConstructor(String className, TypeDesc[] params) {
        invokeConstructor(className, TypeDesc.forClass(className), params);
    }

    public void invokeConstructor(TypeDesc classDesc, TypeDesc[] params) {
        invokeConstructor(getClassName(classDesc), classDesc, params);
    }

    private void invokeConstructor(String className, TypeDesc classDesc, TypeDesc[] params) {
        mInstructions.new InvokeConstructorInstruction
            (mCp.addConstantConstructor(className, params), classDesc, params);
    }

    public void invokeSuperConstructor(TypeDesc[] params) {
        invokeConstructor(mClassFile.getSuperClassName(), params);
    }

    // creation style instructions

    public void newObject(TypeDesc type) {
        if (type.isArray()) {
            newObject(type, 1);
        } else {
            mInstructions.new NewObjectInstruction(mCp.addConstantClass(type));
        }
    }

    public void newObject(TypeDesc type, int dimensions) {
        if (dimensions <= 0) {
            // If type refers to an array, then this code is bogus.
            mInstructions.new NewObjectInstruction(mCp.addConstantClass(type));
            return;
        }

        TypeDesc componentType = type.getComponentType();

        if (dimensions == 1) {
            if (componentType.isPrimitive()) {
                addInstruction(0, type, Opcode.NEWARRAY, (byte)componentType.getTypeCode());
                return;
            }
            addInstruction(0, type, Opcode.ANEWARRAY, mCp.addConstantClass(componentType));
            return;
        }

        int stackAdjust = -(dimensions - 1);
        ConstantInfo info = mCp.addConstantClass(type);
        byte[] bytes = new byte[4];

        bytes[0] = Opcode.MULTIANEWARRAY;
        //bytes[1] = (byte)0;
        //bytes[2] = (byte)0;
        bytes[3] = (byte)dimensions;
       
        mInstructions.new ConstantOperandInstruction(stackAdjust, type, bytes, info);
    }

    // stack operation style instructions

    public void dup() {
        mInstructions.new StackOperationInstruction(Opcode.DUP);
    }

    public void dupX1() {
        mInstructions.new StackOperationInstruction(Opcode.DUP_X1);
    }

    public void dupX2() {
        mInstructions.new StackOperationInstruction(Opcode.DUP_X2);
    }

    public void dup2() {
        mInstructions.new StackOperationInstruction(Opcode.DUP2);
    }

    public void dup2X1() {
        mInstructions.new StackOperationInstruction(Opcode.DUP2_X1);
    }

    public void dup2X2() {
        mInstructions.new StackOperationInstruction(Opcode.DUP2_X2);
    }

    public void pop() {
        mInstructions.new StackOperationInstruction(Opcode.POP);
    }

    public void pop2() {
        mInstructions.new StackOperationInstruction(Opcode.POP2);
    }

    public void swap() {
        mInstructions.new StackOperationInstruction(Opcode.SWAP);
    }

    public void swap2() {
        dup2X2();
        pop2();
    }

    // flow control instructions

    private void branch(int stackAdjust, Location location, byte opcode) {
        if (!(location instanceof InstructionList.LabelInstruction)) {
            throw new IllegalArgumentException("Branch location is not a label instruction");
        }
        mInstructions.new BranchInstruction(stackAdjust, opcode,
                                            (InstructionList.LabelInstruction)location);
    }

    public void branch(Location location) {
        branch(0, location, Opcode.GOTO);
    }

    public void ifNullBranch(Location location, boolean choice) {
        branch(-1, location, choice ? Opcode.IFNULL : Opcode.IFNONNULL);
    }

    public void ifEqualBranch(Location location, boolean choice) {
        branch(-2, location, choice ? Opcode.IF_ACMPEQ : Opcode.IF_ACMPNE);
    }

    public void ifZeroComparisonBranch(Location location, String choice)
        throws IllegalArgumentException {

        choice = choice.intern();

        byte opcode;
        if (choice ==  "==") {
            opcode = Opcode.IFEQ;
        } else if (choice == "!=") {
            opcode = Opcode.IFNE;
        } else if (choice == "<") {
            opcode = Opcode.IFLT;
        } else if (choice == ">=") {
            opcode = Opcode.IFGE;
        } else if (choice == ">") {
            opcode = Opcode.IFGT;
        } else if (choice == "<=") {
            opcode = Opcode.IFLE;
        } else {
            throw new IllegalArgumentException
                ("Invalid comparision choice: " + choice);
        }
       
        branch(-1, location, opcode);
    }

    public void ifComparisonBranch(Location location, String choice)
        throws IllegalArgumentException {

        choice = choice.intern();

        byte opcode;
        if (choice ==  "==") {
            opcode = Opcode.IF_ICMPEQ;
        } else if (choice == "!=") {
            opcode = Opcode.IF_ICMPNE;
        } else if (choice == "<") {
            opcode = Opcode.IF_ICMPLT;
        } else if (choice == ">=") {
            opcode = Opcode.IF_ICMPGE;
        } else if (choice == ">") {
            opcode = Opcode.IF_ICMPGT;
        } else if (choice == "<=") {
            opcode = Opcode.IF_ICMPLE;
        } else {
            throw new IllegalArgumentException
                ("Invalid comparision choice: " + choice);
        }

        branch(-2, location, opcode);
    }

    public void switchBranch(int[] cases, Location[] locations, Location defaultLocation) {
        mInstructions.new SwitchInstruction(cases, locations, defaultLocation);
    }

    public void jsr(Location location) {
        // Adjust the stack by one to make room for the return address.
        branch(1, location, Opcode.JSR);
    }

    public void ret(LocalVariable local) {
        if (local == null) {
            throw new IllegalArgumentException("No local variable specified");
        }

        mInstructions.new RetInstruction(local);
    }

    // math instructions

    public void math(byte opcode) {
        int stackAdjust;
       
        switch(opcode) {
        case Opcode.INEG:
        case Opcode.LNEG:
        case Opcode.FNEG:
        case Opcode.DNEG:
            stackAdjust = 0;
            break;
        case Opcode.IADD:
        case Opcode.ISUB:
        case Opcode.IMUL:
        case Opcode.IDIV:
        case Opcode.IREM:
        case Opcode.IAND:
        case Opcode.IOR:
        case Opcode.IXOR:
        case Opcode.ISHL:
        case Opcode.ISHR:
        case Opcode.IUSHR:
        case Opcode.FADD:
        case Opcode.FSUB:
        case Opcode.FMUL:
        case Opcode.FDIV:
        case Opcode.FREM:
        case Opcode.FCMPG:
        case Opcode.FCMPL:
        case Opcode.LSHL:
        case Opcode.LSHR:
        case Opcode.LUSHR:
            stackAdjust = -1;
            break;
        case Opcode.LADD:
        case Opcode.LSUB:
        case Opcode.LMUL:
        case Opcode.LDIV:
        case Opcode.LREM:
        case Opcode.LAND:
        case Opcode.LOR:
        case Opcode.LXOR:
        case Opcode.DADD:
        case Opcode.DSUB:
        case Opcode.DMUL:
        case Opcode.DDIV:
        case Opcode.DREM:
            stackAdjust = -2;
            break;
        case Opcode.LCMP:
        case Opcode.DCMPG:
        case Opcode.DCMPL:
            stackAdjust = -3;
            break;
        default:
            throw new IllegalArgumentException
                ("Not a math opcode: " + Opcode.getMnemonic(opcode));
        }

        TypeDesc pushed;

        switch(opcode) {
        case Opcode.INEG:
        case Opcode.IADD:
        case Opcode.ISUB:
        case Opcode.IMUL:
        case Opcode.IDIV:
        case Opcode.IREM:
        case Opcode.IAND:
        case Opcode.IOR:
        case Opcode.IXOR:
        case Opcode.ISHL:
        case Opcode.ISHR:
        case Opcode.IUSHR:
        case Opcode.LCMP:
        case Opcode.FCMPG:
        case Opcode.FCMPL:
        case Opcode.DCMPG:
        case Opcode.DCMPL:
            pushed = TypeDesc.INT;
            break;
        case Opcode.LNEG:
        case Opcode.LADD:
        case Opcode.LSUB:
        case Opcode.LMUL:
        case Opcode.LDIV:
        case Opcode.LREM:
        case Opcode.LAND:
        case Opcode.LOR:
        case Opcode.LXOR:
        case Opcode.LSHL:
        case Opcode.LSHR:
        case Opcode.LUSHR:
            pushed = TypeDesc.LONG;
            break;
        case Opcode.FNEG:
        case Opcode.FADD:
        case Opcode.FSUB:
        case Opcode.FMUL:
        case Opcode.FDIV:
        case Opcode.FREM:
            pushed = TypeDesc.FLOAT;
            break;
        case Opcode.DNEG:
        case Opcode.DADD:
        case Opcode.DSUB:
        case Opcode.DMUL:
        case Opcode.DDIV:
        case Opcode.DREM:
            pushed = TypeDesc.DOUBLE;
            break;
        default:
            throw new IllegalArgumentException
                ("Not a math opcode: " + Opcode.getMnemonic(opcode));
        }

        addInstruction(stackAdjust, pushed, opcode);
    }

    // miscellaneous instructions

    public void arrayLength() {
        addInstruction(0, TypeDesc.INT, Opcode.ARRAYLENGTH);
    }

    public void throwObject() {
        addInstruction(-1, TypeDesc.VOID, Opcode.ATHROW);
    }

    public void checkCast(TypeDesc type) {
        ConstantInfo info = mCp.addConstantClass(type);
        addInstruction(0, TypeDesc.VOID, Opcode.CHECKCAST, info);
    }

    public void instanceOf(TypeDesc type) {
        ConstantInfo info = mCp.addConstantClass(type);
        addInstruction(0, TypeDesc.INT, Opcode.INSTANCEOF, info);
    }

    public void integerIncrement(LocalVariable local, int amount) {
        if (local == null) {
            throw new IllegalArgumentException("No local variable specified");
        }

        if (-32768 <= amount && amount <= 32767) {
            mInstructions.new ShortIncrementInstruction(local, (short)amount);
        } else {
            // Amount can't possibly fit in a 16-bit value, so use regular
            // instructions instead.

            loadLocal(local);
            loadConstant(amount);
            math(Opcode.IADD);
            storeLocal(local);
        }
    }

    public void monitorEnter() {
        addInstruction(-1, TypeDesc.VOID, Opcode.MONITORENTER);
    }

    public void monitorExit() {
        addInstruction(-1, TypeDesc.VOID, Opcode.MONITOREXIT);
    }

    public void nop() {
        addInstruction(0, TypeDesc.VOID, Opcode.NOP);
    }

    public void breakpoint() {
        addInstruction(0, TypeDesc.VOID, Opcode.BREAKPOINT);
    }
}
TOP

Related Classes of org.cojen.classfile.CodeBuilder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.