Skip to main content

Code Generation

Coco code generation (coco-feature-codegen) is a template-based CRUD scaffolding capability. It takes the description of "a business resource" (package name, resource name, data table, fields) and renders it into maintainable, plain source code through FreeMarker templates, without registering any runtime dynamic CRUD behavior. It is a development-time capability, and the generated output consists of source files owned by the business project itself. The module binds the coco.codegen namespace, is enabled by default, participates in auto-configuration as a Coco Feature (CocoFeature.CODEGEN), and loads after the MyBatis-Plus auto-configuration (the repository and Mapper generated by the built-in CRUD templates depend on MyBatis-Plus).

Feature overview

  • CocoCodeGenerator: the code generator SPI. It takes a CocoCodegenRequest (template group, target package name, extension context) and returns a CocoCodegenResult (the set of generated files held in memory). The default implementation is FreeMarkerCocoCodeGenerator.
  • CocoCrudSpec: the default CRUD generation spec. It describes a single business resource and, before entering the templates, normalizes and validates the package name, resource name, table name, fields, and Java types, then converts them into a request for the built-in crud template group via toRequest().
  • FreeMarkerCocoCodeGenerator: the template engine implementation. It reads the template resources and output paths declared in each template group's <group>/manifest.properties from the template root directory, and returns only the in-memory file results, never implicitly creating directories or writing to disk.

How to enable and integrate

The module is enabled by default. Once enabled, the framework registers two beans: CocoCodeGenerator (the default FreeMarkerCocoCodeGenerator, which reads coco.codegen.templates.location and encoding) and CocoGeneratedFileWriter (used when explicitly writing to disk). Just inject the generator:

@Component
public class CrudScaffolder {

private final CocoCodeGenerator codeGenerator;
private final CocoGeneratedFileWriter fileWriter;

public CrudScaffolder(CocoCodeGenerator codeGenerator, CocoGeneratedFileWriter fileWriter) {
this.codeGenerator = codeGenerator;
this.fileWriter = fileWriter;
}
}

Usage example

Describe a resource with CocoCrudSpec, convert it into a request, and hand it to the generator; the generated result is a set of in-memory files, and whether to write them to disk is decided explicitly by the caller:

CocoCrudSpec spec = CocoCrudSpec.builder("com.example.order", "Order", "t_order")
.id("id", "id", Long.class, CocoCrudIdStrategy.AUTO)
.field("orderNo", "order_no", String.class, true)
.field("amount", "amount", java.math.BigDecimal.class, true)
.field("remark", "remark", String.class, false)
.apiPath("/orders") // when omitted, derived from the resource name, e.g. Order -> /orders
.build();

CocoCodegenResult result = codeGenerator.generate(spec.toRequest());

for (CocoGeneratedFile file : result.files()) {
System.out.println(file.path()); // relative output path
// call fileWriter to write to the target directory only when persistence is needed
}

CocoCrudSpec performs extensive safety validation during the build phase: package names and field names must be valid Java identifiers and must not be keywords; table names and column names must match safe SQL identifiers; apiPath must be an absolute path composed of safe segments; the primary key type may not be a primitive type; field names and column names must not be duplicated; and the resource name and field types must not conflict with the template's built-in generated types (such as Controller, Mapper, Service, etc.). These checks guarantee that the rendered source is compilable and free of injection risks.

Template mechanism

FreeMarkerCocoCodeGenerator organizes templates by template group. Each template group has a <group>/manifest.properties under the template root directory, declaring the number of templates and the source file and output path for each template:

group=crud
template.count=2
template.0.source=Entity.java.ftl
template.0.output=${basePackagePath}/entity/${resourceName}Entity.java
template.1.source=Controller.java.ftl
template.1.output=${basePackagePath}/web/${resourceName}Controller.java
  • Template paths go through normalization checks that reject traversal attempts such as absolute paths, drive-letter prefixes, and ./.. segments; for template reads under a file: / plain-path root directory, the target path is additionally checked to ensure it does not escape the template root.
  • The output path is itself a FreeMarker expression, normalized after rendering by CocoGeneratedPathValidator.normalizeRelativePath; producing duplicate output paths within the same template group raises an error.
  • The template model's reserved fields _coco, templateGroup, and targetPackage may not be overridden by request attributes.
  • FreeMarker is configured in strict mode (RETHROW_HANDLER, localizedLookup disabled, empty loop-variable fallback forbidden), and template errors throw CocoCodegenException directly.

The business side can point coco.codegen.templates.location at its own template root directory to replace or extend the built-in crud template group. The template location supports three prefixes: classpath:, file:, and plain file paths.

Key configuration properties

Bound under the prefix coco.codegen (corresponding to CocoCodegenProperties):

PropertyTypeDefaultDescription
coco.codegen.enabledbooleantrueWhether to enable the code generation infrastructure
coco.codegen.templates.locationStringclasspath:/coco/codegen/templatesThe FreeMarker template root location; supports classpath: / file: / plain paths
coco.codegen.templates.encodingStringUTF-8The template file encoding

Boundaries and caveats

  • This is a development-time capability: it generates plain source code the business project can keep maintaining, does not register dynamic CRUD behavior at runtime, and does not read database metadata.
  • The repository and Mapper generated by the built-in crud templates depend on MyBatis-Plus, so the auto-configuration is declared after CocoMybatisPlusAutoConfiguration; projects using the built-in templates need MyBatis-Plus available.
  • The generator only computes files and does not implicitly write to disk. Whether to persist and to which directory is controlled explicitly by the caller through CocoGeneratedFileWriter, avoiding overwriting existing source code.
  • The strict validation in CocoCrudSpec means non-compliant package names, table names, and field names throw exceptions right at build time, rather than producing code that will not compile.