/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */

#include <sal/config.h>

#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <map>
#include <memory>
#include <set>
#include <utility>
#include <vector>

#include <codemaker/codemaker.hxx>
#include <codemaker/exceptiontree.hxx>
#include <codemaker/generatedtypeset.hxx>
#include <codemaker/global.hxx>
#include <codemaker/options.hxx>
#include <codemaker/typemanager.hxx>
#include <codemaker/unotype.hxx>
#include <codemaker/commonjava.hxx>
#include <rtl/ref.hxx>
#include <rtl/strbuf.hxx>
#include <rtl/string.hxx>
#include <rtl/ustrbuf.hxx>
#include <rtl/ustring.hxx>
#include <sal/types.h>
#include <unoidl/unoidl.hxx>

#include "classfile.hxx"
#include "javaoptions.hxx"
#include "javatype.hxx"

using codemaker::javamaker::ClassFile;

namespace {

void appendUnoName(
    rtl::Reference< TypeManager > const & manager, OUString const & nucleus,
    sal_Int32 rank, std::vector< OUString > const & arguments,
    OUStringBuffer * buffer)
{
    assert(manager.is());
    assert(rank >= 0);
    assert(buffer != nullptr);
    for (sal_Int32 i = 0; i != rank; ++i) {
        buffer->append("[]");
    }
    buffer->append(nucleus);
    if (!arguments.empty()) {
        buffer->append('<');
        for (std::vector< OUString >::const_iterator i(arguments.begin());
             i != arguments.end(); ++i)
        {
            if (i != arguments.begin()) {
                buffer->append(',');
            }
            OUString n;
            sal_Int32 k;
            std::vector< OUString > args;
            manager->decompose(*i, false, &n, &k, &args, nullptr);
            appendUnoName(manager, n, k, args, buffer);
        }
        buffer->append('>');
    }
}

// Translate the name of a UNOIDL entity (enum type, plain struct type,
// polymorphic struct type template, or interface type, decomposed into nucleus,
// sequence rank, and template arguments) into a core UNO type name:
OUString createUnoName(
    rtl::Reference< TypeManager > const & manager, OUString const & nucleus,
    sal_Int32 rank, std::vector< OUString > const & arguments)
{
    OUStringBuffer buf;
    appendUnoName(manager, nucleus, rank, arguments, &buf);
    return buf.makeStringAndClear();
}

enum SpecialType {
    SPECIAL_TYPE_NONE,
    SPECIAL_TYPE_ANY,
    SPECIAL_TYPE_UNSIGNED,
    SPECIAL_TYPE_INTERFACE
};

bool isSpecialType(SpecialType special) {
    return special >= SPECIAL_TYPE_UNSIGNED;
}

OString translateUnoidlEntityNameToJavaFullyQualifiedName(
    OUString const & name, OString const & prefix)
{
    assert(!name.startsWith("[]"));
    assert(name.indexOf('<') == -1);
    sal_Int32 i = name.lastIndexOf('.') + 1;
    return codemaker::convertString(name.copy(0, i)).replace('.', '/')
        + codemaker::java::translateUnoToJavaIdentifier(
            codemaker::convertString(name.copy(i)), prefix);
}

struct PolymorphicUnoType {
    PolymorphicUnoType(): kind(KIND_NONE) {}

    enum Kind { KIND_NONE, KIND_STRUCT, KIND_SEQUENCE };
    Kind kind;
    OUString name;
};

SpecialType translateUnoTypeToDescriptor(
    rtl::Reference< TypeManager > const & manager, OUString const & type,
    bool array, bool classType, std::set<OUString> * dependencies,
    OStringBuffer * descriptor, OStringBuffer * signature,
    bool * needsSignature, PolymorphicUnoType * polymorphicUnoType);

SpecialType translateUnoTypeToDescriptor(
    rtl::Reference< TypeManager > const & manager,
    codemaker::UnoType::Sort sort, OUString const & nucleus, sal_Int32 rank,
    std::vector< OUString > const & arguments, bool array, bool classType,
    std::set<OUString> * dependencies, OStringBuffer * descriptor,
    OStringBuffer * signature, bool * needsSignature,
    PolymorphicUnoType * polymorphicUnoType)
{
    assert(rank >= 0);
    assert((signature == nullptr) == (needsSignature == nullptr));
    assert(
        arguments.empty()
        == (sort
            != codemaker::UnoType::Sort::InstantiatedPolymorphicStruct));
    if (rank > 0xFF - (array ? 1 : 0)) {
        throw CannotDumpException(
            "Too many array dimensions for Java class file format");
    }
    if (array) {
        ++rank;
    }
    for (sal_Int32 i = 0; i != rank; ++i) {
        if (descriptor != nullptr) {
            descriptor->append('[');
        }
        if (signature != nullptr) {
            signature->append('[');
        }
    }
    if (polymorphicUnoType != nullptr) {
        if (sort
            == codemaker::UnoType::Sort::InstantiatedPolymorphicStruct)
        {
            polymorphicUnoType->kind = rank == 0
                ? PolymorphicUnoType::KIND_STRUCT
                : PolymorphicUnoType::KIND_SEQUENCE;
            polymorphicUnoType->name = createUnoName(
                manager, nucleus, rank, arguments);
        } else {
            polymorphicUnoType->kind = PolymorphicUnoType::KIND_NONE;
        }
    }
    switch (sort) {
    case codemaker::UnoType::Sort::Void:
    case codemaker::UnoType::Sort::Boolean:
    case codemaker::UnoType::Sort::Byte:
    case codemaker::UnoType::Sort::Short:
    case codemaker::UnoType::Sort::UnsignedShort:
    case codemaker::UnoType::Sort::Long:
    case codemaker::UnoType::Sort::UnsignedLong:
    case codemaker::UnoType::Sort::Hyper:
    case codemaker::UnoType::Sort::UnsignedHyper:
    case codemaker::UnoType::Sort::Float:
    case codemaker::UnoType::Sort::Double:
    case codemaker::UnoType::Sort::Char:
    case codemaker::UnoType::Sort::String:
    case codemaker::UnoType::Sort::Type:
    case codemaker::UnoType::Sort::Any:
        {
            static char const * const
                simpleTypeDescriptors[static_cast<int>(codemaker::UnoType::Sort::Any) + 1][2] = {
                { "V", "Ljava/lang/Void;" },
                { "Z", "Ljava/lang/Boolean;" },
                { "B", "Ljava/lang/Byte;" },
                { "S", "Ljava/lang/Short;" },
                { "S", "Ljava/lang/Short;" },
                { "I", "Ljava/lang/Integer;" },
                { "I", "Ljava/lang/Integer;" },
                { "J", "Ljava/lang/Long;" },
                { "J", "Ljava/lang/Long;" },
                { "F", "Ljava/lang/Float;" },
                { "D", "Ljava/lang/Double;" },
                { "C", "Ljava/lang/Character;" },
                { "Ljava/lang/String;", "Ljava/lang/String;" },
                { "Lcom/sun/star/uno/Type;", "Lcom/sun/star/uno/Type;" },
                { "Ljava/lang/Object;", "Ljava/lang/Object;" } };
            char const * s
                = simpleTypeDescriptors[static_cast<int>(sort)][rank == 0 && classType];
            if (descriptor != nullptr) {
                descriptor->append(s);
            }
            if (signature != nullptr) {
                signature->append(s);
            }
            static SpecialType const
                simpleTypeSpecials[static_cast<int>(codemaker::UnoType::Sort::Any) + 1] = {
                SPECIAL_TYPE_NONE, SPECIAL_TYPE_NONE, SPECIAL_TYPE_NONE,
                SPECIAL_TYPE_NONE, SPECIAL_TYPE_UNSIGNED, SPECIAL_TYPE_NONE,
                SPECIAL_TYPE_UNSIGNED, SPECIAL_TYPE_NONE, SPECIAL_TYPE_UNSIGNED,
                SPECIAL_TYPE_NONE, SPECIAL_TYPE_NONE, SPECIAL_TYPE_NONE,
                SPECIAL_TYPE_NONE, SPECIAL_TYPE_NONE, SPECIAL_TYPE_ANY };
            return simpleTypeSpecials[static_cast<int>(sort)];
        }
    case codemaker::UnoType::Sort::Interface:
        if (nucleus == "com.sun.star.uno.XInterface") {
            if (descriptor != nullptr) {
                descriptor->append("Ljava/lang/Object;");
            }
            if (signature != nullptr) {
                signature->append("Ljava/lang/Object;");
            }
            return SPECIAL_TYPE_INTERFACE;
        }
        SAL_FALLTHROUGH;
    case codemaker::UnoType::Sort::Sequence:
    case codemaker::UnoType::Sort::Enum:
    case codemaker::UnoType::Sort::PlainStruct:
    case codemaker::UnoType::Sort::InstantiatedPolymorphicStruct:
        if (dependencies != nullptr) {
            dependencies->insert(nucleus);
        }
        if (descriptor != nullptr) {
            descriptor->append(
                "L" + codemaker::convertString(nucleus).replace('.', '/')
                + ";");
        }
        if (signature != nullptr) {
            signature->append(
                "L" + codemaker::convertString(nucleus).replace('.', '/'));
            if (!arguments.empty()) {
                signature->append('<');
                for (const OUString& arg : arguments)
                {
                    translateUnoTypeToDescriptor(
                        manager, arg, false, true, dependencies, nullptr, signature,
                        needsSignature, nullptr);
                }
                signature->append('>');
                *needsSignature = true;
            }
            signature->append(';');
        }
        return SPECIAL_TYPE_NONE;
    default:
        throw CannotDumpException(
            "unexpected nucleus \"" + nucleus
            + "\" in call to translateUnoTypeToDescriptor");
    }
}

SpecialType translateUnoTypeToDescriptor(
    rtl::Reference< TypeManager > const & manager, OUString const & type,
    bool array, bool classType, std::set<OUString> * dependencies,
    OStringBuffer * descriptor, OStringBuffer * signature,
    bool * needsSignature, PolymorphicUnoType * polymorphicUnoType)
{
    assert(manager.is());
    OUString nucleus;
    sal_Int32 rank;
    std::vector< OUString > args;
    codemaker::UnoType::Sort sort = manager->decompose(
        type, true, &nucleus, &rank, &args, nullptr);
    return translateUnoTypeToDescriptor(
        manager, sort, nucleus, rank, args, array, classType, dependencies,
        descriptor, signature, needsSignature, polymorphicUnoType);
}

SpecialType getFieldDescriptor(
    rtl::Reference< TypeManager > const & manager, std::set<OUString> * dependencies,
    OUString const & type, OString * descriptor, OString * signature,
    PolymorphicUnoType * polymorphicUnoType)
{
    assert(descriptor != nullptr);
    OStringBuffer desc;
    OStringBuffer sig;
    bool needsSig = false;
    SpecialType specialType = translateUnoTypeToDescriptor(
        manager, type, false, false, dependencies, &desc, &sig, &needsSig,
        polymorphicUnoType);
    *descriptor = desc.makeStringAndClear();
    if (signature != nullptr) {
        if (needsSig) {
            *signature = sig.makeStringAndClear();
        } else {
            signature->clear();
        }
    }
    return specialType;
}

class MethodDescriptor {
public:
    MethodDescriptor(
        rtl::Reference< TypeManager > const & manager,
        std::set<OUString> * dependencies, OUString const & returnType,
        SpecialType * specialReturnType,
        PolymorphicUnoType * polymorphicUnoType);

    SpecialType addParameter(
        OUString const & type, bool array, bool dependency,
        PolymorphicUnoType * polymorphicUnoType);

    void addTypeParameter(OUString const & name);

    OString getDescriptor() const;

    OString getSignature() const { return m_needsSignature ? m_signatureStart.toString() + m_signatureEnd : OString();}

private:
    rtl::Reference< TypeManager > m_manager;
    std::set<OUString> * m_dependencies;
    OStringBuffer m_descriptorStart;
    OString m_descriptorEnd;
    OStringBuffer m_signatureStart;
    OString m_signatureEnd;
    bool m_needsSignature;
};

MethodDescriptor::MethodDescriptor(
    rtl::Reference< TypeManager > const & manager, std::set<OUString> * dependencies,
    OUString const & returnType, SpecialType * specialReturnType,
    PolymorphicUnoType * polymorphicUnoType):
    m_manager(manager), m_dependencies(dependencies), m_needsSignature(false)
{
    assert(dependencies != nullptr);
    m_descriptorStart.append('(');
    m_signatureStart.append('(');
    OStringBuffer descEnd;
    descEnd.append(')');
    OStringBuffer sigEnd;
    sigEnd.append(')');
    SpecialType special = translateUnoTypeToDescriptor(
        m_manager, returnType, false, false, m_dependencies, &descEnd, &sigEnd,
        &m_needsSignature, polymorphicUnoType);
    m_descriptorEnd = descEnd.makeStringAndClear();
    m_signatureEnd = sigEnd.makeStringAndClear();
    if (specialReturnType != nullptr) {
        *specialReturnType = special;
    }
}

SpecialType MethodDescriptor::addParameter(
    OUString const & type, bool array, bool dependency,
    PolymorphicUnoType * polymorphicUnoType)
{
    return translateUnoTypeToDescriptor(
        m_manager, type, array, false, dependency ? m_dependencies : nullptr,
        &m_descriptorStart, &m_signatureStart, &m_needsSignature,
        polymorphicUnoType);
}

void MethodDescriptor::addTypeParameter(OUString const & name) {
    m_descriptorStart.append("Ljava/lang/Object;");
    m_signatureStart.append("T" + codemaker::convertString(name) + ";");
    m_needsSignature = true;
}

OString MethodDescriptor::getDescriptor() const {
    OStringBuffer buf(m_descriptorStart);
    buf.append(m_descriptorEnd);
    return buf.makeStringAndClear();
}


class TypeInfo {
public:
    enum Kind { KIND_MEMBER, KIND_ATTRIBUTE, KIND_METHOD, KIND_PARAMETER };

    // Same values as in com/sun/star/lib/uno/typeinfo/TypeInfo.java:
    enum Flags {
        FLAG_READONLY = 0x008, FLAG_BOUND = 0x100
    };

    // KIND_MEMBER:
    TypeInfo(
        OString const & name, SpecialType specialType, sal_Int32 index,
        PolymorphicUnoType const & polymorphicUnoType,
        sal_Int32 typeParameterIndex);

    // KIND_ATTRIBUTE/METHOD:
    TypeInfo(
        Kind kind, OString const & name, SpecialType specialType, Flags flags,
        sal_Int32 index, PolymorphicUnoType const & polymorphicUnoType);

    // KIND_PARAMETER:
    TypeInfo(
        OString const & parameterName, SpecialType specialType,
        bool inParameter, bool outParameter, OString const & methodName,
        sal_Int32 index, PolymorphicUnoType const & polymorphicUnoType);

    sal_uInt16 generateCode(ClassFile::Code & code, std::set<OUString> * dependencies)
        const;

    void generatePolymorphicUnoTypeCode(
        ClassFile::Code & code, std::set<OUString> * dependencies) const;

private:
    Kind m_kind;
    OString m_name;
    sal_Int32 m_flags;
    sal_Int32 m_index;
    OString m_methodName;
    PolymorphicUnoType m_polymorphicUnoType;
    sal_Int32 m_typeParameterIndex;
};

sal_Int32 translateSpecialTypeFlags(
    SpecialType specialType, bool inParameter, bool outParameter)
{
    static sal_Int32 const specialTypeFlags[SPECIAL_TYPE_INTERFACE + 1] = {
        0, 0x0040 /* ANY */, 0x0004 /* UNSIGNED */, 0x0080 /* INTERFACE */ };
    sal_Int32 flags = specialTypeFlags[specialType];
    if (inParameter) {
        flags |= 0x0001; /* IN */
    }
    if (outParameter) {
        flags |= 0x0002; /* OUT */
    }
    return flags;
}

TypeInfo::TypeInfo(
    OString const & name, SpecialType specialType, sal_Int32 index,
    PolymorphicUnoType const & polymorphicUnoType,
    sal_Int32 typeParameterIndex):
    m_kind(KIND_MEMBER), m_name(name),
    m_flags(translateSpecialTypeFlags(specialType, false, false)),
    m_index(index), m_polymorphicUnoType(polymorphicUnoType),
    m_typeParameterIndex(typeParameterIndex)
{
    assert(
        polymorphicUnoType.kind == PolymorphicUnoType::KIND_NONE
        ? typeParameterIndex >= -1 : typeParameterIndex == -1);
}

TypeInfo::TypeInfo(
    Kind kind, OString const & name, SpecialType specialType, Flags flags,
    sal_Int32 index, PolymorphicUnoType const & polymorphicUnoType):
    m_kind(kind), m_name(name),
    m_flags(flags | translateSpecialTypeFlags(specialType, false, false)),
    m_index(index), m_polymorphicUnoType(polymorphicUnoType),
    m_typeParameterIndex(0)
{
    assert(kind == KIND_ATTRIBUTE || kind == KIND_METHOD);
}

TypeInfo::TypeInfo(
    OString const & parameterName, SpecialType specialType, bool inParameter,
    bool outParameter, OString const & methodName, sal_Int32 index,
    PolymorphicUnoType const & polymorphicUnoType):
    m_kind(KIND_PARAMETER), m_name(parameterName),
    m_flags(translateSpecialTypeFlags(specialType, inParameter, outParameter)),
    m_index(index), m_methodName(methodName),
    m_polymorphicUnoType(polymorphicUnoType),
    m_typeParameterIndex(0)
{}

sal_uInt16 TypeInfo::generateCode(
    ClassFile::Code & code, std::set<OUString> * dependencies) const
{
    switch (m_kind) {
    case KIND_MEMBER:
        code.instrNew("com/sun/star/lib/uno/typeinfo/MemberTypeInfo");
        code.instrDup();
        code.loadStringConstant(m_name);
        code.loadIntegerConstant(m_index);
        code.loadIntegerConstant(m_flags);
        if (m_polymorphicUnoType.kind != PolymorphicUnoType::KIND_NONE) {
            generatePolymorphicUnoTypeCode(code, dependencies);
            code.loadIntegerConstant(m_typeParameterIndex);
            code.instrInvokespecial(
                "com/sun/star/lib/uno/typeinfo/MemberTypeInfo", "<init>",
                "(Ljava/lang/String;IILcom/sun/star/uno/Type;I)V");
            return 8;
        } else if (m_typeParameterIndex >= 0) {
            code.instrAconstNull();
            code.loadIntegerConstant(m_typeParameterIndex);
            code.instrInvokespecial(
                "com/sun/star/lib/uno/typeinfo/MemberTypeInfo", "<init>",
                "(Ljava/lang/String;IILcom/sun/star/uno/Type;I)V");
            return 6;
        } else {
            code.instrInvokespecial(
                "com/sun/star/lib/uno/typeinfo/MemberTypeInfo", "<init>",
                "(Ljava/lang/String;II)V");
            return 4;
        }
    case KIND_ATTRIBUTE:
        code.instrNew("com/sun/star/lib/uno/typeinfo/AttributeTypeInfo");
        code.instrDup();
        code.loadStringConstant(m_name);
        code.loadIntegerConstant(m_index);
        code.loadIntegerConstant(m_flags);
        if (m_polymorphicUnoType.kind != PolymorphicUnoType::KIND_NONE) {
            generatePolymorphicUnoTypeCode(code, dependencies);
            code.instrInvokespecial(
                "com/sun/star/lib/uno/typeinfo/AttributeTypeInfo", "<init>",
                "(Ljava/lang/String;IILcom/sun/star/uno/Type;)V");
            return 8;
        } else {
            code.instrInvokespecial(
                "com/sun/star/lib/uno/typeinfo/AttributeTypeInfo", "<init>",
                "(Ljava/lang/String;II)V");
            return 4;
        }
    case KIND_METHOD:
        code.instrNew("com/sun/star/lib/uno/typeinfo/MethodTypeInfo");
        code.instrDup();
        code.loadStringConstant(m_name);
        code.loadIntegerConstant(m_index);
        code.loadIntegerConstant(m_flags);
        if (m_polymorphicUnoType.kind != PolymorphicUnoType::KIND_NONE) {
            generatePolymorphicUnoTypeCode(code, dependencies);
            code.instrInvokespecial(
                "com/sun/star/lib/uno/typeinfo/MethodTypeInfo", "<init>",
                "(Ljava/lang/String;IILcom/sun/star/uno/Type;)V");
            return 8;
        } else {
            code.instrInvokespecial(
                "com/sun/star/lib/uno/typeinfo/MethodTypeInfo", "<init>",
                "(Ljava/lang/String;II)V");
            return 4;
        }
    case KIND_PARAMETER:
        code.instrNew("com/sun/star/lib/uno/typeinfo/ParameterTypeInfo");
        code.instrDup();
        code.loadStringConstant(m_name);
        code.loadStringConstant(m_methodName);
        code.loadIntegerConstant(m_index);
        code.loadIntegerConstant(m_flags);
        if (m_polymorphicUnoType.kind != PolymorphicUnoType::KIND_NONE) {
            generatePolymorphicUnoTypeCode(code, dependencies);
            code.instrInvokespecial(
                "com/sun/star/lib/uno/typeinfo/ParameterTypeInfo", "<init>",
                ("(Ljava/lang/String;Ljava/lang/String;II"
                 "Lcom/sun/star/uno/Type;)V"));
            return 9;
        } else {
            code.instrInvokespecial(
                "com/sun/star/lib/uno/typeinfo/ParameterTypeInfo", "<init>",
                "(Ljava/lang/String;Ljava/lang/String;II)V");
            return 5;
        }
    default:
        assert(false);
        return 0;
    }
}

void TypeInfo::generatePolymorphicUnoTypeCode(
    ClassFile::Code & code, std::set<OUString> * dependencies) const
{
    assert(dependencies != nullptr);
    assert(m_polymorphicUnoType.kind != PolymorphicUnoType::KIND_NONE);
    code.instrNew("com/sun/star/uno/Type");
    code.instrDup();
    code.loadStringConstant(
        codemaker::convertString(m_polymorphicUnoType.name));
    if (m_polymorphicUnoType.kind == PolymorphicUnoType::KIND_STRUCT) {
        code.instrGetstatic(
            "com/sun/star/uno/TypeClass", "STRUCT",
            "Lcom/sun/star/uno/TypeClass;");
    } else {
        code.instrGetstatic(
            "com/sun/star/uno/TypeClass", "SEQUENCE",
            "Lcom/sun/star/uno/TypeClass;");
    }
    dependencies->insert("com.sun.star.uno.TypeClass");
    code.instrInvokespecial(
        "com/sun/star/uno/Type", "<init>",
        "(Ljava/lang/String;Lcom/sun/star/uno/TypeClass;)V");
}

void writeClassFile(
    JavaOptions const & options, OString const & type,
    ClassFile const & classFile)
{
    OString path;
    if (options.isValid("-O")) {
        path = options.getOption("-O");
    }
    OString filename(createFileNameFromType(path, type, ".class"));
    bool bCheck = false;
    if (fileExists(filename)) {
        if (options.isValid("-G")) {
            return;
        }
        bCheck = options.isValid("-Gc");
    }
    FileStream tempfile;
    tempfile.createTempFile(getTempDir(filename));
    if (!tempfile.isValid()) {
        throw CannotDumpException(
            "Cannot create temporary file for " + b2u(filename));
    }
    OString tempname(tempfile.getName());
    try {
        classFile.write(tempfile);
    } catch (...) {
        // Remove existing file for consistency:
        if (fileExists(filename)) {
            removeTypeFile(filename);
        }
        tempfile.close();
        removeTypeFile(tempname);
        throw;
    }
    tempfile.close();
    if (!makeValidTypeFile(filename, tempname, bCheck)) {
        throw CannotDumpException(
            "Cannot create " + b2u(filename) + " from temporary file "
            + b2u(tempname));
    }
}

void addTypeInfo(
    OString const & className, std::vector< TypeInfo > const & typeInfo,
    std::set<OUString> * dependencies, ClassFile * classFile)
{
    assert(classFile != nullptr);
    std::vector< TypeInfo >::size_type typeInfos = typeInfo.size();
    if (typeInfos > SAL_MAX_INT32) {
        throw CannotDumpException(
            "UNOTYPEINFO array too big for Java class file format");
    }
    if (typeInfos != 0) {
        classFile->addField(
            static_cast< ClassFile::AccessFlags >(
                ClassFile::ACC_PUBLIC | ClassFile::ACC_STATIC
                | ClassFile::ACC_FINAL),
            "UNOTYPEINFO", "[Lcom/sun/star/lib/uno/typeinfo/TypeInfo;",
            0, "");
        std::unique_ptr< ClassFile::Code > code(classFile->newCode());
        code->loadIntegerConstant(static_cast< sal_Int32 >(typeInfos));
        code->instrAnewarray("com/sun/star/lib/uno/typeinfo/TypeInfo");
        sal_Int32 index = 0;
        sal_uInt16 stack = 0;
        for (const TypeInfo& ti : typeInfo)
        {
            code->instrDup();
            code->loadIntegerConstant(index++);
            stack = std::max(stack, ti.generateCode(*code, dependencies));
            code->instrAastore();
        }
        code->instrPutstatic(
            className, "UNOTYPEINFO",
            "[Lcom/sun/star/lib/uno/typeinfo/TypeInfo;");
        code->instrReturn();
        if (stack > SAL_MAX_UINT16 - 4) {
            throw CannotDumpException(
                "Stack too big for Java class file format");
        }
        code->setMaxStackAndLocals(static_cast< sal_uInt16 >(stack + 4), 0);
        classFile->addMethod(
            static_cast< ClassFile::AccessFlags >(
                ClassFile::ACC_PRIVATE | ClassFile::ACC_STATIC),
            "<clinit>", "()V", code.get(), std::vector< OString >(), "");
    }
}

void handleEnumType(
    const OUString& name, rtl::Reference< unoidl::EnumTypeEntity > const & entity,
    JavaOptions const & options)
{
    assert(entity.is());
    OString className(codemaker::convertString(name).replace('.', '/'));
    std::unique_ptr< ClassFile > cf(
        new ClassFile(
            static_cast< ClassFile::AccessFlags >(
                ClassFile::ACC_PUBLIC | ClassFile::ACC_FINAL
                | ClassFile::ACC_SUPER),
            className, "com/sun/star/uno/Enum", ""));
    OString classDescriptor("L" + className + ";");
    for (const unoidl::EnumTypeEntity::Member& member : entity->getMembers())
    {
        OString fieldName(codemaker::convertString(member.name));
        cf->addField(
            static_cast< ClassFile::AccessFlags >(
                ClassFile::ACC_PUBLIC | ClassFile::ACC_STATIC
                | ClassFile::ACC_FINAL),
            fieldName, classDescriptor, 0, OString());
        cf->addField(
            static_cast< ClassFile::AccessFlags >(
                ClassFile::ACC_PUBLIC | ClassFile::ACC_STATIC
                | ClassFile::ACC_FINAL),
            fieldName + "_value", "I",
            cf->addIntegerInfo(member.value), "");
    }
    std::unique_ptr< ClassFile::Code > code(cf->newCode());
    code->loadLocalReference(0);
    code->loadLocalInteger(1);
    code->instrInvokespecial("com/sun/star/uno/Enum", "<init>", "(I)V");
    code->instrReturn();
    code->setMaxStackAndLocals(2, 2);
    cf->addMethod(
        ClassFile::ACC_PRIVATE,
        "<init>", "(I)V", code.get(),
        std::vector< OString >(), "");
    code.reset(cf->newCode());
    code->instrGetstatic(
        className,
        codemaker::convertString(entity->getMembers()[0].name),
        classDescriptor);
    code->instrAreturn();
    code->setMaxStackAndLocals(1, 0);
    cf->addMethod(
        static_cast< ClassFile::AccessFlags >(
            ClassFile::ACC_PUBLIC | ClassFile::ACC_STATIC),
        "getDefault", "()" + classDescriptor,
        code.get(), std::vector< OString >(), "");
    code.reset(cf->newCode());
    code->loadLocalInteger(0);
    std::map< sal_Int32, OString > map;
    sal_Int32 min = SAL_MAX_INT32;
    sal_Int32 max = SAL_MIN_INT32;
    for (const unoidl::EnumTypeEntity::Member& member : entity->getMembers())
    {
        min = std::min(min, member.value);
        max = std::max(max, member.value);
        map.emplace(member.value, codemaker::convertString(member.name));
    }
    sal_uInt64 size = static_cast< sal_uInt64 >(map.size());
    if ((static_cast< sal_uInt64 >(max) - static_cast< sal_uInt64 >(min)
         <= 2 * size)
        || size > SAL_MAX_INT32)
    {
        std::unique_ptr< ClassFile::Code > defCode(cf->newCode());
        defCode->instrAconstNull();
        defCode->instrAreturn();
        std::vector< std::unique_ptr<ClassFile::Code> > blocks;
            //FIXME: pointers contained in blocks may leak
        sal_Int32 last = SAL_MAX_INT32;
        for (const auto& pair : map)
        {
            sal_Int32 value = pair.first;
            if (last != SAL_MAX_INT32) {
                for (sal_Int32 j = last + 1; j < value; ++j) {
                    blocks.push_back(nullptr);
                }
            }
            last = value;
            std::unique_ptr< ClassFile::Code > blockCode(cf->newCode());
            blockCode->instrGetstatic(className, pair.second, classDescriptor);
            blockCode->instrAreturn();
            blocks.push_back(std::move(blockCode));
        }
        code->instrTableswitch(defCode.get(), min, blocks);
    } else{
        std::unique_ptr< ClassFile::Code > defCode(cf->newCode());
        defCode->instrAconstNull();
        defCode->instrAreturn();
        std::vector< std::pair< sal_Int32, ClassFile::Code * > > blocks;
            //FIXME: pointers contained in blocks may leak
        for (const auto& pair : map )
        {
            std::unique_ptr< ClassFile::Code > blockCode(cf->newCode());
            blockCode->instrGetstatic(className, pair.second, classDescriptor);
            blockCode->instrAreturn();
            blocks.emplace_back(pair.first, blockCode.get());
            blockCode.release();
        }
        code->instrLookupswitch(defCode.get(), blocks);
        for (const std::pair< sal_Int32, ClassFile::Code * >& pair : blocks)
        {
            delete pair.second;
        }
    }
    code->setMaxStackAndLocals(1, 1);
    cf->addMethod(
        static_cast< ClassFile::AccessFlags >(
            ClassFile::ACC_PUBLIC | ClassFile::ACC_STATIC),
        "fromInt", "(I)" + classDescriptor, code.get(),
        std::vector< OString >(), "");
    code.reset(cf->newCode());
    for (const unoidl::EnumTypeEntity::Member& member : entity->getMembers())
    {
        code->instrNew(className);
        code->instrDup();
        code->loadIntegerConstant(member.value);
        code->instrInvokespecial(className, "<init>", "(I)V");
        code->instrPutstatic(
            className, codemaker::convertString(member.name), classDescriptor);
    }
    code->instrReturn();
    code->setMaxStackAndLocals(3, 0);
    cf->addMethod(
        static_cast< ClassFile::AccessFlags >(
            ClassFile::ACC_PRIVATE | ClassFile::ACC_STATIC),
        "<clinit>", "()V", code.get(), std::vector< OString >(), "");
    writeClassFile(options, className, *cf);
}

void addField(
    rtl::Reference< TypeManager > const & manager, std::set<OUString> * dependencies,
    ClassFile * classFile, std::vector< TypeInfo > * typeInfo,
    sal_Int32 typeParameterIndex, OUString const & type, OUString const & name,
    sal_Int32 index)
{
    assert(classFile != nullptr);
    assert(typeInfo != nullptr);
    OString descriptor;
    OString signature;
    SpecialType specialType;
    PolymorphicUnoType polymorphicUnoType;
    if (typeParameterIndex >= 0) {
        descriptor = "Ljava/lang/Object;";
        signature = "T" + codemaker::convertString(type).replace('.', '/')
            + ";";
        specialType = SPECIAL_TYPE_NONE; //TODO: SPECIAL_TYPE_TYPE_PARAMETER?
    } else {
        specialType = getFieldDescriptor(
            manager, dependencies, type, &descriptor, &signature,
            &polymorphicUnoType);
    }
    classFile->addField(
        ClassFile::ACC_PUBLIC, codemaker::convertString(name), descriptor, 0,
        signature);
    typeInfo->push_back(
        TypeInfo(
            codemaker::convertString(name), specialType, index,
            polymorphicUnoType, typeParameterIndex));
}

sal_uInt16 addFieldInit(
    rtl::Reference< TypeManager > const & manager, OString const & className,
    OUString const & fieldName, bool typeParameter, OUString const & fieldType,
    std::set<OUString> * dependencies, ClassFile::Code * code)
{
    assert(manager.is());
    assert(code != nullptr);
    if (typeParameter) {
        return 0;
    }
    OString name(codemaker::convertString(fieldName));
    OUString nucleus;
    sal_Int32 rank;
    std::vector< OUString > args;
    rtl::Reference< unoidl::Entity > ent;
    codemaker::UnoType::Sort sort = manager->decompose(
        fieldType, true, &nucleus, &rank, &args, &ent);
    if (rank == 0) {
        switch (sort) {
        case codemaker::UnoType::Sort::Boolean:
        case codemaker::UnoType::Sort::Byte:
        case codemaker::UnoType::Sort::Short:
        case codemaker::UnoType::Sort::UnsignedShort:
        case codemaker::UnoType::Sort::Long:
        case codemaker::UnoType::Sort::UnsignedLong:
        case codemaker::UnoType::Sort::Hyper:
        case codemaker::UnoType::Sort::UnsignedHyper:
        case codemaker::UnoType::Sort::Float:
        case codemaker::UnoType::Sort::Double:
        case codemaker::UnoType::Sort::Char:
        case codemaker::UnoType::Sort::Interface:
            return 0;
        case codemaker::UnoType::Sort::String:
            code->loadLocalReference(0);
            code->loadStringConstant(OString());
            code->instrPutfield(className, name, "Ljava/lang/String;");
            return 2;
        case codemaker::UnoType::Sort::Type:
            code->loadLocalReference(0);
            code->instrGetstatic(
                "com/sun/star/uno/Type", "VOID", "Lcom/sun/star/uno/Type;");
            code->instrPutfield(className, name, "Lcom/sun/star/uno/Type;");
            return 2;
        case codemaker::UnoType::Sort::Any:
            code->loadLocalReference(0);
            code->instrGetstatic(
                "com/sun/star/uno/Any", "VOID", "Lcom/sun/star/uno/Any;");
            code->instrPutfield(className, name, "Ljava/lang/Object;");
            return 2;
        case codemaker::UnoType::Sort::Enum:
            {
                rtl::Reference< unoidl::EnumTypeEntity > ent2(
                    dynamic_cast< unoidl::EnumTypeEntity * >(ent.get()));
                assert(ent2.is());
                code->loadLocalReference(0);
                OStringBuffer descBuf;
                translateUnoTypeToDescriptor(
                    manager, sort, nucleus, 0, std::vector< OUString >(), false,
                    false, dependencies, &descBuf, nullptr, nullptr, nullptr);
                OString desc(descBuf.makeStringAndClear());
                code->instrGetstatic(
                    codemaker::convertString(nucleus).replace('.', '/'),
                    codemaker::convertString(ent2->getMembers()[0].name), desc);
                code->instrPutfield(className, name, desc);
                return 2;
            }
        case codemaker::UnoType::Sort::PlainStruct:
        case codemaker::UnoType::Sort::InstantiatedPolymorphicStruct:
            {
                code->loadLocalReference(0);
                code->instrNew(
                    codemaker::convertString(nucleus).replace('.', '/'));
                code->instrDup();
                code->instrInvokespecial(
                    codemaker::convertString(nucleus).replace('.', '/'),
                    "<init>", "()V");
                OStringBuffer desc;
                translateUnoTypeToDescriptor(
                    manager, sort, nucleus, 0, args, false, false, dependencies,
                    &desc, nullptr, nullptr, nullptr);
                code->instrPutfield(className, name, desc.makeStringAndClear());
                return 3;
            }
        case codemaker::UnoType::Sort::Sequence:
        case codemaker::UnoType::Sort::Typedef:
            for (;;) std::abort(); // this cannot happen
        default:
            throw CannotDumpException(
                "unexpected entity \"" + fieldType
                + "\" in call to addFieldInit");
        }
    }
    code->loadLocalReference(0);
    code->loadIntegerConstant(0);
    if (rank == 1) {
        if (sort >= codemaker::UnoType::Sort::Boolean
            && sort <= codemaker::UnoType::Sort::Char)
        {
            code->instrNewarray(sort);
        } else {
            code->instrAnewarray(
                codemaker::java::translateUnoToJavaType(
                    sort, codemaker::convertString(nucleus).replace('.', '/'),
                    false));
        }
    } else {
        OStringBuffer desc;
        translateUnoTypeToDescriptor(
            manager, sort, nucleus, rank - 1, std::vector< OUString >(), false,
            false, dependencies, &desc, nullptr, nullptr, nullptr);
        code->instrAnewarray(desc.makeStringAndClear());
    }
    OStringBuffer desc;
    translateUnoTypeToDescriptor(
        manager, sort, nucleus, rank, std::vector< OUString >(), false, false,
        dependencies, &desc, nullptr, nullptr, nullptr);
    code->instrPutfield(className, name, desc.makeStringAndClear());
    return 2;
}

sal_uInt16 addLoadLocal(
    rtl::Reference< TypeManager > const & manager, ClassFile::Code * code,
    sal_uInt16 * index, bool typeParameter, OUString const & type, bool any,
    std::set<OUString> * dependencies)
{
    assert(manager.is());
    assert(code != nullptr);
    assert(index != nullptr);
    assert(!(typeParameter && any));
    assert(dependencies != nullptr);
    sal_uInt16 stack = 1;
    sal_uInt16 size = 1;
    if (typeParameter) {
        code->loadLocalReference(*index);
        stack = size = 1;
    } else {
        OUString nucleus;
        sal_Int32 rank;
        std::vector< OUString > args;
        codemaker::UnoType::Sort sort = manager->decompose(
            type, true, &nucleus, &rank, &args, nullptr);
        if (rank == 0) {
            switch (sort) {
            case codemaker::UnoType::Sort::Boolean:
                if (any) {
                    code->instrNew("java/lang/Boolean");
                    code->instrDup();
                    code->loadLocalInteger(*index);
                    code->instrInvokespecial(
                        "java/lang/Boolean", "<init>", "(Z)V");
                    stack = 3;
                } else {
                    code->loadLocalInteger(*index);
                    stack = 1;
                }
                size = 1;
                break;
            case codemaker::UnoType::Sort::Byte:
                if (any) {
                    code->instrNew("java/lang/Byte");
                    code->instrDup();
                    code->loadLocalInteger(*index);
                    code->instrInvokespecial(
                        "java/lang/Byte", "<init>", "(B)V");
                    stack = 3;
                } else {
                    code->loadLocalInteger(*index);
                    stack = 1;
                }
                size = 1;
                break;
            case codemaker::UnoType::Sort::Short:
                if (any) {
                    code->instrNew("java/lang/Short");
                    code->instrDup();
                    code->loadLocalInteger(*index);
                    code->instrInvokespecial(
                        "java/lang/Short", "<init>", "(S)V");
                    stack = 3;
                } else {
                    code->loadLocalInteger(*index);
                    stack = 1;
                }
                size = 1;
                break;
            case codemaker::UnoType::Sort::UnsignedShort:
                if (any) {
                    code->instrNew("com/sun/star/uno/Any");
                    code->instrDup();
                    code->instrGetstatic(
                        "com/sun/star/uno/Type", "UNSIGNED_SHORT",
                        "Lcom/sun/star/uno/Type;");
                    code->instrNew("java/lang/Short");
                    code->instrDup();
                    code->loadLocalInteger(*index);
                    code->instrInvokespecial(
                        "java/lang/Short", "<init>", "(S)V");
                    code->instrInvokespecial(
                        "com/sun/star/uno/Any", "<init>",
                        "(Lcom/sun/star/uno/Type;Ljava/lang/Object;)V");
                    stack = 6;
                } else {
                    code->loadLocalInteger(*index);
                    stack = 1;
                }
                size = 1;
                break;
            case codemaker::UnoType::Sort::Long:
                if (any) {
                    code->instrNew("java/lang/Integer");
                    code->instrDup();
                    code->loadLocalInteger(*index);
                    code->instrInvokespecial(
                        "java/lang/Integer", "<init>", "(I)V");
                    stack = 3;
                } else {
                    code->loadLocalInteger(*index);
                    stack = 1;
                }
                size = 1;
                break;
            case codemaker::UnoType::Sort::UnsignedLong:
                if (any) {
                    code->instrNew("com/sun/star/uno/Any");
                    code->instrDup();
                    code->instrGetstatic(
                        "com/sun/star/uno/Type", "UNSIGNED_LONG",
                        "Lcom/sun/star/uno/Type;");
                    code->instrNew("java/lang/Integer");
                    code->instrDup();
                    code->loadLocalInteger(*index);
                    code->instrInvokespecial(
                        "java/lang/Integer", "<init>", "(I)V");
                    code->instrInvokespecial(
                        "com/sun/star/uno/Any", "<init>",
                        "(Lcom/sun/star/uno/Type;Ljava/lang/Object;)V");
                    stack = 6;
                } else {
                    code->loadLocalInteger(*index);
                    stack = 1;
                }
                size = 1;
                break;
            case codemaker::UnoType::Sort::Hyper:
                if (any) {
                    code->instrNew("java/lang/Long");
                    code->instrDup();
                    code->loadLocalLong(*index);
                    code->instrInvokespecial(
                        "java/lang/Long", "<init>", "(J)V");
                    stack = 4;
                } else {
                    code->loadLocalLong(*index);
                    stack = 2;
                }
                size = 2;
                break;
            case codemaker::UnoType::Sort::UnsignedHyper:
                if (any) {
                    code->instrNew("com/sun/star/uno/Any");
                    code->instrDup();
                    code->instrGetstatic(
                        "com/sun/star/uno/Type", "UNSIGNED_HYPER",
                        "Lcom/sun/star/uno/Type;");
                    code->instrNew("java/lang/Long");
                    code->instrDup();
                    code->loadLocalLong(*index);
                    code->instrInvokespecial(
                        "java/lang/Long", "<init>", "(J)V");
                    code->instrInvokespecial(
                        "com/sun/star/uno/Any", "<init>",
                        "(Lcom/sun/star/uno/Type;Ljava/lang/Object;)V");
                    stack = 7;
                } else {
                    code->loadLocalLong(*index);
                    stack = 2;
                }
                size = 2;
                break;
            case codemaker::UnoType::Sort::Float:
                if (any) {
                    code->instrNew("java/lang/Float");
                    code->instrDup();
                    code->loadLocalFloat(*index);
                    code->instrInvokespecial(
                        "java/lang/Float", "<init>", "(F)V");
                    stack = 3;
                } else {
                    code->loadLocalFloat(*index);
                    stack = 1;
                }
                size = 1;
                break;
            case codemaker::UnoType::Sort::Double:
                if (any) {
                    code->instrNew("java/lang/Double");
                    code->instrDup();
                    code->loadLocalDouble(*index);
                    code->instrInvokespecial(
                        "java/lang/Double", "<init>", "(D)V");
                    stack = 4;
                } else {
                    code->loadLocalDouble(*index);
                    stack = 2;
                }
                size = 2;
                break;
            case codemaker::UnoType::Sort::Char:
                if (any) {
                    code->instrNew("java/lang/Character");
                    code->instrDup();
                    code->loadLocalInteger(*index);
                    code->instrInvokespecial(
                        "java/lang/Character", "<init>", "(C)V");
                    stack = 3;
                } else {
                    code->loadLocalInteger(*index);
                    stack = 1;
                }
                size = 1;
                break;
            case codemaker::UnoType::Sort::String:
            case codemaker::UnoType::Sort::Type:
            case codemaker::UnoType::Sort::Any:
                code->loadLocalReference(*index);
                stack = size = 1;
                break;
            case codemaker::UnoType::Sort::Enum:
                // Assuming that no Java types are derived from Java types that
                // are directly derived from com.sun.star.uno.Enum:
                code->loadLocalReference(*index);
                stack = size = 1;
                break;
            case codemaker::UnoType::Sort::PlainStruct:
            case codemaker::UnoType::Sort::InstantiatedPolymorphicStruct:
                if (any) {
                    code->instrNew("com/sun/star/uno/Any");
                    code->instrDup();
                    code->instrNew("com/sun/star/uno/Type");
                    code->instrDup();
                    code->loadStringConstant(
                        codemaker::convertString(
                            createUnoName(manager, nucleus, rank, args)));
                    code->instrGetstatic(
                        "com/sun/star/uno/TypeClass", "STRUCT",
                        "Lcom/sun/star/uno/TypeClass;");
                    dependencies->insert("com.sun.star.uno.TypeClass");
                    code->instrInvokespecial(
                        "com/sun/star/uno/Type", "<init>",
                        "(Ljava/lang/String;Lcom/sun/star/uno/TypeClass;)V");
                    code->loadLocalReference(*index);
                    code->instrInvokespecial(
                        "com/sun/star/uno/Any", "<init>",
                        "(Lcom/sun/star/uno/Type;Ljava/lang/Object;)V");
                    stack = 6;
                } else {
                    code->loadLocalReference(*index);
                    stack = 1;
                }
                size = 1;
                break;
            case codemaker::UnoType::Sort::Interface:
                if (any && nucleus != "com.sun.star.uno.XInterface") {
                    code->instrNew("com/sun/star/uno/Any");
                    code->instrDup();
                    code->instrNew("com/sun/star/uno/Type");
                    code->instrDup();
                    code->loadStringConstant(codemaker::convertString(nucleus));
                    code->instrGetstatic(
                        "com/sun/star/uno/TypeClass", "INTERFACE",
                        "Lcom/sun/star/uno/TypeClass;");
                    dependencies->insert("com.sun.star.uno.TypeClass");
                    code->instrInvokespecial(
                        "com/sun/star/uno/Type", "<init>",
                        "(Ljava/lang/String;Lcom/sun/star/uno/TypeClass;)V");
                    code->loadLocalReference(*index);
                    code->instrInvokespecial(
                        "com/sun/star/uno/Any", "<init>",
                        "(Lcom/sun/star/uno/Type;Ljava/lang/Object;)V");
                    stack = 6;
                } else {
                    code->loadLocalReference(*index);
                    stack = 1;
                }
                size = 1;
                break;
            case codemaker::UnoType::Sort::Sequence:
            case codemaker::UnoType::Sort::Typedef:
                for (;;) std::abort(); // this cannot happen
            default:
                throw CannotDumpException(
                    "unexpected entity \"" + type
                    + "\" in call to addLoadLocal");
            }
        } else {
            bool bWrap = false;
            if (any) {
                switch (sort) {
                case codemaker::UnoType::Sort::Boolean:
                case codemaker::UnoType::Sort::Byte:
                case codemaker::UnoType::Sort::Short:
                case codemaker::UnoType::Sort::Long:
                case codemaker::UnoType::Sort::Hyper:
                case codemaker::UnoType::Sort::Float:
                case codemaker::UnoType::Sort::Double:
                case codemaker::UnoType::Sort::Char:
                case codemaker::UnoType::Sort::String:
                case codemaker::UnoType::Sort::Type:
                        // assuming that no Java types are derived from
                        // com.sun.star.uno.Type
                case codemaker::UnoType::Sort::Enum:
                        // assuming that no Java types are derived from Java
                        // types that are directly derived from
                        // com.sun.star.uno.Enum
                    break;
                case codemaker::UnoType::Sort::UnsignedShort:
                case codemaker::UnoType::Sort::UnsignedLong:
                case codemaker::UnoType::Sort::UnsignedHyper:
                case codemaker::UnoType::Sort::Any:
                case codemaker::UnoType::Sort::PlainStruct:
                case codemaker::UnoType::Sort::InstantiatedPolymorphicStruct:
                case codemaker::UnoType::Sort::Interface:
                    bWrap = true;
                    break;
                case codemaker::UnoType::Sort::Sequence:
                case codemaker::UnoType::Sort::Typedef:
                    for (;;) std::abort(); // this cannot happen
                default:
                    throw CannotDumpException(
                        "unexpected entity \"" + type
                        + "\" in call to addLoadLocal");
                }
            }
            if (bWrap) {
                code->instrNew("com/sun/star/uno/Any");
                code->instrDup();
                code->instrNew("com/sun/star/uno/Type");
                code->instrDup();
                code->loadStringConstant(
                    codemaker::convertString(
                        createUnoName(manager, nucleus, rank, args)));
                code->instrInvokespecial(
                    "com/sun/star/uno/Type", "<init>", "(Ljava/lang/String;)V");
                code->loadLocalReference(*index);
                code->instrInvokespecial(
                    "com/sun/star/uno/Any", "<init>",
                    "(Lcom/sun/star/uno/Type;Ljava/lang/Object;)V");
                stack = 5;
            } else {
                code->loadLocalReference(*index);
                stack = 1;
            }
            size = 1;
        }
    }
    if (*index > SAL_MAX_UINT16 - size) {
        throw CannotDumpException(
            "Too many local variables for Java class file format");
    }
    *index = *index + size;
    return stack;
}

sal_uInt16 addDirectArgument(
    rtl::Reference< TypeManager > const & manager, std::set<OUString> * dependencies,
    MethodDescriptor * methodDescriptor, ClassFile::Code * code,
    sal_uInt16 * index, OString const & className, OString const & fieldName,
    bool typeParameter, OUString const & fieldType)
{
    assert(methodDescriptor != nullptr);
    assert(code != nullptr);
    OString desc;
    if (typeParameter) {
        methodDescriptor->addTypeParameter(fieldType);
        desc = "Ljava/lang/Object;";
    } else {
        methodDescriptor->addParameter(fieldType, false, true, nullptr);
        getFieldDescriptor(manager, dependencies, fieldType, &desc, nullptr, nullptr);
    }
    code->loadLocalReference(0);
    sal_uInt16 stack = addLoadLocal(
        manager, code, index, typeParameter, fieldType, false, dependencies);
    code->instrPutfield(className, fieldName, desc);
    return stack + 1;
}

void addPlainStructBaseArguments(
    rtl::Reference< TypeManager > const & manager, std::set<OUString> * dependencies,
    MethodDescriptor * methodDescriptor, ClassFile::Code * code,
    OUString const & base, sal_uInt16 * index)
{
    assert(manager.is());
    assert(methodDescriptor != nullptr);
    rtl::Reference< unoidl::Entity > ent;
    if (manager->getSort(base, &ent)
        != codemaker::UnoType::Sort::PlainStruct)
    {
        throw CannotDumpException(
            "unexpected entity \"" + base
            + "\" in call to addPlainStructBaseArguments");
    }
    unoidl::PlainStructTypeEntity& ent2(dynamic_cast<unoidl::PlainStructTypeEntity&>(*ent));
    if (!ent2.getDirectBase().isEmpty()) {
        addPlainStructBaseArguments(
            manager, dependencies, methodDescriptor, code,
            ent2.getDirectBase(), index);
    }
    for (const unoidl::PlainStructTypeEntity::Member& member : ent2.getDirectMembers())
    {
        methodDescriptor->addParameter(member.type, false, true, nullptr);
        addLoadLocal(manager, code, index, false, member.type, false, dependencies);
    }
}

void handlePlainStructType(
    const OUString& name,
    rtl::Reference< unoidl::PlainStructTypeEntity > const & entity,
    rtl::Reference< TypeManager > const & manager, JavaOptions const & options,
    std::set<OUString> * dependencies)
{
    assert(entity.is());
    assert(dependencies != nullptr);
    OString className(codemaker::convertString(name).replace('.', '/'));
    OString superClass;
    if (entity->getDirectBase().isEmpty()) {
        superClass = "java/lang/Object";
    } else {
        superClass = codemaker::convertString(entity->getDirectBase()).
            replace('.', '/');
        dependencies->insert(entity->getDirectBase());
    }
    std::unique_ptr< ClassFile > cf(
        new ClassFile(
            static_cast< ClassFile::AccessFlags >(
                ClassFile::ACC_PUBLIC | ClassFile::ACC_SUPER),
            className, superClass, ""));
    std::vector< TypeInfo > typeInfo;
    sal_Int32 index = 0;
    for (const unoidl::PlainStructTypeEntity::Member& member : entity->getDirectMembers())
    {
        addField(
            manager, dependencies, cf.get(), &typeInfo, -1, member.type, member.name,
            index++);
    }
    std::unique_ptr< ClassFile::Code > code(cf->newCode());
    code->loadLocalReference(0);
    code->instrInvokespecial(superClass, "<init>", "()V");
    sal_uInt16 stack = 0;
    for (const unoidl::PlainStructTypeEntity::Member& member : entity->getDirectMembers())
    {
        stack = std::max(
            stack,
            addFieldInit(
                manager, className, member.name, false, member.type, dependencies,
                code.get()));
    }
    code->instrReturn();
    code->setMaxStackAndLocals(stack + 1, 1);
    cf->addMethod(
        ClassFile::ACC_PUBLIC, "<init>", "()V", code.get(),
        std::vector< OString >(), "");
    MethodDescriptor desc(manager, dependencies, "void", nullptr, nullptr);
    code.reset(cf->newCode());
    code->loadLocalReference(0);
    sal_uInt16 index2 = 1;
    if (!entity->getDirectBase().isEmpty()) {
        addPlainStructBaseArguments(
            manager, dependencies, &desc, code.get(), entity->getDirectBase(),
            &index2);
    }
    code->instrInvokespecial(superClass, "<init>", desc.getDescriptor());
    sal_uInt16 maxSize = index2;
    for (const unoidl::PlainStructTypeEntity::Member& member : entity->getDirectMembers())
    {
        maxSize = std::max(
            maxSize,
            addDirectArgument(
                manager, dependencies, &desc, code.get(), &index2, className,
                codemaker::convertString(member.name), false, member.type));
    }
    code->instrReturn();
    code->setMaxStackAndLocals(maxSize, index2);
    cf->addMethod(
        ClassFile::ACC_PUBLIC, "<init>", desc.getDescriptor(), code.get(),
        std::vector< OString >(), desc.getSignature());
    addTypeInfo(className, typeInfo, dependencies, cf.get());
    writeClassFile(options, className, *cf);
}

void handlePolyStructType(
    const OUString& name,
    rtl::Reference< unoidl::PolymorphicStructTypeTemplateEntity > const &
        entity,
    rtl::Reference< TypeManager > const & manager, JavaOptions const & options,
    std::set<OUString> * dependencies)
{
    assert(entity.is());
    OString className(codemaker::convertString(name).replace('.', '/'));
    std::map< OUString, sal_Int32 > typeParameters;
    OStringBuffer sig("<");
    sal_Int32 index = 0;
    for (const OUString& param : entity->getTypeParameters())
    {
        sig.append(codemaker::convertString(param) + ":Ljava/lang/Object;");
        if (!typeParameters.emplace(param, index++).second)
        {
            throw CannotDumpException("Bad type information"); //TODO
        }
    }
    sig.append(">Ljava/lang/Object;");
    std::unique_ptr< ClassFile > cf(
        new ClassFile(
            static_cast< ClassFile::AccessFlags >(
                ClassFile::ACC_PUBLIC | ClassFile::ACC_SUPER),
            className, "java/lang/Object", sig.makeStringAndClear()));
    std::vector< TypeInfo > typeInfo;
    index = 0;
    for (const unoidl::PolymorphicStructTypeTemplateEntity::Member& member : entity->getMembers())
    {
        sal_Int32 typeParameterIndex;
        if (member.parameterized) {
            std::map< OUString, sal_Int32 >::iterator it(
                typeParameters.find(member.type));
            if (it == typeParameters.end()) {
                throw CannotDumpException("Bad type information"); //TODO
            }
            typeParameterIndex = it->second;
        } else {
            typeParameterIndex = -1;
        }
        addField(
            manager, dependencies, cf.get(), &typeInfo, typeParameterIndex,
            member.type, member.name, index++);
    }
    std::unique_ptr< ClassFile::Code > code(cf->newCode());
    code->loadLocalReference(0);
    code->instrInvokespecial("java/lang/Object", "<init>", "()V");
    sal_uInt16 stack = 0;
    for (const unoidl::PolymorphicStructTypeTemplateEntity::Member& member : entity->getMembers())
    {
        stack = std::max(
            stack,
            addFieldInit(
                manager, className, member.name, member.parameterized, member.type,
                dependencies, code.get()));
    }
    code->instrReturn();
    code->setMaxStackAndLocals(stack + 1, 1);
    cf->addMethod(
        ClassFile::ACC_PUBLIC, "<init>", "()V", code.get(),
        std::vector< OString >(), "");
    MethodDescriptor desc(manager, dependencies, "void", nullptr, nullptr);
    code.reset(cf->newCode());
    code->loadLocalReference(0);
    sal_uInt16 index2 = 1;
    code->instrInvokespecial(
        "java/lang/Object", "<init>", desc.getDescriptor());
    sal_uInt16 maxSize = index2;
    for (const unoidl::PolymorphicStructTypeTemplateEntity::Member& member : entity->getMembers())
    {
        maxSize = std::max(
            maxSize,
            addDirectArgument(
                manager, dependencies, &desc, code.get(), &index2, className,
                codemaker::convertString(member.name), member.parameterized, member.type));
    }
    code->instrReturn();
    code->setMaxStackAndLocals(maxSize, index2);
    cf->addMethod(
        ClassFile::ACC_PUBLIC, "<init>", desc.getDescriptor(), code.get(),
        std::vector< OString >(), desc.getSignature());
    addTypeInfo(className, typeInfo, dependencies, cf.get());
    writeClassFile(options, className, *cf);
}

void addExceptionBaseArguments(
    rtl::Reference< TypeManager > const & manager, std::set<OUString> * dependencies,
    MethodDescriptor * methodDescriptor, ClassFile::Code * code,
    OUString const & base, sal_uInt16 * index)
{
    assert(manager.is());
    assert(methodDescriptor != nullptr);
    rtl::Reference< unoidl::Entity > ent;
    if (manager->getSort(base, &ent) != codemaker::UnoType::Sort::Exception)
    {
        throw CannotDumpException(
            "unexpected entity \"" + base
            + "\" in call to addExceptionBaseArguments");
    }
    unoidl::ExceptionTypeEntity& ent2(dynamic_cast<unoidl::ExceptionTypeEntity&>(*ent));
    bool baseException = base == "com.sun.star.uno.Exception";
    if (!baseException) {
        addExceptionBaseArguments(
            manager, dependencies, methodDescriptor, code,
            ent2.getDirectBase(), index);
    }
    for (std::vector< unoidl::ExceptionTypeEntity::Member >::const_iterator i(
             ent2.getDirectMembers().begin());
         i != ent2.getDirectMembers().end(); ++i)
    {
        if (!baseException || i != ent2.getDirectMembers().begin()) {
            methodDescriptor->addParameter(i->type, false, true, nullptr);
            addLoadLocal(
                manager, code, index, false, i->type, false, dependencies);
        }
    }
}

void handleExceptionType(
    const OUString& name, rtl::Reference< unoidl::ExceptionTypeEntity > const & entity,
    rtl::Reference< TypeManager > const & manager, JavaOptions const & options,
    std::set<OUString> * dependencies)
{
    assert(entity.is());
    assert(dependencies != nullptr);
    OString className(codemaker::convertString(name).replace('.', '/'));
    bool baseException = false;
    bool baseRuntimeException = false;
    OString superClass;
    if (className == "com/sun/star/uno/Exception") {
        baseException = true;
        superClass = "java/lang/Exception";
    } else if (className == "com/sun/star/uno/RuntimeException") {
        baseRuntimeException = true;
        superClass = "java/lang/RuntimeException";
    } else {
        if (entity->getDirectBase().isEmpty()) {
            throw CannotDumpException(
                "Exception type \"" + name + "\" lacks base");
        }
        superClass = codemaker::convertString(entity->getDirectBase()).
            replace('.', '/');
        dependencies->insert(entity->getDirectBase());
    }
    std::unique_ptr< ClassFile > cf(
        new ClassFile(
            static_cast< ClassFile::AccessFlags >(
                ClassFile::ACC_PUBLIC | ClassFile::ACC_SUPER),
            className, superClass, ""));
    std::vector< TypeInfo > typeInfo;
    sal_Int32 index = 0;
    if (baseRuntimeException) {
        addField(
            manager, dependencies, cf.get(), &typeInfo, -1,
            "com.sun.star.uno.XInterface", "Context", index++);
    }
    for (std::vector< unoidl::ExceptionTypeEntity::Member >::const_iterator i(
             entity->getDirectMembers().begin());
         i != entity->getDirectMembers().end(); ++i)
    {
        if (!baseException || i != entity->getDirectMembers().begin()) {
            addField(
                manager, dependencies, cf.get(), &typeInfo, -1, i->type,
                i->name, index++);
        }
    }

    // create default constructor
    std::unique_ptr< ClassFile::Code > code(cf->newCode());
    code->loadLocalReference(0);
    code->instrInvokespecial(superClass, "<init>", "()V");
    sal_uInt16 stack = 0;
    if (baseRuntimeException) {
        stack = std::max(
            stack,
            addFieldInit(
                manager, className, "Context", false,
                "com.sun.star.uno.XInterface", dependencies, code.get()));
    }
    for (std::vector< unoidl::ExceptionTypeEntity::Member >::const_iterator i(
             entity->getDirectMembers().begin());
         i != entity->getDirectMembers().end(); ++i)
    {
        if (!baseException || i != entity->getDirectMembers().begin()) {
            stack = std::max(
                stack,
                addFieldInit(
                    manager, className, i->name, false, i->type, dependencies,
                    code.get()));
        }
    }
    code->instrReturn();
    code->setMaxStackAndLocals(stack + 1, 1);
    cf->addMethod(
        ClassFile::ACC_PUBLIC, "<init>", "()V", code.get(),
        std::vector< OString >(), "");


    // create (Throwable Cause) constructor
    code.reset(cf->newCode());
    code->loadLocalReference(0);
    code->loadLocalReference(1);
    code->instrInvokespecial(superClass, "<init>", "(Ljava/lang/Throwable;)V");
    stack = 0;
    if (baseRuntimeException) {
        stack = std::max(
            stack,
            addFieldInit(
                manager, className, "Context", false,
                "com.sun.star.uno.XInterface", dependencies, code.get()));
    }
    for (std::vector< unoidl::ExceptionTypeEntity::Member >::const_iterator i(
             entity->getDirectMembers().begin());
         i != entity->getDirectMembers().end(); ++i)
    {
        if (!baseException || i != entity->getDirectMembers().begin()) {
            stack = std::max(
                stack,
                addFieldInit(
                    manager, className, i->name, false, i->type, dependencies,
                    code.get()));
        }
    }
    code->instrReturn();
    code->setMaxStackAndLocals(stack + 2, 2);
    cf->addMethod(
        ClassFile::ACC_PUBLIC, "<init>", "(Ljava/lang/Throwable;)V", code.get(),
        std::vector< OString >(), "");

    // create (Throwable Cause, String Message) constructor
    code.reset(cf->newCode());
    code->loadLocalReference(0);
    if (baseException || baseRuntimeException) {
        code->loadLocalReference(2);
        code->loadLocalReference(1);
        code->instrInvokespecial(superClass, "<init>", "(Ljava/lang/String;Ljava/lang/Throwable;)V");
    } else {
        code->loadLocalReference(1);
        code->loadLocalReference(2);
        code->instrInvokespecial(superClass, "<init>", "(Ljava/lang/Throwable;Ljava/lang/String;)V");
    }
    stack = 0;
    if (baseRuntimeException) {
        stack = std::max(
            stack,
            addFieldInit(
                manager, className, "Context", false,
                "com.sun.star.uno.XInterface", dependencies, code.get()));
    }
    for (std::vector< unoidl::ExceptionTypeEntity::Member >::const_iterator i(
             entity->getDirectMembers().begin());
         i != entity->getDirectMembers().end(); ++i)
    {
        if (!baseException || i != entity->getDirectMembers().begin()) {
            stack = std::max(
                stack,
                addFieldInit(
                    manager, className, i->name, false, i->type, dependencies,
                    code.get()));
        }
    }
    code->instrReturn();
    code->setMaxStackAndLocals(stack + 3, 3);
    cf->addMethod(
        ClassFile::ACC_PUBLIC, "<init>", "(Ljava/lang/Throwable;Ljava/lang/String;)V", code.get(),
        std::vector< OString >(), "");

    // create (String Message) constructor
    code.reset(cf->newCode());
    code->loadLocalReference(0);
    code->loadLocalReference(1);
    code->instrInvokespecial(superClass, "<init>", "(Ljava/lang/String;)V");
    stack = 0;
    if (baseRuntimeException) {
        stack = std::max(
            stack,
            addFieldInit(
                manager, className, "Context", false,
                "com.sun.star.uno.XInterface", dependencies, code.get()));
    }
    for (std::vector< unoidl::ExceptionTypeEntity::Member >::const_iterator i(
             entity->getDirectMembers().begin());
         i != entity->getDirectMembers().end(); ++i)
    {
        if (!baseException || i != entity->getDirectMembers().begin()) {
            stack = std::max(
                stack,
                addFieldInit(
                    manager, className, i->name, false, i->type, dependencies,
                    code.get()));
        }
    }
    code->instrReturn();
    code->setMaxStackAndLocals(stack + 2, 2);
    cf->addMethod(
        ClassFile::ACC_PUBLIC, "<init>", "(Ljava/lang/String;)V", code.get(),
        std::vector< OString >(), "");


    // create (String Message, Object Context, T1 m1, ..., Tn mn) constructor
    MethodDescriptor desc1(manager, dependencies, "void", nullptr, nullptr);
    code.reset(cf->newCode());
    code->loadLocalReference(0);
    sal_uInt16 index2 = 1;
    code->loadLocalReference(index2++);
    desc1.addParameter("string", false, true, nullptr);
    if (!(baseException || baseRuntimeException)) {
        addExceptionBaseArguments(
            manager, dependencies, &desc1, code.get(), entity->getDirectBase(),
            &index2);
    }
    code->instrInvokespecial(superClass, "<init>", desc1.getDescriptor());
    sal_uInt16 maxSize = index2;
    if (baseRuntimeException) {
        maxSize = std::max(
            maxSize,
            addDirectArgument(
                manager, dependencies, &desc1, code.get(), &index2, className,
                "Context", false, "com.sun.star.uno.XInterface"));
    }
    for (std::vector< unoidl::ExceptionTypeEntity::Member >::const_iterator i(
             entity->getDirectMembers().begin());
         i != entity->getDirectMembers().end(); ++i)
    {
        if (!baseException || i != entity->getDirectMembers().begin()) {
            maxSize = std::max(
                maxSize,
                addDirectArgument(
                    manager, dependencies, &desc1, code.get(), &index2,
                    className, codemaker::convertString(i->name), false,
                    i->type));
        }
    }
    code->instrReturn();
    code->setMaxStackAndLocals(maxSize, index2);
    cf->addMethod(
        ClassFile::ACC_PUBLIC, "<init>", desc1.getDescriptor(), code.get(),
        std::vector< OString >(), desc1.getSignature());

    // create (Throwable Cause, String Message, Object Context, T1 m1, ..., Tn mn) constructor
    MethodDescriptor desc2(manager, dependencies, "void", nullptr, nullptr);
    code.reset(cf->newCode());
    code->loadLocalReference(0);
    sal_uInt16 index3 = 3;
    // Note that we hack in the java.lang.Throwable parameter further down,
    // because MethodDescriptor does not know how to handle it.
    desc2.addParameter("string", false, true, nullptr);
    if (baseException || baseRuntimeException) {
        code->loadLocalReference(2);
        code->loadLocalReference(1);
        code->instrInvokespecial(superClass, "<init>", "(Ljava/lang/String;Ljava/lang/Throwable;)V");
    } else {
        code->loadLocalReference(1);
        code->loadLocalReference(2);
        addExceptionBaseArguments(
            manager, dependencies, &desc2, code.get(), entity->getDirectBase(),
            &index3);
        code->instrInvokespecial(superClass, "<init>", "(Ljava/lang/Throwable;" + desc2.getDescriptor().copy(1));
    }
    sal_uInt16 maxSize2 = index3;
    if (baseRuntimeException) {
        maxSize2 = std::max(
            maxSize2,
            addDirectArgument(
                manager, dependencies, &desc2, code.get(), &index3, className,
                "Context", false, "com.sun.star.uno.XInterface"));
    }
    for (std::vector< unoidl::ExceptionTypeEntity::Member >::const_iterator i(
             entity->getDirectMembers().begin());
         i != entity->getDirectMembers().end(); ++i)
    {
        if (!baseException || i != entity->getDirectMembers().begin()) {
            maxSize2 = std::max(
                maxSize2,
                addDirectArgument(
                    manager, dependencies, &desc2, code.get(), &index3,
                    className, codemaker::convertString(i->name), false,
                    i->type));
        }
    }
    code->instrReturn();
    code->setMaxStackAndLocals(maxSize2, index3);
    cf->addMethod(
        ClassFile::ACC_PUBLIC, "<init>", "(Ljava/lang/Throwable;" + desc2.getDescriptor().copy(1), code.get(),
        std::vector< OString >(), desc2.getSignature());

    addTypeInfo(className, typeInfo, dependencies, cf.get());
    writeClassFile(options, className, *cf);
}

void createExceptionsAttribute(
    rtl::Reference< TypeManager > const & manager,
    std::vector< OUString > const & exceptionTypes,
    std::set<OUString> * dependencies, std::vector< OString > * exceptions,
    codemaker::ExceptionTree * tree)
{
    assert(dependencies != nullptr);
    assert(exceptions != nullptr);
    for (const OUString& ex : exceptionTypes)
    {
        dependencies->insert(ex);
        OString type(codemaker::convertString(ex).replace('.', '/'));
        exceptions->push_back(type);
        if (tree != nullptr) {
            tree->add(type.replace('/', '.'), manager);
        }
    }
}

void handleInterfaceType(
    const OUString& name, rtl::Reference< unoidl::InterfaceTypeEntity > const & entity,
    rtl::Reference< TypeManager > const & manager, JavaOptions const & options,
    std::set<OUString> * dependencies)
{
    assert(entity.is());
    assert(dependencies != nullptr);
    OString className(codemaker::convertString(name).replace('.', '/'));
    std::unique_ptr< ClassFile > cf(
        new ClassFile(
            static_cast< ClassFile::AccessFlags >(
                ClassFile::ACC_PUBLIC | ClassFile::ACC_INTERFACE
                | ClassFile::ACC_ABSTRACT),
            className, "java/lang/Object", ""));
    for (const unoidl::AnnotatedReference& ar : entity->getDirectMandatoryBases())
    {
        dependencies->insert(ar.name);
        cf->addInterface(codemaker::convertString(ar.name).replace('.', '/'));
    }
    // As a special case, let com.sun.star.lang.XEventListener extend
    // java.util.EventListener ("A tagging interface that all event listener
    // interfaces must extend"):
    if (className == "com/sun/star/lang/XEventListener") {
        cf->addInterface("java/util/EventListener");
    }
    std::vector< TypeInfo > typeInfo;
    if (className != "com/sun/star/uno/XInterface") {
        sal_Int32 index = 0;
        for (const unoidl::InterfaceTypeEntity::Attribute& attr : entity->getDirectAttributes())
        {
            SpecialType specialType;
            PolymorphicUnoType polymorphicUnoType;
            MethodDescriptor gdesc(
                manager, dependencies, attr.type, &specialType,
                &polymorphicUnoType);
            std::vector< OString > exc;
            createExceptionsAttribute(
                manager, attr.getExceptions, dependencies, &exc, nullptr);
            OString attrName(codemaker::convertString(attr.name));
            cf->addMethod(
                static_cast< ClassFile::AccessFlags >(
                    ClassFile::ACC_PUBLIC | ClassFile::ACC_ABSTRACT),
                "get" + attrName, gdesc.getDescriptor(), nullptr, exc,
                gdesc.getSignature());
            if (!attr.readOnly) {
                MethodDescriptor sdesc(manager, dependencies, "void", nullptr, nullptr);
                sdesc.addParameter(attr.type, false, true, nullptr);
                std::vector< OString > exc2;
                createExceptionsAttribute(
                    manager, attr.setExceptions, dependencies, &exc2, nullptr);
                cf->addMethod(
                    static_cast< ClassFile::AccessFlags >(
                        ClassFile::ACC_PUBLIC | ClassFile::ACC_ABSTRACT),
                    "set" + attrName, sdesc.getDescriptor(), nullptr, exc2,
                    sdesc.getSignature());
            }
            typeInfo.emplace_back(
                    TypeInfo::KIND_ATTRIBUTE, attrName, specialType,
                    static_cast< TypeInfo::Flags >(
                        (attr.readOnly ? TypeInfo::FLAG_READONLY : 0)
                        | (attr.bound ? TypeInfo::FLAG_BOUND : 0)),
                    index, polymorphicUnoType);
            index += (attr.readOnly ? 1 : 2);
        }
        for (const unoidl::InterfaceTypeEntity::Method& method : entity->getDirectMethods())
        {
            OString methodName(codemaker::convertString(method.name));
            SpecialType specialReturnType;
            PolymorphicUnoType polymorphicUnoReturnType;
            MethodDescriptor desc(
                manager, dependencies, method.returnType, &specialReturnType,
                &polymorphicUnoReturnType);
            typeInfo.emplace_back(
                    TypeInfo::KIND_METHOD, methodName, specialReturnType,
                    static_cast< TypeInfo::Flags >(0), index++,
                    polymorphicUnoReturnType);
            sal_Int32 paramIndex = 0;
            for (const unoidl::InterfaceTypeEntity::Method::Parameter& param : method.parameters)
            {
                bool in = param.direction
                    != unoidl::InterfaceTypeEntity::Method::Parameter::DIRECTION_OUT;
                bool out = param.direction
                    != unoidl::InterfaceTypeEntity::Method::Parameter::DIRECTION_IN;
                PolymorphicUnoType polymorphicUnoType;
                SpecialType specialType = desc.addParameter(
                    param.type, out, true, &polymorphicUnoType);
                if (out || isSpecialType(specialType)
                    || polymorphicUnoType.kind != PolymorphicUnoType::KIND_NONE)
                {
                    typeInfo.emplace_back(
                            codemaker::convertString(param.name), specialType, in,
                            out, methodName, paramIndex, polymorphicUnoType);
                }
                ++paramIndex;
            }
            std::vector< OString > exc2;
            createExceptionsAttribute(
                manager, method.exceptions, dependencies, &exc2, nullptr);
            cf->addMethod(
                static_cast< ClassFile::AccessFlags >(
                    ClassFile::ACC_PUBLIC | ClassFile::ACC_ABSTRACT),
                methodName, desc.getDescriptor(), nullptr, exc2, desc.getSignature());
        }
    }
    addTypeInfo(className, typeInfo, dependencies, cf.get());
    writeClassFile(options, className, *cf);
}

void handleTypedef(
    rtl::Reference< unoidl::TypedefEntity > const & entity,
    rtl::Reference< TypeManager > const & manager, std::set<OUString> * dependencies)
{
    assert(entity.is());
    assert(manager.is());
    assert(dependencies != nullptr);
    OUString nucleus;
    switch (manager->decompose(entity->getType(), false, &nucleus, nullptr, nullptr, nullptr))
    {
    case codemaker::UnoType::Sort::Boolean:
    case codemaker::UnoType::Sort::Byte:
    case codemaker::UnoType::Sort::Short:
    case codemaker::UnoType::Sort::UnsignedShort:
    case codemaker::UnoType::Sort::Long:
    case codemaker::UnoType::Sort::UnsignedLong:
    case codemaker::UnoType::Sort::Hyper:
    case codemaker::UnoType::Sort::UnsignedHyper:
    case codemaker::UnoType::Sort::Float:
    case codemaker::UnoType::Sort::Double:
    case codemaker::UnoType::Sort::Char:
    case codemaker::UnoType::Sort::String:
    case codemaker::UnoType::Sort::Type:
    case codemaker::UnoType::Sort::Any:
        break;
    case codemaker::UnoType::Sort::Enum:
    case codemaker::UnoType::Sort::PlainStruct:
    case codemaker::UnoType::Sort::Interface:
    case codemaker::UnoType::Sort::Typedef:
        dependencies->insert(nucleus);
        break;
    default:
        assert(false); // this cannot happen
    }
}

void handleConstantGroup(
    const OUString& name, rtl::Reference< unoidl::ConstantGroupEntity > const & entity,
    rtl::Reference< TypeManager > const & manager, JavaOptions const & options,
    std::set<OUString> * dependencies)
{
    assert(entity.is());
    OString className(codemaker::convertString(name).replace('.', '/'));
    std::unique_ptr< ClassFile > cf(
        new ClassFile(
            static_cast< ClassFile::AccessFlags >(
                ClassFile::ACC_PUBLIC | ClassFile::ACC_INTERFACE
                | ClassFile::ACC_ABSTRACT),
            className, "java/lang/Object", ""));
    for (const unoidl::ConstantGroupEntity::Member& member : entity->getMembers())
    {
        OUString type;
        sal_uInt16 valueIndex = sal_uInt16(); // avoid false warnings
        switch (member.value.type) {
        case unoidl::ConstantValue::TYPE_BOOLEAN:
            type = "boolean";
            valueIndex = cf->addIntegerInfo(sal_Int32(member.value.booleanValue));
            break;
        case unoidl::ConstantValue::TYPE_BYTE:
            type = "byte";
            valueIndex = cf->addIntegerInfo(member.value.byteValue);
            break;
        case unoidl::ConstantValue::TYPE_SHORT:
            type = "short";
            valueIndex = cf->addIntegerInfo(member.value.shortValue);
            break;
        case unoidl::ConstantValue::TYPE_UNSIGNED_SHORT:
            type = "unsigned short";
            valueIndex = cf->addIntegerInfo(member.value.unsignedShortValue);
            break;
        case unoidl::ConstantValue::TYPE_LONG:
            type = "long";
            valueIndex = cf->addIntegerInfo(member.value.longValue);
            break;
        case unoidl::ConstantValue::TYPE_UNSIGNED_LONG:
            type = "unsigned long";
            valueIndex = cf->addIntegerInfo(
                static_cast< sal_Int32 >(member.value.unsignedLongValue));
            break;
        case unoidl::ConstantValue::TYPE_HYPER:
            type = "hyper";
            valueIndex = cf->addLongInfo(member.value.hyperValue);
            break;
        case unoidl::ConstantValue::TYPE_UNSIGNED_HYPER:
            type = "unsigned hyper";
            valueIndex = cf->addLongInfo(
                static_cast< sal_Int64 >(member.value.unsignedHyperValue));
            break;
        case unoidl::ConstantValue::TYPE_FLOAT:
            type = "float";
            valueIndex = cf->addFloatInfo(member.value.floatValue);
            break;
        case unoidl::ConstantValue::TYPE_DOUBLE:
            type = "double";
            valueIndex = cf->addDoubleInfo(member.value.doubleValue);
            break;
        }
        OString desc;
        OString sig;
        getFieldDescriptor(manager, dependencies, type, &desc, &sig, nullptr);
        cf->addField(
            static_cast< ClassFile::AccessFlags >(
                ClassFile::ACC_PUBLIC | ClassFile::ACC_STATIC
                | ClassFile::ACC_FINAL),
            codemaker::convertString(member.name), desc, valueIndex, sig);
    }
    writeClassFile(options, className, *cf);
}

void addExceptionHandlers(
    codemaker::ExceptionTreeNode const * node,
    ClassFile::Code::Position start, ClassFile::Code::Position end,
    ClassFile::Code::Position handler, ClassFile::Code * code)
{
    assert(node != nullptr);
    assert(code != nullptr);
    if (node->present) {
        code->addException(start, end, handler, node->name.replace('.', '/'));
    } else {
        for (std::unique_ptr<codemaker::ExceptionTreeNode> const & p : node->children)
        {
            addExceptionHandlers(p.get(), start, end, handler, code);
        }
    }
}

void addConstructor(
    rtl::Reference< TypeManager > const & manager,
    OString const & realJavaBaseName, OString const & unoName,
    OString const & className,
    unoidl::SingleInterfaceBasedServiceEntity::Constructor const & constructor,
    OUString const & returnType, std::set<OUString> * dependencies,
    ClassFile * classFile)
{
    assert(dependencies != nullptr);
    assert(classFile != nullptr);
    MethodDescriptor desc(manager, dependencies, returnType, nullptr, nullptr);
    desc.addParameter("com.sun.star.uno.XComponentContext", false, false, nullptr);
    std::unique_ptr< ClassFile::Code > code(classFile->newCode());
    code->loadLocalReference(0);
    // stack: context
    code->instrInvokeinterface(
        "com/sun/star/uno/XComponentContext", "getServiceManager",
        "()Lcom/sun/star/lang/XMultiComponentFactory;", 1);
    // stack: factory
    code->loadStringConstant(unoName);
    // stack: factory serviceName
    codemaker::ExceptionTree tree;
    ClassFile::Code::Position tryStart;
    ClassFile::Code::Position tryEnd;
    std::vector< OString > exc;
    sal_uInt16 stack;
    sal_uInt16 localIndex = 1;
    ClassFile::AccessFlags access = static_cast< ClassFile::AccessFlags >(
        ClassFile::ACC_PUBLIC | ClassFile::ACC_STATIC);
    if (constructor.defaultConstructor) {
        code->loadLocalReference(0);
        // stack: factory serviceName context
        tryStart = code->getPosition();
        code->instrInvokeinterface(
            "com/sun/star/lang/XMultiComponentFactory",
            "createInstanceWithContext",
            ("(Ljava/lang/String;Lcom/sun/star/uno/XComponentContext;)"
             "Ljava/lang/Object;"),
            3);
        tryEnd = code->getPosition();
        // stack: instance
        stack = 3;
    } else {
        if (constructor.parameters.size() == 1
            && constructor.parameters[0].rest)
        {
            desc.addParameter("any", true, true, nullptr);
            code->loadLocalReference(localIndex++);
            // stack: factory serviceName args
            stack = 4;
            access = static_cast< ClassFile::AccessFlags >(
                access | ClassFile::ACC_VARARGS);
        } else {
            code->loadIntegerConstant(constructor.parameters.size());
            // stack: factory serviceName N
            code->instrAnewarray("java/lang/Object");
            // stack: factory serviceName args
            stack = 0;
            sal_Int32 n = 0;
            for (const unoidl::SingleInterfaceBasedServiceEntity::Constructor::Parameter& param :
                         constructor.parameters)
            {
                desc.addParameter(param.type, false, true, nullptr);
                code->instrDup();
                // stack: factory serviceName args args
                code->loadIntegerConstant(n++);
                // stack: factory serviceName args args i
                stack = std::max(
                    stack,
                    addLoadLocal(
                        manager, code.get(), &localIndex, false, param.type, true,
                        dependencies));
                // stack: factory serviceName args args i any
                code->instrAastore();
                // stack: factory serviceName args
            }
            stack += 5;
        }
        code->loadLocalReference(0);
        // stack: factory serviceName args context
        tryStart = code->getPosition();
        code->instrInvokeinterface(
            "com/sun/star/lang/XMultiComponentFactory",
            "createInstanceWithArgumentsAndContext",
            ("(Ljava/lang/String;[Ljava/lang/Object;"
             "Lcom/sun/star/uno/XComponentContext;)Ljava/lang/Object;"),
            4);
        tryEnd = code->getPosition();
        // stack: instance
        createExceptionsAttribute(
            manager, constructor.exceptions, dependencies, &exc, &tree);
    }
    code->loadLocalReference(0);
    // stack: instance context
    code->instrInvokestatic(
        className, "$castInstance",
        ("(Ljava/lang/Object;Lcom/sun/star/uno/XComponentContext;)"
         "Ljava/lang/Object;"));
    // stack: instance
    code->instrCheckcast(
        codemaker::convertString(returnType).replace('.', '/'));
    // stack: instance
    code->instrAreturn();
    if (!tree.getRoot().present) {
        ClassFile::Code::Position pos1 = code->getPosition();
        // stack: e
        code->instrInvokevirtual(
            "java/lang/Throwable", "toString", "()Ljava/lang/String;");
        // stack: str
        localIndex = std::max< sal_uInt16 >(localIndex, 2);
        code->storeLocalReference(1);
        // stack: -
        code->instrNew("com/sun/star/uno/DeploymentException");
        // stack: ex
        code->instrDup();
        // stack: ex ex
        code->loadStringConstant(
            "component context fails to supply service " + unoName + " of type "
            + realJavaBaseName + ": ");
        // stack: ex ex "..."
        code->loadLocalReference(1);
        // stack: ex ex "..." str
        code->instrInvokevirtual(
            "java/lang/String", "concat",
            "(Ljava/lang/String;)Ljava/lang/String;");
        // stack: ex ex "..."
        code->loadLocalReference(0);
        // stack: ex ex "..." context
        code->instrInvokespecial(
            "com/sun/star/uno/DeploymentException", "<init>",
            "(Ljava/lang/String;Ljava/lang/Object;)V");
        // stack: ex
        ClassFile::Code::Position pos2 = code->getPosition();
        code->instrAthrow();
        addExceptionHandlers(
            &tree.getRoot(), tryStart, tryEnd, pos2, code.get());
        code->addException(
            tryStart, tryEnd, pos1, "com/sun/star/uno/Exception");
        dependencies->insert("com.sun.star.uno.Exception");
        stack = std::max< sal_uInt16 >(stack, 4);
    }
    code->setMaxStackAndLocals(stack, localIndex);
    classFile->addMethod(
        access,
        codemaker::java::translateUnoToJavaIdentifier(
            (constructor.defaultConstructor
             ? OString("create") : codemaker::convertString(constructor.name)),
            "method"),
        desc.getDescriptor(), code.get(), exc, desc.getSignature());
}

void handleService(
    const OUString& name,
    rtl::Reference< unoidl::SingleInterfaceBasedServiceEntity > const & entity,
    rtl::Reference< TypeManager > const & manager, JavaOptions const & options,
    std::set<OUString> * dependencies)
{
    assert(entity.is());
    assert(dependencies != nullptr);
    OString unoName(codemaker::convertString(name));
    OString className(
        translateUnoidlEntityNameToJavaFullyQualifiedName(name, "service"));
    std::unique_ptr< ClassFile > cf(
        new ClassFile(
            static_cast< ClassFile::AccessFlags >(
                ClassFile::ACC_PUBLIC | ClassFile::ACC_FINAL
                | ClassFile::ACC_SUPER),
            className, "java/lang/Object", ""));
    if (!entity->getConstructors().empty()) {
        OString realJavaBaseName(
            codemaker::convertString(entity->getBase()));
        dependencies->insert(entity->getBase());
        dependencies->insert("com.sun.star.lang.XMultiComponentFactory");
        dependencies->insert("com.sun.star.uno.DeploymentException");
        dependencies->insert("com.sun.star.uno.TypeClass");
        dependencies->insert("com.sun.star.uno.XComponentContext");
        for (const unoidl::SingleInterfaceBasedServiceEntity::Constructor& cons :
                 entity->getConstructors())
        {
            addConstructor(
                manager, realJavaBaseName, unoName, className, cons,
                entity->getBase(), dependencies, cf.get());
        }
        // Synthetic castInstance method:
        {
            std::unique_ptr< ClassFile::Code > code(cf->newCode());
            code->instrNew("com/sun/star/uno/Type");
            // stack: type
            code->instrDup();
            // stack: type type
            code->loadStringConstant(realJavaBaseName);
            // stack: type type "..."
            code->instrGetstatic(
                "com/sun/star/uno/TypeClass", "INTERFACE",
                "Lcom/sun/star/uno/TypeClass;");
            // stack: type type "..." INTERFACE
            code->instrInvokespecial(
                "com/sun/star/uno/Type", "<init>",
                "(Ljava/lang/String;Lcom/sun/star/uno/TypeClass;)V");
            // stack: type
            code->loadLocalReference(0);
            // stack: type instance
            code->instrInvokestatic(
                "com/sun/star/uno/UnoRuntime", "queryInterface",
                ("(Lcom/sun/star/uno/Type;Ljava/lang/Object;)"
                 "Ljava/lang/Object;"));
            // stack: instance
            code->instrDup();
            // stack: instance instance
            ClassFile::Code::Branch branch = code->instrIfnull();
            // stack: instance
            code->instrAreturn();
            code->branchHere(branch);
            code->instrPop();
            // stack: -
            code->instrNew("com/sun/star/uno/DeploymentException");
            // stack: ex
            code->instrDup();
            // stack: ex ex
            code->loadStringConstant(
                "component context fails to supply service " + unoName
                + " of type " + realJavaBaseName);
            // stack: ex ex "..."
            code->loadLocalReference(1);
            // stack: ex ex "..." context
            code->instrInvokespecial(
                "com/sun/star/uno/DeploymentException", "<init>",
                "(Ljava/lang/String;Ljava/lang/Object;)V");
            // stack: ex
            code->instrAthrow();
            code->setMaxStackAndLocals(4, 2);
            cf->addMethod(
                static_cast< ClassFile::AccessFlags >(
                    ClassFile::ACC_PRIVATE | ClassFile::ACC_STATIC
                    | ClassFile::ACC_SYNTHETIC),
                "$castInstance",
                ("(Ljava/lang/Object;Lcom/sun/star/uno/XComponentContext;)"
                 "Ljava/lang/Object;"),
                code.get(), std::vector< OString >(), "");
        }
    }
    writeClassFile(options, className, *cf);
}

void handleSingleton(
    const OUString& name,
    rtl::Reference< unoidl::InterfaceBasedSingletonEntity > const & entity,
    rtl::Reference< TypeManager > const & manager, JavaOptions const & options,
    std::set<OUString> * dependencies)
{
    assert(entity.is());
    assert(dependencies != nullptr);
    OString realJavaBaseName(codemaker::convertString(entity->getBase()));
    OString base(realJavaBaseName.replace('.', '/'));
    dependencies->insert(entity->getBase());
    OString unoName(codemaker::convertString(name));
    OString className(
        translateUnoidlEntityNameToJavaFullyQualifiedName(name, "singleton"));
    dependencies->insert("com.sun.star.uno.DeploymentException");
    dependencies->insert("com.sun.star.uno.TypeClass");
    dependencies->insert("com.sun.star.uno.XComponentContext");
    std::unique_ptr< ClassFile > cf(
        new ClassFile(
            static_cast< ClassFile::AccessFlags >(
                ClassFile::ACC_PUBLIC | ClassFile::ACC_FINAL
                | ClassFile::ACC_SUPER),
            className, "java/lang/Object", ""));
    MethodDescriptor desc(manager, dependencies, entity->getBase(), nullptr, nullptr);
    desc.addParameter("com.sun.star.uno.XComponentContext", false, false, nullptr);
    std::unique_ptr< ClassFile::Code > code(cf->newCode());
    code->loadLocalReference(0);
    // stack: context
    code->loadStringConstant("/singletons/" + unoName);
    // stack: context "..."
    code->instrInvokeinterface(
        "com/sun/star/uno/XComponentContext", "getValueByName",
        "(Ljava/lang/String;)Ljava/lang/Object;", 2);
    // stack: value
    code->instrDup();
    // stack: value value
    code->instrInstanceof("com/sun/star/uno/Any");
    // stack: value 0/1
    ClassFile::Code::Branch branch1 = code->instrIfeq();
    // stack: value
    code->instrCheckcast("com/sun/star/uno/Any");
    // stack: value
    code->instrDup();
    // stack: value value
    code->instrInvokevirtual(
        "com/sun/star/uno/Any", "getType", "()Lcom/sun/star/uno/Type;");
    // stack: value type
    code->instrInvokevirtual(
        "com/sun/star/uno/Type", "getTypeClass",
        "()Lcom/sun/star/uno/TypeClass;");
    // stack: value typeClass
    code->instrGetstatic(
        "com/sun/star/uno/TypeClass", "INTERFACE",
        "Lcom/sun/star/uno/TypeClass;");
    // stack: value typeClass INTERFACE
    ClassFile::Code::Branch branch2 = code->instrIfAcmpne();
    // stack: value
    code->instrInvokevirtual(
        "com/sun/star/uno/Any", "getObject", "()Ljava/lang/Object;");
    // stack: value
    code->branchHere(branch1);
    code->instrNew("com/sun/star/uno/Type");
    // stack: value type
    code->instrDup();
    // stack: value type type
    code->loadStringConstant(realJavaBaseName);
    // stack: value type type "..."
    code->instrGetstatic(
        "com/sun/star/uno/TypeClass", "INTERFACE",
        "Lcom/sun/star/uno/TypeClass;");
    // stack: value type type "..." INTERFACE
    code->instrInvokespecial(
        "com/sun/star/uno/Type", "<init>",
        "(Ljava/lang/String;Lcom/sun/star/uno/TypeClass;)V");
    // stack: value type
    code->instrSwap();
    // stack: type value
    code->instrInvokestatic(
        "com/sun/star/uno/UnoRuntime", "queryInterface",
        "(Lcom/sun/star/uno/Type;Ljava/lang/Object;)Ljava/lang/Object;");
    // stack: instance
    code->instrDup();
    // stack: instance instance
    ClassFile::Code::Branch branch3 = code->instrIfnull();
    // stack: instance
    code->instrCheckcast(base);
    // stack: instance
    code->instrAreturn();
    code->branchHere(branch2);
    code->branchHere(branch3);
    code->instrPop();
    // stack: -
    code->instrNew("com/sun/star/uno/DeploymentException");
    // stack: ex
    code->instrDup();
    // stack: ex ex
    code->loadStringConstant(
        "component context fails to supply singleton " + unoName + " of type "
        + realJavaBaseName);
    // stack: ex ex "..."
    code->loadLocalReference(0);
    // stack: ex ex "..." context
    code->instrInvokespecial(
        "com/sun/star/uno/DeploymentException", "<init>",
        "(Ljava/lang/String;Ljava/lang/Object;)V");
    // stack: ex
    code->instrAthrow();
    code->setMaxStackAndLocals(5, 1);
    cf->addMethod(
        static_cast< ClassFile::AccessFlags >(
            ClassFile::ACC_PUBLIC | ClassFile::ACC_STATIC),
        "get", desc.getDescriptor(), code.get(), std::vector< OString >(),
        desc.getSignature());
    writeClassFile(options, className, *cf);
}

}

void produce(
    OUString const & name, rtl::Reference< TypeManager > const & manager,
    codemaker::GeneratedTypeSet & generated, JavaOptions const & options)
{
    if (generated.contains(u2b(name))) {
        return;
    }
    generated.add(u2b(name));
    if (!manager->foundAtPrimaryProvider(name)) {
        return;
    }
    std::set<OUString> deps;
    rtl::Reference< unoidl::Entity > ent;
    rtl::Reference< unoidl::MapCursor > cur;
    switch (manager->getSort(name, &ent, &cur)) {
    case codemaker::UnoType::Sort::Module:
        {
            OUString prefix;
            if (!name.isEmpty()) {
                prefix = name + ".";
            }
            for (;;) {
                OUString mem;
                if (!cur->getNext(&mem).is()) {
                    break;
                }
                produce(prefix + mem, manager, generated, options);
            }
            return;
        }
    case codemaker::UnoType::Sort::Enum:
        handleEnumType(
            name, dynamic_cast< unoidl::EnumTypeEntity * >(ent.get()), options);
        break;
    case codemaker::UnoType::Sort::PlainStruct:
        handlePlainStructType(
            name, dynamic_cast< unoidl::PlainStructTypeEntity * >(ent.get()),
            manager, options, &deps);
        break;
    case codemaker::UnoType::Sort::PolymorphicStructTemplate:
        handlePolyStructType(
            name,
            dynamic_cast< unoidl::PolymorphicStructTypeTemplateEntity * >(
                ent.get()),
            manager, options, &deps);
        break;
    case codemaker::UnoType::Sort::Exception:
        handleExceptionType(
            name, dynamic_cast< unoidl::ExceptionTypeEntity * >(ent.get()),
            manager, options, &deps);
        break;
    case codemaker::UnoType::Sort::Interface:
        handleInterfaceType(
            name, dynamic_cast< unoidl::InterfaceTypeEntity * >(ent.get()),
            manager, options, &deps);
        break;
    case codemaker::UnoType::Sort::Typedef:
        handleTypedef(
            dynamic_cast< unoidl::TypedefEntity * >(ent.get()), manager, &deps);
        break;
    case codemaker::UnoType::Sort::ConstantGroup:
        handleConstantGroup(
            name, dynamic_cast< unoidl::ConstantGroupEntity * >(ent.get()),
            manager, options, &deps);
        break;
    case codemaker::UnoType::Sort::SingleInterfaceBasedService:
        handleService(
            name,
            dynamic_cast< unoidl::SingleInterfaceBasedServiceEntity * >(
                ent.get()),
            manager, options, &deps);
        break;
    case codemaker::UnoType::Sort::InterfaceBasedSingleton:
        handleSingleton(
            name,
            dynamic_cast< unoidl::InterfaceBasedSingletonEntity * >(ent.get()),
            manager, options, &deps);
        break;
    case codemaker::UnoType::Sort::AccumulationBasedService:
    case codemaker::UnoType::Sort::ServiceBasedSingleton:
        break;
    default:
        throw CannotDumpException(
            "unexpected entity \"" + name + "\" in call to produce");
    }
    if (!options.isValid("-nD")) {
        for (const OUString& d : deps) {
            produce(d, manager, generated, options);
        }
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
