sigil-stitch 0.5.2

Type-safe, import-aware, width-aware code generation for multiple languages
Documentation
//! Generate a Java file — builder API vs `sigil_quote!` comparison.
//!
//! Demonstrates: interface with generics, abstract class, enum with constructor-arg
//! values, static/override methods, constructor delegation (`super()`), wildcard
//! type bounds (`? extends T`, `? super T`), protected visibility, and
//! import-tracked annotations.
//!
//! Run: `cargo run --example java_codegen`

use sigil_stitch::lang::java_lang::JavaLang;
use sigil_stitch::prelude::*;

fn main() {
    println!("=== Builder API ===\n");
    let builder_output = builder_approach();
    println!("{builder_output}");

    println!("=== sigil_quote! Macro ===\n");
    let macro_output = macro_approach();
    println!("{macro_output}");
}

fn build_enum() -> TypeSpec {
    TypeSpec::builder("Priority", TypeKind::Enum)
        .visibility(Visibility::Public)
        .add_variant(
            EnumVariantSpec::builder("LOW")
                .value(CodeBlock::of("1", ()).unwrap())
                .build()
                .unwrap(),
        )
        .add_variant(
            EnumVariantSpec::builder("MEDIUM")
                .value(CodeBlock::of("2", ()).unwrap())
                .build()
                .unwrap(),
        )
        .add_variant(
            EnumVariantSpec::builder("HIGH")
                .value(CodeBlock::of("3", ()).unwrap())
                .build()
                .unwrap(),
        )
        .add_field(
            FieldSpec::builder("value", TypeName::primitive("int"))
                .visibility(Visibility::Private)
                .is_readonly()
                .build()
                .unwrap(),
        )
        .build()
        .unwrap()
}

fn build_interface() -> TypeSpec {
    let optional = TypeName::importable("java.util", "Optional");
    let list = TypeName::importable("java.util", "List");

    TypeSpec::builder("Repository", TypeKind::Interface)
        .visibility(Visibility::Public)
        .add_type_param(TypeParamSpec::new("T"))
        .add_method(
            FunSpec::builder("findById")
                .returns(TypeName::generic(optional, vec![TypeName::primitive("T")]))
                .add_param(ParameterSpec::new("id", TypeName::primitive("long")).unwrap())
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("save")
                .returns(TypeName::primitive("void"))
                .add_param(ParameterSpec::new("entity", TypeName::primitive("T")).unwrap())
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("findAll")
                .returns(TypeName::generic(list, vec![TypeName::primitive("T")]))
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("addAll")
                .returns(TypeName::primitive("void"))
                .add_param(
                    ParameterSpec::new(
                        "items",
                        TypeName::generic(
                            TypeName::importable("java.util", "Collection"),
                            vec![TypeName::wildcard_extends(TypeName::primitive("T"))],
                        ),
                    )
                    .unwrap(),
                )
                .build()
                .unwrap(),
        )
        .build()
        .unwrap()
}

fn builder_approach() -> String {
    let priority_enum = build_enum();
    let repo_iface = build_interface();

    // --- Abstract base class ---
    let mut get_name_body = CodeBlock::builder();
    get_name_body.add_statement("return this.name", ());

    let base_entity = TypeSpec::builder("BaseEntity", TypeKind::Class)
        .visibility(Visibility::Public)
        .is_abstract()
        .add_field(
            FieldSpec::builder("name", TypeName::primitive("String"))
                .visibility(Visibility::Protected)
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("BaseEntity")
                .visibility(Visibility::Protected)
                .add_param(ParameterSpec::new("name", TypeName::primitive("String")).unwrap())
                .body({
                    let mut b = CodeBlock::builder();
                    b.add_statement("this.name = name", ());
                    b.build().unwrap()
                })
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("getName")
                .visibility(Visibility::Public)
                .returns(TypeName::primitive("String"))
                .body(get_name_body.build().unwrap())
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("validate")
                .visibility(Visibility::Public)
                .is_abstract()
                .returns(TypeName::primitive("boolean"))
                .build()
                .unwrap(),
        )
        .build()
        .unwrap();

    // --- Concrete class with override and delegation ---
    let mut validate_body = CodeBlock::builder();
    validate_body.add_statement("return this.name != null && !this.name.isEmpty()", ());

    let mut to_string_body = CodeBlock::builder();
    to_string_body.add_statement(
        "return \"User{name=\" + this.name + \", email=\" + this.email + \"}\"",
        (),
    );

    let user_cls = TypeSpec::builder("User", TypeKind::Class)
        .visibility(Visibility::Public)
        .extends(TypeName::primitive("BaseEntity"))
        .add_field(
            FieldSpec::builder("email", TypeName::primitive("String"))
                .visibility(Visibility::Private)
                .is_readonly()
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("User")
                .visibility(Visibility::Public)
                .add_param(ParameterSpec::new("name", TypeName::primitive("String")).unwrap())
                .add_param(ParameterSpec::new("email", TypeName::primitive("String")).unwrap())
                .delegation(CodeBlock::of("super(name)", ()).unwrap())
                .body({
                    let mut b = CodeBlock::builder();
                    b.add_statement("this.email = email", ());
                    b.build().unwrap()
                })
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("validate")
                .visibility(Visibility::Public)
                .is_override()
                .returns(TypeName::primitive("boolean"))
                .body(validate_body.build().unwrap())
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("getEmail")
                .visibility(Visibility::Public)
                .returns(TypeName::primitive("String"))
                .body({
                    let mut b = CodeBlock::builder();
                    b.add_statement("return this.email", ());
                    b.build().unwrap()
                })
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("toString")
                .visibility(Visibility::Public)
                .is_override()
                .returns(TypeName::primitive("String"))
                .body(to_string_body.build().unwrap())
                .build()
                .unwrap(),
        )
        .build()
        .unwrap();

    // --- Static utility method with generic where-bound ---
    let collections = TypeName::importable("java.util", "Collections");
    let comparable = TypeName::primitive("Comparable");
    let mut sort_body = CodeBlock::builder();
    sort_body.add_statement("%T.sort(list)", (collections,));
    sort_body.add_statement("return list", ());

    let sort_fn = FunSpec::builder("sortList")
        .visibility(Visibility::Public)
        .is_static()
        .add_type_param(TypeParamSpec::new("T").with_bound(comparable))
        .add_param(
            ParameterSpec::new(
                "list",
                TypeName::generic(
                    TypeName::importable("java.util", "List"),
                    vec![TypeName::primitive("T")],
                ),
            )
            .unwrap(),
        )
        .returns(TypeName::generic(
            TypeName::importable("java.util", "List"),
            vec![TypeName::primitive("T")],
        ))
        .body(sort_body.build().unwrap())
        .build()
        .unwrap();

    FileSpec::builder_with("User.java", JavaLang::new())
        .add_type(priority_enum)
        .add_type(repo_iface)
        .add_type(base_entity)
        .add_type(user_cls)
        .add_function(sort_fn)
        .build()
        .unwrap()
        .render(80)
        .unwrap()
}

fn macro_approach() -> String {
    let priority_enum = build_enum();
    let repo_iface = build_interface();

    // --- Abstract base class ---
    let base_entity = TypeSpec::builder("BaseEntity", TypeKind::Class)
        .visibility(Visibility::Public)
        .is_abstract()
        .add_field(
            FieldSpec::builder("name", TypeName::primitive("String"))
                .visibility(Visibility::Protected)
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("BaseEntity")
                .visibility(Visibility::Protected)
                .add_param(ParameterSpec::new("name", TypeName::primitive("String")).unwrap())
                .body(sigil_quote!(JavaLang { this.name = name; }).unwrap())
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("getName")
                .visibility(Visibility::Public)
                .returns(TypeName::primitive("String"))
                .body(sigil_quote!(JavaLang { return this.name; }).unwrap())
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("validate")
                .visibility(Visibility::Public)
                .is_abstract()
                .returns(TypeName::primitive("boolean"))
                .build()
                .unwrap(),
        )
        .build()
        .unwrap();

    // --- Concrete class ---
    let user_cls = TypeSpec::builder("User", TypeKind::Class)
        .visibility(Visibility::Public)
        .extends(TypeName::primitive("BaseEntity"))
        .add_field(
            FieldSpec::builder("email", TypeName::primitive("String"))
                .visibility(Visibility::Private)
                .is_readonly()
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("User")
                .visibility(Visibility::Public)
                .add_param(ParameterSpec::new("name", TypeName::primitive("String")).unwrap())
                .add_param(ParameterSpec::new("email", TypeName::primitive("String")).unwrap())
                .delegation(CodeBlock::of("super(name)", ()).unwrap())
                .body(sigil_quote!(JavaLang { this.email = email; }).unwrap())
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("validate")
                .visibility(Visibility::Public)
                .is_override()
                .returns(TypeName::primitive("boolean"))
                .body(
                    sigil_quote!(JavaLang {
                        return this.name != null && !this.name.isEmpty();
                    })
                    .unwrap(),
                )
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("getEmail")
                .visibility(Visibility::Public)
                .returns(TypeName::primitive("String"))
                .body(sigil_quote!(JavaLang { return this.email; }).unwrap())
                .build()
                .unwrap(),
        )
        .add_method(
            FunSpec::builder("toString")
                .visibility(Visibility::Public)
                .is_override()
                .returns(TypeName::primitive("String"))
                .body(
                    sigil_quote!(JavaLang {
                        return "User{name=" + this.name + ", email=" + this.email + "}";
                    })
                    .unwrap(),
                )
                .build()
                .unwrap(),
        )
        .build()
        .unwrap();

    // --- Static generic method ---
    let collections = TypeName::importable("java.util", "Collections");
    let sort_body = sigil_quote!(JavaLang {
        $T(collections).sort(list);
        return list;
    })
    .unwrap();

    let sort_fn = FunSpec::builder("sortList")
        .visibility(Visibility::Public)
        .is_static()
        .add_type_param(TypeParamSpec::new("T").with_bound(TypeName::primitive("Comparable")))
        .add_param(
            ParameterSpec::new(
                "list",
                TypeName::generic(
                    TypeName::importable("java.util", "List"),
                    vec![TypeName::primitive("T")],
                ),
            )
            .unwrap(),
        )
        .returns(TypeName::generic(
            TypeName::importable("java.util", "List"),
            vec![TypeName::primitive("T")],
        ))
        .body(sort_body)
        .build()
        .unwrap();

    FileSpec::builder_with("User.java", JavaLang::new())
        .add_type(priority_enum)
        .add_type(repo_iface)
        .add_type(base_entity)
        .add_type(user_cls)
        .add_function(sort_fn)
        .build()
        .unwrap()
        .render(80)
        .unwrap()
}