// Copyright (c) 2013, Kenton Varda // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // This program is a code generator plugin for `capnp compile` which generates java code. // It is a modified version of the C++ code generator plugin, capnpc-c++. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_CONFIG_H #include "config.h" #endif #ifndef VERSION #define VERSION "(unknown)" #endif namespace capnp { namespace { static constexpr uint64_t OUTER_CLASSNAME_ANNOTATION_ID = 0x9b066bb4881f7cd3; static constexpr uint64_t PACKAGE_ANNOTATION_ID = 0x9ee4c8f803b3b596ull; static constexpr const char* FIELD_SIZE_NAMES[] = { "VOID", "BIT", "BYTE", "TWO_BYTES", "FOUR_BYTES", "EIGHT_BYTES", "POINTER", "INLINE_COMPOSITE" }; bool hasDiscriminantValue(const schema::Field::Reader& reader) { return reader.getDiscriminantValue() != schema::Field::NO_DISCRIMINANT; } void enumerateDeps(schema::Type::Reader type, std::set& deps) { switch (type.which()) { case schema::Type::STRUCT: deps.insert(type.getStruct().getTypeId()); break; case schema::Type::ENUM: deps.insert(type.getEnum().getTypeId()); break; case schema::Type::INTERFACE: deps.insert(type.getInterface().getTypeId()); break; case schema::Type::LIST: enumerateDeps(type.getList().getElementType(), deps); break; default: break; } } void enumerateDeps(schema::Node::Reader node, std::set& deps) { switch (node.which()) { case schema::Node::STRUCT: { auto structNode = node.getStruct(); for (auto field: structNode.getFields()) { switch (field.which()) { case schema::Field::SLOT: enumerateDeps(field.getSlot().getType(), deps); break; case schema::Field::GROUP: deps.insert(field.getGroup().getTypeId()); break; } } if (structNode.getIsGroup()) { deps.insert(node.getScopeId()); } break; } case schema::Node::INTERFACE: { auto interfaceNode = node.getInterface(); for (auto extend: interfaceNode.getExtends()) { deps.insert(extend); } for (auto method: interfaceNode.getMethods()) { deps.insert(method.getParamStructType()); deps.insert(method.getResultStructType()); } break; } default: break; } } struct OrderByName { template inline bool operator()(const T& a, const T& b) const { return a.getProto().getName() < b.getProto().getName(); } }; template kj::Array makeMembersByName(MemberList&& members) { auto sorted = KJ_MAP(member, members) { return member; }; std::sort(sorted.begin(), sorted.end(), OrderByName()); return KJ_MAP(member, sorted) { return member.getIndex(); }; } kj::StringPtr baseName(kj::StringPtr path) { KJ_IF_MAYBE(slashPos, path.findLast('/')) { return path.slice(*slashPos + 1); } else { return path; } } kj::String safeIdentifier(kj::StringPtr identifier) { // Given a desired identifier name, munge it to make it safe for use in generated code. // // If the identifier is a keyword, this adds an underscore to the end. static const std::set keywords({ "alignas", "alignof", "and", "and_eq", "asm", "auto", "bitand", "bitor", "bool", "break", "case", "catch", "char", "char16_t", "char32_t", "class", "compl", "const", "constexpr", "const_cast", "continue", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", "protected", "public", "register", "reinterpret_cast", "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "template", "this", "thread_local", "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq" }); if (keywords.count(identifier) > 0) { return kj::str(identifier, '_'); } else { return kj::heapString(identifier); } } kj::String spaces(int n) { return kj::str(kj::repeat(' ', n * 2)); } // ======================================================================================= class CapnpcJavaMain { public: CapnpcJavaMain(kj::ProcessContext& context): context(context) {} kj::MainFunc getMain() { return kj::MainBuilder(context, "Cap'n Proto Java plugin version " VERSION, "This is a Cap'n Proto compiler plugin which generates Java code." " This is meant to be run using the Cap'n Proto compiler, e.g.:\n" " capnp compile -ojava foo.capnp") .callAfterParsing(KJ_BIND_METHOD(*this, run)) .build(); } private: kj::ProcessContext& context; SchemaLoader schemaLoader; std::unordered_set usedImports; bool hasInterfaces = false; kj::StringTree javaFullName(Schema schema) { auto node = schema.getProto(); if (node.getScopeId() == 0) { usedImports.insert(node.getId()); for (auto annotation: node.getAnnotations()) { if (annotation.getId() == OUTER_CLASSNAME_ANNOTATION_ID) { return kj::strTree("", toTitleCase(annotation.getValue().getText())); } } return kj::strTree(" ");//kj::strTree(outerClassName); } else { Schema parent = schemaLoader.get(node.getScopeId()); for (auto nested: parent.getProto().getNestedNodes()) { if (nested.getId() == node.getId()) { return kj::strTree(javaFullName(parent), ".", nested.getName()); } } KJ_FAIL_REQUIRE("A schema Node's supposed scope did not contain the node as a NestedNode."); } } kj::String toUpperCase(kj::StringPtr name) { kj::Vector result(name.size() + 4); for (char c: name) { if ('a' <= c && c <= 'z') { result.add(c - 'a' + 'A'); } else if (result.size() > 0 && 'A' <= c && c <= 'Z') { result.add('_'); result.add(c); } else { result.add(c); } } result.add('\0'); return kj::String(result.releaseAsArray()); } kj::String toTitleCase(kj::StringPtr name) { kj::String result = kj::heapString(name); if ('a' <= result[0] && result[0] <= 'z') { result[0] = result[0] - 'a' + 'A'; } return kj::mv(result); } kj::StringTree typeName(schema::Type::Reader type) { switch (type.which()) { case schema::Type::VOID: return kj::strTree("org.capnproto.Void"); case schema::Type::BOOL: return kj::strTree("boolean"); case schema::Type::INT8: return kj::strTree("byte"); case schema::Type::INT16: return kj::strTree("short"); case schema::Type::INT32: return kj::strTree("int"); case schema::Type::INT64: return kj::strTree("long"); case schema::Type::UINT8: return kj::strTree("byte"); case schema::Type::UINT16: return kj::strTree("short"); case schema::Type::UINT32: return kj::strTree("int"); case schema::Type::UINT64: return kj::strTree("long"); case schema::Type::FLOAT32: return kj::strTree("float"); case schema::Type::FLOAT64: return kj::strTree("double"); case schema::Type::TEXT: return kj::strTree(" org.capnproto.Text"); case schema::Type::DATA: return kj::strTree(" org.capnproto.Data"); case schema::Type::ENUM: return javaFullName(schemaLoader.get(type.getEnum().getTypeId())); case schema::Type::STRUCT: return javaFullName(schemaLoader.get(type.getStruct().getTypeId())); case schema::Type::INTERFACE: return javaFullName(schemaLoader.get(type.getInterface().getTypeId())); case schema::Type::LIST: { auto elementType = type.getList().getElementType(); switch (elementType.which()) { case schema::Type::VOID: return kj::strTree(" org.capnproto.PrimitiveList.Void"); case schema::Type::BOOL: return kj::strTree(" org.capnproto.PrimitiveList.Boolean"); case schema::Type::INT8: case schema::Type::UINT8: return kj::strTree(" org.capnproto.PrimitiveList.Byte"); case schema::Type::INT16: case schema::Type::UINT16: return kj::strTree(" org.capnproto.PrimitiveList.Short"); case schema::Type::INT32: case schema::Type::UINT32: return kj::strTree(" org.capnproto.PrimitiveList.Int"); case schema::Type::INT64: case schema::Type::UINT64: return kj::strTree(" org.capnproto.PrimitiveList.Long"); case schema::Type::FLOAT32: return kj::strTree(" org.capnproto.PrimitiveList.Float"); case schema::Type::FLOAT64: return kj::strTree(" org.capnproto.PrimitiveList.Double"); case schema::Type::STRUCT: return kj::strTree(" org.capnproto.StructList"); case schema::Type::TEXT: return kj::strTree( "org.capnproto.TextList"); case schema::Type::DATA: return kj::strTree( "org.capnproto.DataList"); case schema::Type::ENUM: case schema::Type::INTERFACE: case schema::Type::ANY_POINTER: case schema::Type::LIST: KJ_FAIL_REQUIRE("unimplemented"); } KJ_UNREACHABLE; } case schema::Type::ANY_POINTER: // Not used. return kj::strTree(); } KJ_UNREACHABLE; } kj::StringTree literalValue(schema::Type::Reader type, schema::Value::Reader value) { switch (value.which()) { case schema::Value::VOID: return kj::strTree("org.capnproto.Void.VOID"); case schema::Value::BOOL: return kj::strTree(value.getBool() ? "true" : "false"); case schema::Value::INT8: return kj::strTree(value.getInt8()); case schema::Value::INT16: return kj::strTree(value.getInt16()); case schema::Value::INT32: return kj::strTree(value.getInt32()); case schema::Value::INT64: return kj::strTree(value.getInt64(), "L"); case schema::Value::UINT8: return kj::strTree(kj::implicitCast(value.getUint8())); case schema::Value::UINT16: return kj::strTree(kj::implicitCast(value.getUint16())); case schema::Value::UINT32: return kj::strTree(kj::implicitCast(value.getUint32())); case schema::Value::UINT64: return kj::strTree(kj::implicitCast(value.getUint64()), "L"); case schema::Value::FLOAT32: return kj::strTree(value.getFloat32(), "f"); case schema::Value::FLOAT64: return kj::strTree(value.getFloat64()); case schema::Value::ENUM: { EnumSchema schema = schemaLoader.get(type.getEnum().getTypeId()).asEnum(); if (value.getEnum() < schema.getEnumerants().size()) { return kj::strTree( javaFullName(schema), ".", toUpperCase(schema.getEnumerants()[value.getEnum()].getProto().getName())); } else { return kj::strTree("static_cast<", javaFullName(schema), ">(", value.getEnum(), ")"); } } case schema::Value::TEXT: case schema::Value::DATA: case schema::Value::STRUCT: case schema::Value::INTERFACE: case schema::Value::LIST: case schema::Value::ANY_POINTER: KJ_FAIL_REQUIRE("literalValue() can only be used on primitive types."); } KJ_UNREACHABLE; } // ----------------------------------------------------------------- // Code to deal with "slots" -- determines what to zero out when we clear a group. static uint typeSizeBits(schema::Type::Which whichType) { switch (whichType) { case schema::Type::BOOL: return 1; case schema::Type::INT8: return 8; case schema::Type::INT16: return 16; case schema::Type::INT32: return 32; case schema::Type::INT64: return 64; case schema::Type::UINT8: return 8; case schema::Type::UINT16: return 16; case schema::Type::UINT32: return 32; case schema::Type::UINT64: return 64; case schema::Type::FLOAT32: return 32; case schema::Type::FLOAT64: return 64; case schema::Type::ENUM: return 16; case schema::Type::VOID: case schema::Type::TEXT: case schema::Type::DATA: case schema::Type::LIST: case schema::Type::STRUCT: case schema::Type::INTERFACE: case schema::Type::ANY_POINTER: KJ_FAIL_REQUIRE("Should only be called for data types."); } KJ_UNREACHABLE; } enum class Section { NONE, DATA, POINTERS }; static Section sectionFor(schema::Type::Which whichType) { switch (whichType) { case schema::Type::VOID: return Section::NONE; case schema::Type::BOOL: case schema::Type::INT8: case schema::Type::INT16: case schema::Type::INT32: case schema::Type::INT64: case schema::Type::UINT8: case schema::Type::UINT16: case schema::Type::UINT32: case schema::Type::UINT64: case schema::Type::FLOAT32: case schema::Type::FLOAT64: case schema::Type::ENUM: return Section::DATA; case schema::Type::TEXT: case schema::Type::DATA: case schema::Type::LIST: case schema::Type::STRUCT: case schema::Type::INTERFACE: case schema::Type::ANY_POINTER: return Section::POINTERS; } KJ_UNREACHABLE; } static kj::StringPtr maskType(schema::Type::Which whichType) { switch (whichType) { case schema::Type::BOOL: return "boolean"; case schema::Type::INT8: return "byte"; case schema::Type::INT16: return "short"; case schema::Type::INT32: return "int"; case schema::Type::INT64: return "long"; case schema::Type::UINT8: return "byte"; case schema::Type::UINT16: return "short"; case schema::Type::UINT32: return "int"; case schema::Type::UINT64: return "long"; case schema::Type::FLOAT32: return "int"; case schema::Type::FLOAT64: return "long"; case schema::Type::ENUM: return "short"; case schema::Type::VOID: case schema::Type::TEXT: case schema::Type::DATA: case schema::Type::LIST: case schema::Type::STRUCT: case schema::Type::INTERFACE: case schema::Type::ANY_POINTER: KJ_FAIL_REQUIRE("Should only be called for data types."); } KJ_UNREACHABLE; } static kj::StringPtr maskZeroLiteral(schema::Type::Which whichType) { switch (whichType) { case schema::Type::BOOL: return "false"; case schema::Type::INT8: return "(byte)0"; case schema::Type::INT16: return "(short)0"; case schema::Type::INT32: return "0"; case schema::Type::INT64: return "0L"; case schema::Type::UINT8: return "(byte)0"; case schema::Type::UINT16: return "(short)0"; case schema::Type::UINT32: return "0"; case schema::Type::UINT64: return "0L"; case schema::Type::FLOAT32: return "0"; case schema::Type::FLOAT64: return "0L"; case schema::Type::ENUM: return "(short)0"; case schema::Type::VOID: case schema::Type::TEXT: case schema::Type::DATA: case schema::Type::LIST: case schema::Type::STRUCT: case schema::Type::INTERFACE: case schema::Type::ANY_POINTER: KJ_FAIL_REQUIRE("Should only be called for data types."); } KJ_UNREACHABLE; } struct Slot { schema::Type::Which whichType; uint offset; bool isSupersetOf(Slot other) const { auto section = sectionFor(whichType); if (section != sectionFor(other.whichType)) return false; switch (section) { case Section::NONE: return true; // all voids overlap case Section::DATA: { auto bits = typeSizeBits(whichType); auto start = offset * bits; auto otherBits = typeSizeBits(other.whichType); auto otherStart = other.offset * otherBits; return start <= otherStart && otherStart + otherBits <= start + bits; } case Section::POINTERS: return offset == other.offset; } KJ_UNREACHABLE; } bool operator<(Slot other) const { // Sort by section, then start position, and finally size. auto section = sectionFor(whichType); auto otherSection = sectionFor(other.whichType); if (section < otherSection) { return true; } else if (section > otherSection) { return false; } switch (section) { case Section::NONE: return false; case Section::DATA: { auto bits = typeSizeBits(whichType); auto start = offset * bits; auto otherBits = typeSizeBits(other.whichType); auto otherStart = other.offset * otherBits; if (start < otherStart) { return true; } else if (start > otherStart) { return false; } // Sort larger sizes before smaller. return bits > otherBits; } case Section::POINTERS: return offset < other.offset; } KJ_UNREACHABLE; } }; void getSlots(StructSchema schema, kj::Vector& slots) { auto structProto = schema.getProto().getStruct(); if (structProto.getDiscriminantCount() > 0) { slots.add(Slot { schema::Type::UINT16, structProto.getDiscriminantOffset() }); } for (auto field: schema.getFields()) { auto proto = field.getProto(); switch (proto.which()) { case schema::Field::SLOT: { auto slot = proto.getSlot(); slots.add(Slot { slot.getType().which(), slot.getOffset() }); break; } case schema::Field::GROUP: getSlots(schema.getDependency(proto.getGroup().getTypeId()).asStruct(), slots); break; } } } kj::Array getSortedSlots(StructSchema schema) { // Get a representation of all of the field locations owned by this schema, e.g. so that they // can be zero'd out. kj::Vector slots(schema.getFields().size()); getSlots(schema, slots); std::sort(slots.begin(), slots.end()); kj::Vector result(slots.size()); // All void slots are redundant, and they sort towards the front of the list. By starting out // with `prevSlot` = void, we will end up skipping them all, which is what we want. Slot prevSlot = { schema::Type::VOID, 0 }; for (auto slot: slots) { if (prevSlot.isSupersetOf(slot)) { // This slot is redundant as prevSlot is a superset of it. continue; } // Since all sizes are power-of-two, if two slots overlap at all, one must be a superset of // the other. Since we sort slots by starting position, we know that the only way `slot` // could be a superset of `prevSlot` is if they have the same starting position. However, // since we sort slots with the same starting position by descending size, this is not // possible. KJ_DASSERT(!slot.isSupersetOf(prevSlot)); result.add(slot); prevSlot = slot; } return result.releaseAsArray(); } // ----------------------------------------------------------------- struct DiscriminantChecks { kj::String has; kj::String check; kj::String set; kj::StringTree readerIsDecl; kj::StringTree builderIsDecl; kj::StringTree isDefs; }; DiscriminantChecks makeDiscriminantChecks(kj::StringPtr scope, kj::StringPtr memberName, StructSchema containingStruct, int indent) { auto discrimOffset = containingStruct.getProto().getStruct().getDiscriminantOffset(); kj::String titleCase = toTitleCase(memberName); kj::String upperCase = toUpperCase(memberName); return DiscriminantChecks { kj::str(spaces(indent), " if (which() != ", scope, "Which.", upperCase, ") return false;\n"), kj::str(), // XXX //kj::str( // " KJ_IREQUIRE(which() == ", scope, upperCase, ",\n" // " \"Must check which() before get()ing a union member.\");\n"), kj::str( spaces(indent), " _builder.setShortField(", discrimOffset, ", (short)", scope, "Which.", upperCase, ".ordinal());\n"), kj::strTree(spaces(indent), "public final boolean is", titleCase, "() {\n", spaces(indent), " return which() == ", scope, "Which.", upperCase,";\n", spaces(indent), "}\n"), kj::strTree(spaces(indent), "public final boolean is", titleCase, "() {\n", spaces(indent), " return which() == ", scope, "Which.", upperCase, ";\n", spaces(indent), "}\n"), kj::strTree( "inline boolean ", scope, "Reader::is", titleCase, "() const {\n" " return which() == ", scope, upperCase, ";\n" "}\n" "inline boolean ", scope, "Builder::is", titleCase, "() {\n" " return which() == ", scope, upperCase, ";\n" "}\n") }; } // ----------------------------------------------------------------- struct FieldText { kj::StringTree readerMethodDecls; kj::StringTree builderMethodDecls; kj::StringTree pipelineMethodDecls; kj::StringTree inlineMethodDefs; }; enum class FieldKind { PRIMITIVE, BLOB, STRUCT, LIST, INTERFACE, ANY_POINTER }; kj::StringTree makeEnumGetter(EnumSchema schema, kj::String member, uint offset, int indent) { auto enumerants = schema.getEnumerants(); return kj::strTree( spaces(indent), "switch(", member, ".getShortField(", offset, ")) {\n", KJ_MAP(e, enumerants) { return kj::strTree(spaces(indent+1), "case ", e.getOrdinal(), " : return ", javaFullName(schema), ".", toUpperCase(e.getProto().getName()), ";\n"); }, spaces(indent+1), "default: return ", javaFullName(schema), "._UNKNOWN;\n", spaces(indent), "}\n" ); } FieldText makeFieldText(kj::StringPtr scope, StructSchema::Field field, int indent) { auto proto = field.getProto(); kj::String titleCase = toTitleCase(proto.getName()); DiscriminantChecks unionDiscrim; if (hasDiscriminantValue(proto)) { unionDiscrim = makeDiscriminantChecks(scope, proto.getName(), field.getContainingStruct(), indent + 1); } switch (proto.which()) { case schema::Field::SLOT: // Continue below. break; case schema::Field::GROUP: { auto slots = getSortedSlots(schemaLoader.get( field.getProto().getGroup().getTypeId()).asStruct()); return FieldText { kj::strTree( kj::mv(unionDiscrim.readerIsDecl), spaces(indent), " public ", titleCase, ".Reader get", titleCase, "() {\n", spaces(indent), " return new ", scope, titleCase, ".Reader(_reader);\n", spaces(indent), " }\n", "\n"), kj::strTree( kj::mv(unionDiscrim.builderIsDecl), spaces(indent), " public final ", titleCase, ".Builder get", titleCase, "() {\n", spaces(indent), " return new ", scope, titleCase, ".Builder(_builder);\n", spaces(indent), " }\n", spaces(indent), " public final ", titleCase, ".Builder init", titleCase, "() {\n", unionDiscrim.set, KJ_MAP(slot, slots) { switch (sectionFor(slot.whichType)) { case Section::NONE: return kj::strTree(); case Section::DATA: return kj::strTree( spaces(indent), " _builder.set", toTitleCase(maskType(slot.whichType)), "Field(", slot.offset, ",", maskZeroLiteral(slot.whichType), ");\n"); case Section::POINTERS: return kj::strTree( spaces(indent), " _builder.getPointerField(", slot.offset, ").clear();\n"); } KJ_UNREACHABLE; }, " return new ", scope, titleCase, ".Builder(_builder);\n", spaces(indent), " }\n", "\n"), kj::strTree(), kj::strTree() }; } } auto slot = proto.getSlot(); FieldKind kind = FieldKind::PRIMITIVE; kj::String ownedType; kj::String type = typeName(slot.getType()).flatten(); kj::StringPtr setterDefault; // only for void kj::String defaultMask; // primitives only size_t defaultOffset = 0; // pointers only: offset of the default value within the schema. size_t defaultSize = 0; // blobs only: byte size of the default value. auto typeBody = slot.getType(); auto defaultBody = slot.getDefaultValue(); switch (typeBody.which()) { case schema::Type::VOID: kind = FieldKind::PRIMITIVE; setterDefault = " = ::capnp::VOID"; break; #define HANDLE_PRIMITIVE(discrim, typeName, javaTypeName, defaultName, suffix) \ case schema::Type::discrim: \ kind = FieldKind::PRIMITIVE; \ if (defaultBody.get##defaultName() != 0) { \ defaultMask = kj::str("(", #javaTypeName, ")", kj::implicitCast< typeName>(defaultBody.get##defaultName()), #suffix); \ } \ break; HANDLE_PRIMITIVE(BOOL, bool, boolean, Bool, ); HANDLE_PRIMITIVE(INT8 , ::int8_t , byte, Int8 , ); HANDLE_PRIMITIVE(INT16, ::int16_t, short, Int16, ); HANDLE_PRIMITIVE(INT32, ::int32_t, int, Int32, ); HANDLE_PRIMITIVE(INT64, ::int64_t, long, Int64, L); HANDLE_PRIMITIVE(UINT8 , ::int8_t , byte, Uint8 , ); HANDLE_PRIMITIVE(UINT16, ::int16_t, short, Uint16, ); HANDLE_PRIMITIVE(UINT32, ::int32_t, int, Uint32, ); HANDLE_PRIMITIVE(UINT64, ::int64_t, long, Uint64, L); #undef HANDLE_PRIMITIVE case schema::Type::FLOAT32: kind = FieldKind::PRIMITIVE; if (defaultBody.getFloat32() != 0) { int32_t mask; float value = defaultBody.getFloat32(); static_assert(sizeof(mask) == sizeof(value), "bug"); memcpy(&mask, &value, sizeof(mask)); defaultMask = kj::str(mask); } break; case schema::Type::FLOAT64: kind = FieldKind::PRIMITIVE; if (defaultBody.getFloat64() != 0) { int64_t mask; double value = defaultBody.getFloat64(); static_assert(sizeof(mask) == sizeof(value), "bug"); memcpy(&mask, &value, sizeof(mask)); defaultMask = kj::str(mask, "L"); } break; case schema::Type::TEXT: kind = FieldKind::BLOB; if (defaultBody.hasText()) { defaultOffset = field.getDefaultValueSchemaOffset(); defaultSize = defaultBody.getText().size(); } break; case schema::Type::DATA: kind = FieldKind::BLOB; if (defaultBody.hasData()) { defaultOffset = field.getDefaultValueSchemaOffset(); defaultSize = defaultBody.getData().size(); } break; case schema::Type::ENUM: kind = FieldKind::PRIMITIVE; if (defaultBody.getEnum() != 0) { defaultMask = kj::str(defaultBody.getEnum(), "u"); } break; case schema::Type::STRUCT: kind = FieldKind::STRUCT; if (defaultBody.hasStruct()) { defaultOffset = field.getDefaultValueSchemaOffset(); } break; case schema::Type::LIST: kind = FieldKind::LIST; if (defaultBody.hasList()) { defaultOffset = field.getDefaultValueSchemaOffset(); } break; case schema::Type::INTERFACE: kind = FieldKind::INTERFACE; break; case schema::Type::ANY_POINTER: kind = FieldKind::ANY_POINTER; if (defaultBody.hasAnyPointer()) { defaultOffset = field.getDefaultValueSchemaOffset(); } break; } kj::String defaultMaskParam; if (defaultMask.size() > 0) { defaultMaskParam = kj::str(", ", defaultMask); } uint offset = slot.getOffset(); auto structSchema = field.getContainingStruct(); if (kind == FieldKind::PRIMITIVE) { return FieldText { kj::strTree( kj::mv(unionDiscrim.readerIsDecl), spaces(indent), " public final ", type, " get", titleCase, "() {\n", (typeBody.which() == schema::Type::ENUM ? makeEnumGetter(structSchema.getDependency(typeBody.getEnum().getTypeId()).asEnum(), kj::str("_reader"), offset, indent + 2) : (typeBody.which() == schema::Type::VOID ? kj::strTree(spaces(indent), " return org.capnproto.Void.VOID;\n") : kj::strTree(spaces(indent), " return _reader.get",toTitleCase(type),"Field(", offset, defaultMaskParam, ");\n"))), spaces(indent), " }\n", "\n"), kj::strTree( kj::mv(unionDiscrim.builderIsDecl), spaces(indent), " public final ", type, " get", titleCase, "() {\n", (typeBody.which() == schema::Type::ENUM ? makeEnumGetter(structSchema.getDependency(typeBody.getEnum().getTypeId()).asEnum(), kj::str("_builder"), offset, indent + 2) : (typeBody.which() == schema::Type::VOID ? kj::strTree(spaces(indent), " return org.capnproto.Void.VOID;\n") : kj::strTree(spaces(indent), " return _builder.get",toTitleCase(type),"Field(", offset, defaultMaskParam, ");\n"))), spaces(indent), " }\n", spaces(indent), " public final void set", titleCase, "(", type, " value) {\n", unionDiscrim.set, (typeBody.which() == schema::Type::ENUM ? kj::strTree(spaces(indent), " _builder.setShortField(", offset, ", (short)value.ordinal());\n") : (typeBody.which() == schema::Type::VOID ? kj::strTree() : kj::strTree(spaces(indent), " _builder.set", toTitleCase(type), "Field(", offset, ", value", defaultMaskParam, ");\n"))), spaces(indent), " }\n", "\n"), kj::strTree(), kj::strTree(), }; } else if (kind == FieldKind::INTERFACE) { KJ_FAIL_REQUIRE("interfaces unimplemented"); } else if (kind == FieldKind::ANY_POINTER) { return FieldText { kj::strTree( kj::mv(unionDiscrim.readerIsDecl), spaces(indent), " public boolean has", titleCase, "() {\n", unionDiscrim.has, spaces(indent), " return !_reader.getPointerField(", offset, ").isNull();\n", spaces(indent), " }\n", spaces(indent), " public org.capnproto.AnyPointer.Reader get", titleCase, "() {\n", unionDiscrim.check, spaces(indent), " return new org.capnproto.AnyPointer.Reader(_reader.getPointerField(", offset,"));\n", spaces(indent), " }\n"), kj::strTree( kj::mv(unionDiscrim.builderIsDecl), spaces(indent), " public final boolean has", titleCase, "() {\n", spaces(indent), " return !_builder.getPointerField(", offset, ").isNull();\n", spaces(indent), " }\n", spaces(indent), " public org.capnproto.AnyPointer.Builder get", titleCase, "() {\n", unionDiscrim.check, spaces(indent), " return new org.capnproto.AnyPointer.Builder(_builder.getPointerField(", offset, "));\n", spaces(indent), " }\n", //" inline ::capnp::AnyPointer::Builder init", titleCase, "();\n" "\n"), kj::strTree(), kj::strTree( "inline ::capnp::AnyPointer::Builder ", scope, "Builder::init", titleCase, "() {\n", unionDiscrim.set, " auto result = ::capnp::AnyPointer::Builder(\n" " _builder.getPointerField(", offset, " * ::capnp::POINTERS));\n" " result.clear();\n" " return result;\n" "}\n" "\n") }; } else if (kind == FieldKind::STRUCT) { return FieldText { kj::strTree( kj::mv(unionDiscrim.readerIsDecl), spaces(indent), " public boolean has", titleCase, "() {\n", spaces(indent), " return !_reader.getPointerField(", offset, ").isNull();\n", spaces(indent), " }\n", spaces(indent), " public ", type, ".Reader get", titleCase, "() {\n", spaces(indent), " return ", type, ".factory.fromStructReader(_reader.getPointerField(", offset,").getStruct());\n", spaces(indent), " }\n", "\n"), kj::strTree( kj::mv(unionDiscrim.builderIsDecl), spaces(indent), " public final ", type, ".Builder get", titleCase, "() {\n", spaces(indent), " return ", type, ".factory.fromStructBuilder(_builder.getPointerField(", offset, ").getStruct(", type, ".STRUCT_SIZE", "));\n", spaces(indent), " }\n", spaces(indent), " public final void set", titleCase, "(", type, ".Reader value) {\n", unionDiscrim.set, spaces(indent), " throw new Error();\n", spaces(indent), " }\n", spaces(indent), " public final ", type, ".Builder init", titleCase, "() {\n", unionDiscrim.set, spaces(indent), " return ", type, ".factory.fromStructBuilder(_builder.getPointerField(", offset, ").initStruct(", type, ".STRUCT_SIZE", "));\n", spaces(indent), " }\n"), kj::strTree(), kj::strTree() }; } else if (kind == FieldKind::BLOB && typeBody.which() == schema::Type::TEXT ) { kj::String blobKind = kj::str("Text"); return FieldText { kj::strTree( kj::mv(unionDiscrim.readerIsDecl), spaces(indent), " public boolean has", titleCase, "() {\n", unionDiscrim.has, spaces(indent), " return !_reader.getPointerField(", offset, ").isNull();\n", spaces(indent), " }\n", spaces(indent), " public ", type, ".Reader", " get", titleCase, "() {\n", spaces(indent), " return _reader.getPointerField(", offset, ").get", blobKind, " ();\n", // XXX spaces(indent), " }\n", "\n"), kj::strTree( kj::mv(unionDiscrim.builderIsDecl), spaces(indent), " public final boolean has", titleCase, "() {\n", unionDiscrim.has, spaces(indent), " return !_builder.getPointerField(", offset, ").isNull();\n", spaces(indent), " }\n", spaces(indent), " public final ", type, ".Builder get", titleCase, "() {\n", spaces(indent), " return _builder.getPointerField(", offset, ").get", blobKind, " ();\n", // XXX spaces(indent), " }\n", spaces(indent), " public final void set", titleCase, "(", type, ".Reader value) {\n", unionDiscrim.set, spaces(indent), " _builder.getPointerField(", offset, ").set", blobKind, "(value);\n", spaces(indent), " }\n", spaces(indent), " public final void set", titleCase, "(String value) {\n", unionDiscrim.set, spaces(indent), " _builder.getPointerField(", offset, ").set", blobKind, "( new", type, ".Reader(value));\n", spaces(indent), " }\n", spaces(indent), " public final ", type, ".Builder init", titleCase, "(int size) {\n", spaces(indent), " throw new Error();\n", spaces(indent), " }\n"), kj::strTree(), kj::strTree() }; } else if (kind == FieldKind::BLOB && typeBody.which() == schema::Type::DATA ) { kj::String blobKind = kj::str("Data"); return FieldText { kj::strTree( kj::mv(unionDiscrim.readerIsDecl), spaces(indent), " public boolean has", titleCase, "() {\n", unionDiscrim.has, spaces(indent), " return !_reader.getPointerField(", offset, ").isNull();\n", spaces(indent), " }\n", spaces(indent), " public ", type, ".Reader", " get", titleCase, "() {\n", spaces(indent), " return _reader.getPointerField(", offset, ").get", blobKind, " ();\n", // XXX spaces(indent), " }\n", "\n"), kj::strTree( kj::mv(unionDiscrim.builderIsDecl), spaces(indent), " public final boolean has", titleCase, "() {\n", unionDiscrim.has, spaces(indent), " return !_builder.getPointerField(", offset, ").isNull();\n", spaces(indent), " }\n", spaces(indent), " public final ", type, ".Builder get", titleCase, "() {\n", spaces(indent), " return _builder.getPointerField(", offset, ").get", blobKind, " ();\n", // XXX spaces(indent), " }\n", spaces(indent), " public final void set", titleCase, "(", type, ".Reader value) {\n", unionDiscrim.set, spaces(indent), " _builder.getPointerField(", offset, ").set", blobKind, "(value);\n", spaces(indent), " }\n", spaces(indent), " public final ", type, ".Builder init", titleCase, "(int size) {\n", spaces(indent), " throw new Error();\n", spaces(indent), " }\n"), kj::strTree(), kj::strTree() }; } else if (kind == FieldKind::LIST) { uint64_t typeId = field.getContainingStruct().getProto().getId(); kj::String defaultParam = defaultOffset == 0 ? kj::str() : kj::str( ",\n ::capnp::schemas::s_", kj::hex(typeId), ".encodedNode + ", defaultOffset, defaultSize == 0 ? kj::strTree() : kj::strTree(", ", defaultSize)); kj::String elementReaderType; kj::String elementBuilderType; kj::String builderFactoryArg = kj::str(""); kj::String readerFactoryArg = kj::str(""); kj::String fieldSize; kj::String readerClass = kj::str("Reader"); kj::String builderClass = kj::str("Builder"); bool isStructOrCapList = false; bool isStructList = false; if (kind == FieldKind::LIST) { bool primitiveElement = false; bool interface = false; switch (typeBody.getList().getElementType().which()) { case schema::Type::VOID: primitiveElement = true; fieldSize = kj::str("org.capnproto.FieldSize.VOID"); break; case schema::Type::BOOL: primitiveElement = true; fieldSize = kj::str("org.capnproto.FieldSize.BIT"); break; case schema::Type::INT8: case schema::Type::UINT8: primitiveElement = true; fieldSize = kj::str("org.capnproto.FieldSize.BYTE"); break; case schema::Type::INT16: case schema::Type::UINT16: primitiveElement = true; fieldSize = kj::str("org.capnproto.FieldSize.TWO_BYTES"); break; case schema::Type::INT32: case schema::Type::UINT32: case schema::Type::FLOAT32: primitiveElement = true; fieldSize = kj::str("org.capnproto.FieldSize.FOUR_BYTES"); break; case schema::Type::INT64: case schema::Type::UINT64: case schema::Type::FLOAT64: primitiveElement = true; fieldSize = kj::str("org.capnproto.FieldSize.EIGHT_BYTES"); break; case schema::Type::ENUM: primitiveElement = true; fieldSize = kj::str("org.capnproto.FieldSize.TWO_BYTES"); break; case schema::Type::TEXT: primitiveElement = false; fieldSize = kj::str("org.capnproto.FieldSize.POINTER"); break; case schema::Type::DATA: primitiveElement = false; fieldSize = kj::str("org.capnproto.FieldSize.POINTER"); break; case schema::Type::LIST: case schema::Type::ANY_POINTER: primitiveElement = false; break; case schema::Type::INTERFACE: isStructOrCapList = true; primitiveElement = false; interface = true; break; case schema::Type::STRUCT: isStructList = true; isStructOrCapList = true; primitiveElement = false; elementReaderType = kj::str(typeName(typeBody.getList().getElementType()), ".Reader"); readerClass = kj::str("Reader<", elementReaderType, ">"); elementBuilderType = kj::str(typeName(typeBody.getList().getElementType()), ".Builder"); builderClass = kj::str("Builder<", elementBuilderType, ">"); readerFactoryArg = kj::str(typeName(typeBody.getList().getElementType()), ".factory, "); builderFactoryArg = kj::str(typeName(typeBody.getList().getElementType()), ".factory, "); fieldSize = kj::str(typeName(typeBody.getList().getElementType()),".STRUCT_SIZE.preferredListEncoding"); break; } if (primitiveElement) { elementReaderType = kj::str(typeName(typeBody.getList().getElementType())); elementBuilderType = kj::str(typeName(typeBody.getList().getElementType())); } } return FieldText { kj::strTree( kj::mv(unionDiscrim.readerIsDecl), spaces(indent), " public final boolean has", titleCase, "() {\n", spaces(indent), " return !_reader.getPointerField(", offset, ").isNull();\n", spaces(indent), " }\n", spaces(indent), " public final ", type, ".", readerClass, " get", titleCase, "() {\n", spaces(indent), " return new ", type, ".", readerClass, "(\n", spaces(indent), " ", readerFactoryArg, "_reader.getPointerField(", offset, ").getList(", fieldSize, ")", ");\n", spaces(indent), " }\n", "\n"), kj::strTree( kj::mv(unionDiscrim.builderIsDecl), spaces(indent), " public final boolean has", titleCase, "() {\n", spaces(indent), " return !_builder.getPointerField(", offset, ").isNull();\n", spaces(indent), " }\n", spaces(indent), " public final ", type, ".", builderClass, " get", titleCase, "() {\n", spaces(indent), " return new ", type, ".", builderClass, " (\n", spaces(indent), " ", builderFactoryArg, "_builder.getPointerField(", offset, ").get", (isStructList ? kj::strTree("StructList(", typeName(typeBody.getList().getElementType()),".STRUCT_SIZE)") : kj::strTree("List(", fieldSize, ")")), ");\n", spaces(indent), " }\n", spaces(indent), " public final void set", titleCase, "(", type, ".Reader value) {\n", spaces(indent), " throw new Error();\n", spaces(indent), " }\n", spaces(indent), " public final ", type, ".", builderClass, " init", titleCase, "(int size) {\n", spaces(indent), " return new ", type, ".", builderClass, "(\n", spaces(indent), " ", builderFactoryArg, "_builder.getPointerField(", offset, ").init", (isStructList ? kj::strTree("StructList(size,", typeName(typeBody.getList().getElementType()),".STRUCT_SIZE)") : kj::strTree("List(", fieldSize, ", size)")), ");\n", spaces(indent), " }\n"), kj::strTree( kind == FieldKind::STRUCT && !hasDiscriminantValue(proto) ? kj::strTree( " inline ", type, "::Pipeline get", titleCase, "();\n") : kj::strTree()), kj::strTree( kj::mv(unionDiscrim.isDefs), "inline ", type, "::Reader ", scope, "Reader::get", titleCase, "() const {\n", unionDiscrim.check, " return ::capnp::_::PointerHelpers<", type, ">::get(\n" " _reader.getPointerField(", offset, " * ::capnp::POINTERS)", defaultParam, ");\n" "}\n" "inline ", type, "::Builder ", scope, "Builder::get", titleCase, "() {\n", unionDiscrim.check, " return ::capnp::_::PointerHelpers<", type, ">::get(\n" " _builder.getPointerField(", offset, " * ::capnp::POINTERS)", defaultParam, ");\n" "}\n", kind == FieldKind::STRUCT && !hasDiscriminantValue(proto) ? kj::strTree( "inline ", type, "::Pipeline ", scope, "Pipeline::get", titleCase, "() {\n", " return ", type, "::Pipeline(_typeless.getPointerField(", offset, "));\n" "}\n") : kj::strTree(), "inline void ", scope, "Builder::set", titleCase, "(", type, "::Reader value) {\n", unionDiscrim.set, " ::capnp::_::PointerHelpers<", type, ">::set(\n" " _builder.getPointerField(", offset, " * ::capnp::POINTERS), value);\n" "}\n", kind == FieldKind::LIST && !isStructOrCapList ? kj::strTree( "inline void ", scope, "Builder::set", titleCase, "(::kj::ArrayPtr value) {\n", unionDiscrim.set, " ::capnp::_::PointerHelpers<", type, ">::set(\n" " _builder.getPointerField(", offset, " * ::capnp::POINTERS), value);\n" "}\n") : kj::strTree(), kind == FieldKind::STRUCT ? kj::strTree( "inline ", type, "::Builder ", scope, "Builder::init", titleCase, "() {\n", unionDiscrim.set, " return ::capnp::_::PointerHelpers<", type, ">::init(\n" " _builder.getPointerField(", offset, " * ::capnp::POINTERS));\n" "}\n") : kj::strTree( "inline ", type, "::Builder ", scope, "Builder::init", titleCase, "(unsigned int size) {\n", unionDiscrim.set, " return ::capnp::_::PointerHelpers<", type, ">::init(\n" " _builder.getPointerField(", offset, " * ::capnp::POINTERS), size);\n" "}\n"), "inline void ", scope, "Builder::adopt", titleCase, "(\n" " ::capnp::Orphan<", type, ">&& value) {\n", unionDiscrim.set, " ::capnp::_::PointerHelpers<", type, ">::adopt(\n" " _builder.getPointerField(", offset, " * ::capnp::POINTERS), kj::mv(value));\n" "}\n" "inline ::capnp::Orphan<", type, "> ", scope, "Builder::disown", titleCase, "() {\n", unionDiscrim.check, " return ::capnp::_::PointerHelpers<", type, ">::disown(\n" " _builder.getPointerField(", offset, " * ::capnp::POINTERS));\n" "}\n" "\n") }; } else { KJ_UNREACHABLE; } } // ----------------------------------------------------------------- struct StructText { kj::StringTree outerTypeDef; kj::StringTree readerBuilderDefs; kj::StringTree inlineMethodDefs; }; kj::StringTree makeWhich(StructSchema schema, kj::String member, int indent) { if (schema.getProto().getStruct().getDiscriminantCount() == 0) { return kj::strTree(); } else { auto fields = schema.getUnionFields(); return kj::strTree( spaces(indent), "public Which which() {\n", spaces(indent+1), "switch(", member, ".getShortField(", schema.getProto().getStruct().getDiscriminantOffset(), ")) {\n", KJ_MAP(f, fields) { return kj::strTree(spaces(indent+2), "case ", f.getProto().getDiscriminantValue(), " : return ", "Which.", toUpperCase(f.getProto().getName()), ";\n"); }, spaces(indent+2), "default: return Which._UNKNOWN;\n", spaces(indent+1), "}\n", spaces(indent), "}\n" ); } } kj::StringTree makeReaderDef(kj::StringPtr fullName, kj::StringPtr unqualifiedParentType, StructSchema schema, kj::Array&& methodDecls, int indent) { return kj::strTree( spaces(indent), "public static final class Reader {\n", spaces(indent), " public Reader(org.capnproto.StructReader base){ this._reader = base; }\n", "\n", makeWhich(schema, kj::str("_reader"), indent+1), kj::mv(methodDecls), spaces(indent), " public org.capnproto.StructReader _reader;\n", spaces(indent), "}\n" "\n"); } kj::StringTree makeBuilderDef(kj::StringPtr fullName, kj::StringPtr unqualifiedParentType, StructSchema schema, kj::Array&& methodDecls, int indent) { return kj::strTree( spaces(indent), "public static final class Builder {\n", spaces(indent), " public Builder(org.capnproto.StructBuilder base){ this._builder = base; }\n", spaces(indent), " public org.capnproto.StructBuilder _builder;\n", makeWhich(schema, kj::str("_builder"), indent+1), spaces(indent), " public final Reader asReader() {\n", spaces(indent), " return new Reader(this._builder.asReader());\n", spaces(indent), " }\n", kj::mv(methodDecls), spaces(indent), "}\n", "\n"); } StructText makeStructText(kj::StringPtr scope, kj::StringPtr name, StructSchema schema, kj::Array nestedTypeDecls, int indent) { auto proto = schema.getProto(); auto fullName = kj::str(scope, name); auto subScope = kj::str(fullName, "."); auto fieldTexts = KJ_MAP(f, schema.getFields()) { return makeFieldText(subScope, f, indent + 1); }; auto structNode = proto.getStruct(); uint discrimOffset = structNode.getDiscriminantOffset(); structNode.getPointerCount(); return StructText { kj::strTree( spaces(indent), "public static class ", name, " {\n", kj::strTree( spaces(indent), " public static final org.capnproto.StructSize STRUCT_SIZE =\n", spaces(indent), " new org.capnproto.StructSize((short)", structNode.getDataWordCount(), ",(short)", structNode.getPointerCount(), ", org.capnproto.FieldSize.", FIELD_SIZE_NAMES[(int)structNode.getPreferredListEncoding()], ");\n"), spaces(indent), " public static class Factory implements org.capnproto.StructFactory {\n", spaces(indent), " public final Reader fromStructReader(org.capnproto.StructReader reader) {\n", spaces(indent), " return new Reader(reader);\n", spaces(indent), " }\n", spaces(indent), " public final Builder fromStructBuilder(org.capnproto.StructBuilder builder) {\n", spaces(indent), " return new Builder(builder);\n", spaces(indent), " }\n", spaces(indent), " public final org.capnproto.StructSize structSize() {\n", spaces(indent), " return ", fullName, ".STRUCT_SIZE;\n", spaces(indent), " }\n", spaces(indent), " public final Reader asReader(Builder builder) {\n", spaces(indent), " return new Reader(builder._builder.asReader());\n", spaces(indent), " }\n", spaces(indent), " }\n", spaces(indent), " public static final Factory factory = new Factory();\n", kj::strTree(makeReaderDef(fullName, name, schema, KJ_MAP(f, fieldTexts) { return kj::mv(f.readerMethodDecls); }, indent + 1), makeBuilderDef(fullName, name, schema, KJ_MAP(f, fieldTexts) { return kj::mv(f.builderMethodDecls); }, indent + 1)), structNode.getDiscriminantCount() == 0 ? kj::strTree() : kj::strTree( spaces(indent), " public enum Which {\n", KJ_MAP(f, structNode.getFields()) { if (hasDiscriminantValue(f)) { return kj::strTree(spaces(indent), " ", toUpperCase(f.getName()), ",\n"); } else { return kj::strTree(); } }, spaces(indent), " _UNKNOWN,\n", spaces(indent), " }\n"), KJ_MAP(n, nestedTypeDecls) { return kj::mv(n); }, spaces(indent), "}\n" "\n", "\n"), kj::strTree(), kj::strTree() }; } // ----------------------------------------------------------------- struct ConstText { bool needsSchema; kj::StringTree decl; }; ConstText makeConstText(kj::StringPtr scope, kj::StringPtr name, ConstSchema schema) { auto proto = schema.getProto(); auto constProto = proto.getConst(); auto type = constProto.getType(); auto typeName_ = typeName(type).flatten(); auto upperCase = toUpperCase(name); // Linkage qualifier for non-primitive types. const char* linkage = scope.size() == 0 ? "extern " : "static "; switch (type.which()) { case schema::Value::VOID: case schema::Value::BOOL: case schema::Value::INT8: case schema::Value::INT16: case schema::Value::INT32: case schema::Value::INT64: case schema::Value::UINT8: case schema::Value::UINT16: case schema::Value::UINT32: case schema::Value::UINT64: case schema::Value::FLOAT32: case schema::Value::FLOAT64: case schema::Value::ENUM: return ConstText { false, kj::strTree("public static final ", typeName_, ' ', upperCase, " = ", literalValue(constProto.getType(), constProto.getValue()), ";\n") }; case schema::Value::TEXT: { return ConstText { true, kj::strTree("public static final org.capnproto.Text.Reader ", upperCase, " = new org.capnproto.Text.Reader(Schemas.b_", kj::hex(proto.getId()), ", ", schema.getValueSchemaOffset(), ", ", constProto.getValue().getText().size(), ");\n") }; } case schema::Value::DATA: { kj::String constType = kj::strTree( "::capnp::_::ConstData<", schema.as().size(), ">").flatten(); return ConstText { true, kj::strTree("const ", constType, ' ', scope, upperCase, "(::capnp::schemas::b_", kj::hex(proto.getId()), ".words + ", schema.getValueSchemaOffset(), ");\n") }; } case schema::Value::STRUCT: { kj::String constType = kj::strTree( "::capnp::_::ConstStruct<", typeName_, ">").flatten(); return ConstText { true, kj::strTree("const ", constType, ' ', scope, upperCase, "(::capnp::schemas::b_", kj::hex(proto.getId()), ".words + ", schema.getValueSchemaOffset(), ");\n") }; } case schema::Value::LIST: { kj::String constType = kj::strTree( "::capnp::_::ConstList<", typeName(type.getList().getElementType()), ">").flatten(); return ConstText { true, kj::strTree("const ", constType, ' ', scope, upperCase, "(::capnp::schemas::b_", kj::hex(proto.getId()), ".words + ", schema.getValueSchemaOffset(), ");\n") }; } case schema::Value::ANY_POINTER: case schema::Value::INTERFACE: return ConstText { false, kj::strTree() }; } KJ_UNREACHABLE; } // ----------------------------------------------------------------- struct NodeText { kj::StringTree outerTypeDef; kj::StringTree readerBuilderDefs; kj::StringTree inlineMethodDefs; kj::StringTree capnpSchemaDefs; kj::StringTree capnpPrivateDecls; kj::StringTree capnpPrivateDefs; kj::StringTree sourceFileDefs; }; struct NodeTextNoSchema { kj::StringTree outerTypeDef; kj::StringTree readerBuilderDefs; kj::StringTree inlineMethodDefs; kj::StringTree capnpPrivateDecls; kj::StringTree capnpPrivateDefs; kj::StringTree sourceFileDefs; }; NodeText makeNodeText(kj::StringPtr scope, kj::StringPtr name, Schema schema, int indent) { auto proto = schema.getProto(); auto fullName = kj::str(scope, name); auto subScope = kj::str(fullName, "."); auto hexId = kj::hex(proto.getId()); // Compute nested nodes, including groups. kj::Vector nestedTexts(proto.getNestedNodes().size()); for (auto nested: proto.getNestedNodes()) { nestedTexts.add(makeNodeText( subScope, nested.getName(), schemaLoader.get(nested.getId()), indent + 1)); }; if (proto.isStruct()) { for (auto field: proto.getStruct().getFields()) { if (field.isGroup()) { nestedTexts.add(makeNodeText(subScope, toTitleCase(field.getName()), schemaLoader.get(field.getGroup().getTypeId()), indent + 1)); } } } else if (proto.isInterface()) { KJ_FAIL_REQUIRE("interfaces not implemented"); } // Convert the encoded schema to a literal byte array. kj::ArrayPtr rawSchema = schema.asUncheckedMessage(); auto schemaLiteral = kj::StringTree(KJ_MAP(w, rawSchema) { const byte* bytes = reinterpret_cast(&w); return kj::strTree( "\"", KJ_MAP(i, kj::range(0, sizeof(word))) { switch(bytes[i]) { case 0x0a: return kj::strTree("\\n"); case 0x0d: return kj::strTree("\\r"); case 0x22: return kj::strTree("\\\""); case 0x5c: return kj::strTree("\\\\"); default: auto text = kj::hex(bytes[i]); return kj::strTree("\\u", kj::repeat('0', 4 - text.size()), text); } }, "\" +"); }, "\n "); std::set deps; enumerateDeps(proto, deps); kj::Array membersByName; kj::Array membersByDiscrim; switch (proto.which()) { case schema::Node::STRUCT: { auto structSchema = schema.asStruct(); membersByName = makeMembersByName(structSchema.getFields()); auto builder = kj::heapArrayBuilder(structSchema.getFields().size()); for (auto field: structSchema.getUnionFields()) { builder.add(field.getIndex()); } for (auto field: structSchema.getNonUnionFields()) { builder.add(field.getIndex()); } membersByDiscrim = builder.finish(); break; } case schema::Node::ENUM: membersByName = makeMembersByName(schema.asEnum().getEnumerants()); break; case schema::Node::INTERFACE: membersByName = makeMembersByName(schema.asInterface().getMethods()); break; default: break; } // Java limits method code size to 64KB. Maybe we should use class.getResource()? auto schemaDef = kj::strTree( "public static final java.nio.ByteBuffer b_", hexId, " =\n", " org.capnproto.GeneratedClassSupport.decodeRawBytes(\n", " ", kj::mv(schemaLiteral), " \"\"", ");\n"); /* deps.size() == 0 ? kj::strTree() : kj::strTree( "static const ::capnp::_::RawSchema* const d_", hexId, "[] = {\n", KJ_MAP(depId, deps) { return kj::strTree(" &s_", kj::hex(depId), ",\n"); }, "};\n"), membersByName.size() == 0 ? kj::strTree() : kj::strTree( "static const uint16_t m_", hexId, "[] = {", kj::StringTree(KJ_MAP(index, membersByName) { return kj::strTree(index); }, ", "), "};\n"), membersByDiscrim.size() == 0 ? kj::strTree() : kj::strTree( "static const uint16_t i_", hexId, "[] = {", kj::StringTree(KJ_MAP(index, membersByDiscrim) { return kj::strTree(index); }, ", "), "};\n"), "const ::capnp::_::RawSchema s_", hexId, " = {\n" " 0x", hexId, ", b_", hexId, ".words, ", rawSchema.size(), ", ", deps.size() == 0 ? kj::strTree("nullptr") : kj::strTree("d_", hexId), ", ", membersByName.size() == 0 ? kj::strTree("nullptr") : kj::strTree("m_", hexId), ",\n", " ", deps.size(), ", ", membersByName.size(), ", ", membersByDiscrim.size() == 0 ? kj::strTree("nullptr") : kj::strTree("i_", hexId), ", nullptr, nullptr\n" "};\n");*/ NodeTextNoSchema top = makeNodeTextWithoutNested( scope, name, schema, KJ_MAP(n, nestedTexts) { return kj::mv(n.outerTypeDef); }, indent); return NodeText { kj::strTree( kj::mv(top.outerTypeDef), KJ_MAP(n, nestedTexts) { return kj::mv(n.outerTypeDef); }), kj::strTree( kj::mv(top.readerBuilderDefs), KJ_MAP(n, nestedTexts) { return kj::mv(n.readerBuilderDefs); }), kj::strTree( kj::mv(top.inlineMethodDefs), KJ_MAP(n, nestedTexts) { return kj::mv(n.inlineMethodDefs); }), kj::strTree( kj::mv(schemaDef), KJ_MAP(n, nestedTexts) { return kj::mv(n.capnpSchemaDefs); }), kj::strTree( kj::mv(top.capnpPrivateDecls), KJ_MAP(n, nestedTexts) { return kj::mv(n.capnpPrivateDecls); }), kj::strTree( kj::mv(top.capnpPrivateDefs), KJ_MAP(n, nestedTexts) { return kj::mv(n.capnpPrivateDefs); }), kj::strTree( kj::mv(top.sourceFileDefs), KJ_MAP(n, nestedTexts) { return kj::mv(n.sourceFileDefs); }), }; } NodeTextNoSchema makeNodeTextWithoutNested(kj::StringPtr scope, kj::StringPtr name, Schema schema, kj::Array nestedTypeDecls, int indent) { auto proto = schema.getProto(); auto fullName = kj::str(scope, name); auto hexId = kj::hex(proto.getId()); switch (proto.which()) { case schema::Node::FILE: KJ_FAIL_REQUIRE("This method shouldn't be called on file nodes."); case schema::Node::STRUCT: { StructText structText = makeStructText(scope, name, schema.asStruct(), kj::mv(nestedTypeDecls), indent); auto structNode = proto.getStruct(); return NodeTextNoSchema { kj::mv(structText.outerTypeDef), kj::mv(structText.readerBuilderDefs), kj::mv(structText.inlineMethodDefs), kj::strTree(), kj::strTree(), kj::strTree(), }; } case schema::Node::ENUM: { auto enumerants = schema.asEnum().getEnumerants(); return NodeTextNoSchema { kj::strTree( spaces(indent), "public enum ", name, " {\n", KJ_MAP(e, enumerants) { return kj::strTree(spaces(indent), " ", toUpperCase(e.getProto().getName()), ",\n"); }, spaces(indent), " _UNKNOWN,\n", spaces(indent), "}\n" "\n"), kj::strTree(), kj::strTree(), kj::strTree(), kj::strTree(), kj::strTree(), }; } case schema::Node::INTERFACE: { hasInterfaces = true; KJ_FAIL_REQUIRE("unimplemented"); } case schema::Node::CONST: { auto constText = makeConstText(scope, name, schema.asConst()); return NodeTextNoSchema { kj::strTree(" ", kj::mv(constText.decl)), kj::strTree(), kj::strTree(), kj::strTree(), kj::strTree(), kj::strTree(), }; } case schema::Node::ANNOTATION: { return NodeTextNoSchema { kj::strTree(), kj::strTree(), kj::strTree(), kj::strTree(), kj::strTree(), kj::strTree(), }; } } KJ_UNREACHABLE; } // ----------------------------------------------------------------- struct FileText { kj::String outerClassname; kj::StringTree source; }; FileText makeFileText(Schema schema, schema::CodeGeneratorRequest::RequestedFile::Reader request) { usedImports.clear(); auto node = schema.getProto(); auto displayName = node.getDisplayName(); kj::StringPtr packageName; kj::StringPtr outerClassname; for (auto annotation: node.getAnnotations()) { if (annotation.getId() == PACKAGE_ANNOTATION_ID) { packageName = annotation.getValue().getText(); } else if (annotation.getId() == OUTER_CLASSNAME_ANNOTATION_ID) { outerClassname = annotation.getValue().getText(); } } if (packageName.size() == 0) { context.exitError(kj::str(displayName, ": must provide a Java package name.")); } if (outerClassname.size() == 0) { context.exitError(kj::str(displayName, ": must provide a Java outer classname.")); } auto nodeTexts = KJ_MAP(nested, node.getNestedNodes()) { return makeNodeText("", nested.getName(), schemaLoader.get(nested.getId()), 1); }; kj::String separator = kj::str("// ", kj::repeat('=', 87), "\n"); kj::Vector includes; for (auto import: request.getImports()) { if (usedImports.count(import.getId()) > 0) { includes.add(import.getName()); } } kj::StringTree sourceDefs = kj::strTree( KJ_MAP(n, nodeTexts) { return kj::mv(n.sourceFileDefs); }); return FileText { kj::str(outerClassname), kj::strTree( "// Generated by Cap'n Proto compiler, DO NOT EDIT\n" "// source: ", baseName(displayName), "\n\n", "package ", packageName, ";\n\n", //"import org.capnproto;\n", "public final class ", outerClassname, " {\n", KJ_MAP(n, nodeTexts) { return kj::mv(n.outerTypeDef); }, "\n", "public static final class Schemas {\n", KJ_MAP(n, nodeTexts) { return kj::mv(n.capnpSchemaDefs); }, "}\n", "}\n", "\n") }; } // ----------------------------------------------------------------- void makeDirectory(kj::StringPtr path) { KJ_IF_MAYBE(slashpos, path.findLast('/')) { // Make the parent dir. makeDirectory(kj::str(path.slice(0, *slashpos))); } if (mkdir(path.cStr(), 0777) < 0) { int error = errno; if (error != EEXIST) { KJ_FAIL_SYSCALL("mkdir(path)", error, path); } } } void writeFile(kj::StringPtr filename, const kj::StringTree& text) { if (!filename.startsWith("/")) { KJ_IF_MAYBE(slashpos, filename.findLast('/')) { // Make the parent dir. makeDirectory(kj::str(filename.slice(0, *slashpos))); } } int fd; KJ_SYSCALL(fd = open(filename.cStr(), O_CREAT | O_WRONLY | O_TRUNC, 0666), filename); kj::FdOutputStream out((kj::AutoCloseFd(fd))); text.visit( [&](kj::ArrayPtr text) { out.write(text.begin(), text.size()); }); } kj::MainBuilder::Validity run() { ReaderOptions options; options.traversalLimitInWords = 1 << 30; // Don't limit. StreamFdMessageReader reader(STDIN_FILENO, options); auto request = reader.getRoot(); for (auto node: request.getNodes()) { schemaLoader.load(node); } kj::FdOutputStream rawOut(STDOUT_FILENO); kj::BufferedOutputStreamWrapper out(rawOut); for (auto requestedFile: request.getRequestedFiles()) { auto schema = schemaLoader.get(requestedFile.getId()); auto filename = requestedFile.getFilename(); size_t stemstart = 0; size_t stemend = filename.size(); KJ_IF_MAYBE(slashpos, filename.findLast('/')) { stemstart = *slashpos + 1; } KJ_IF_MAYBE(dotpos, filename.findLast('.')) { stemend = *dotpos; } auto fileText = makeFileText(schema, requestedFile); writeFile(kj::str(filename.slice(0, stemstart), fileText.outerClassname, ".java"), fileText.source); } return true; } }; } // namespace } // namespace capnp KJ_MAIN(capnp::CapnpcJavaMain);