io.github.itroadlabs.apicross.java.JavaCodeGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of apicross-java Show documentation
Show all versions of apicross-java Show documentation
Base classes for Java code generators
The newest version!
package io.github.itroadlabs.apicross.java;
import com.github.jknack.handlebars.Context;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Template;
import com.github.jknack.handlebars.io.ClassPathTemplateLoader;
import com.github.jknack.handlebars.io.FileTemplateLoader;
import com.github.jknack.handlebars.io.TemplateLoader;
import com.google.googlejavaformat.java.Formatter;
import com.google.googlejavaformat.java.FormatterException;
import io.github.itroadlabs.apicross.CodeGenerator;
import io.github.itroadlabs.apicross.CodeGeneratorException;
import io.github.itroadlabs.apicross.core.data.InlineDataModelResolver;
import io.github.itroadlabs.apicross.core.data.PropertyNameResolver;
import io.github.itroadlabs.apicross.core.data.model.ArrayDataModel;
import io.github.itroadlabs.apicross.core.data.model.ObjectDataModel;
import io.github.itroadlabs.apicross.core.handler.ParameterNameResolver;
import io.github.itroadlabs.apicross.core.handler.RequestsHandlerMethodNameResolver;
import io.github.itroadlabs.apicross.core.handler.RequestsHandlerTypeNameResolver;
import io.github.itroadlabs.apicross.core.handler.model.RequestsHandler;
import io.github.itroadlabs.apicross.core.handler.model.RequestsHandlerMethod;
import io.github.itroadlabs.apicross.utils.HandlebarsFactory;
import io.github.itroadlabs.apicross.utils.PluginsHelper;
import io.github.itroadlabs.apicross.utils.SourceCodeLineNumberUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
@Slf4j
public abstract class JavaCodeGenerator extends CodeGenerator {
protected final Formatter formatter = new Formatter();
protected Template requestsHandlerSourceCodeTemplate;
protected Template dataModelSourceCodeTemplate;
protected String apiModelPackage;
protected String apiHandlerPackage;
protected Map dataModelsInterfacesMap;
protected Map dataModelsExternalTypesMap;
protected Map queryObjectsInterfacesMap;
protected Set globalQueryObjectsInterfaces;
protected boolean useJsonNullable;
private Set alternativeTemplatesPath;
@Override
public void setOptions(T options) throws Exception {
super.setOptions(options);
this.apiHandlerPackage = options.getApiHandlerPackage();
this.apiModelPackage = options.getApiModelPackage();
this.dataModelsInterfacesMap = Collections.unmodifiableMap(options.getDataModelsInterfacesMap());
this.dataModelsExternalTypesMap = Collections.unmodifiableMap(options.getDataModelsExternalTypesMap());
this.queryObjectsInterfacesMap = Collections.unmodifiableMap(options.getQueryObjectsInterfacesMap());
this.globalQueryObjectsInterfaces = Collections.unmodifiableSet(options.getGlobalQueryObjectsInterfaces());
this.useJsonNullable = options.isUseJsonNullable();
this.alternativeTemplatesPath = Collections.unmodifiableSet(options.getAlternativeTemplatesPath());
}
@Override
protected void generate(Collection models, List handlers) throws IOException {
List modelsJavaClasses = prepareJavaClassDataModels(models, handlers);
// TODO: following if section must be placed in the preProcess() step, but it influences model type names,
// for example when some type replaced by a java.lang.String, then String source file will be written
if (!getOptions().isGenerateOnlyModels()) {
if (!this.apiHandlerPackage.equals(this.apiModelPackage) && modelsJavaClasses.size() > 0) {
for (RequestsHandler handler : handlers) {
handler.addCustomAttribute("imports", Collections.singleton(this.apiModelPackage + ".*"));
}
}
if (this.dataModelsExternalTypesMap != null) {
for (RequestsHandler handler : handlers) {
handler.replaceHandlerParametersByExternalTypesMap(this.dataModelsExternalTypesMap);
}
}
}
log.info("Setup source code templates...");
Handlebars handlebars = setupHandlebarsTemplates();
initSourceCodeTemplates(handlebars);
log.info("Writing sources...");
File writeSourcesTo = getWriteSourcesTo();
File modelsPackageDir = new File(writeSourcesTo, toFilePath(apiModelPackage));
if (!modelsPackageDir.exists()) {
modelsPackageDir.mkdirs();
log.info("Directory {} created", modelsPackageDir.getAbsolutePath());
}
writeModelsSources(modelsPackageDir, modelsJavaClasses);
if (!getOptions().isGenerateOnlyModels()) {
File handlersPackageDir = new File(writeSourcesTo, toFilePath(apiHandlerPackage));
if (!handlersPackageDir.exists()) {
handlersPackageDir.mkdirs();
log.info("Directory {} created", handlersPackageDir.getAbsolutePath());
}
writeHandlersSources(handlersPackageDir, modelsPackageDir, handlers);
}
}
protected List prepareJavaClassDataModels(Collection models, Collection handlers) {
log.info("Prepare data models java classes...");
List preparedModels = new ArrayList<>(models);
resolveInlineModelsFrom(models, preparedModels);
resolveInlineModelsFromHandlers(handlers, preparedModels);
if (this.dataModelsExternalTypesMap != null) {
log.info("Process data models external types map...");
postProcessExternalTypesForDataModels(preparedModels, this.dataModelsExternalTypesMap);
}
if (this.dataModelsInterfacesMap != null) {
log.info("Process data models interfaces...");
postProcessInterfacesForDataModels(preparedModels, this.dataModelsInterfacesMap);
}
String modelNameSuffix = getOptions().getModelClassNameSuffix();
if (modelNameSuffix != null && !modelNameSuffix.isEmpty()) {
log.info("Process data models name suffix...");
addModelNameSuffix(modelNameSuffix, this.dataModelsExternalTypesMap, preparedModels);
}
String modelNamePrefix = getOptions().getModelClassNamePrefix();
if (modelNamePrefix != null && !modelNamePrefix.isEmpty()) {
log.info("Process data models name prefix...");
addModelNamePrefix(modelNamePrefix, this.dataModelsExternalTypesMap, preparedModels);
}
log.info("Preparing data models java classes completed!");
return preparedModels;
}
private void resolveInlineModelsFromHandlers(Iterable handlers, List collectResolvedTo) {
for (RequestsHandler handler : handlers) {
for (RequestsHandlerMethod method : handler.getMethods()) {
if (method.getRequestBody() != null && method.getRequestBody().getContent().isArray()) {
ArrayDataModel requestBodyModel = (ArrayDataModel) method.getRequestBody().getContent();
List inlineModels =
InlineDataModelResolver.resolveInlineModels((typeName, propertyResolvedName) ->
typeName + StringUtils.capitalize(propertyResolvedName), requestBodyModel);
collectResolvedTo.addAll(inlineModels);
}
}
}
}
private void resolveInlineModelsFrom(Iterable models, List collectResolvedTo) {
for (ObjectDataModel model : models) {
List inlineModels =
InlineDataModelResolver.resolveInlineModels((typeName, propertyResolvedName) ->
typeName + StringUtils.capitalize(propertyResolvedName), model);
if (!inlineModels.isEmpty()) {
collectResolvedTo.addAll(inlineModels);
resolveInlineModelsFrom(inlineModels, collectResolvedTo);
}
}
}
protected void postProcessExternalTypesForDataModels(Iterable models, Map dataModelsExternalTypesMap) {
Iterator dataModelIterator = models.iterator();
while (dataModelIterator.hasNext()) {
ObjectDataModel model = dataModelIterator.next();
String modelClassName = model.getTypeName();
if (dataModelsExternalTypesMap.containsKey(modelClassName)) {
log.debug("Model '{}' replaced by external type '{}'", modelClassName, dataModelsExternalTypesMap.get(modelClassName));
dataModelIterator.remove();
}
}
dataModelIterator = models.iterator();
while (dataModelIterator.hasNext()) {
ObjectDataModel model = dataModelIterator.next();
model.replacePropertyTypeByExternalTypesMap(dataModelsExternalTypesMap);
}
}
protected void postProcessInterfacesForDataModels(Iterable models, Map dataModelsInterfacesMap) {
for (ObjectDataModel model : models) {
String iface = dataModelsInterfacesMap.get(model.getTypeName());
if (iface != null) {
List ifaces = new ArrayList<>();
ifaces.add(iface);
model.addCustomAttribute("implementsInterfaces", ifaces);
}
}
}
protected void addModelNameSuffix(String modelNameSuffix, Map dataModelsExternalTypesMap, Iterable models) {
for (ObjectDataModel model : models) {
if (!dataModelsExternalTypesMap.containsKey(model.getTypeName())) {
model.changeTypeName(model.getTypeName() + modelNameSuffix, false);
}
}
}
protected void addModelNamePrefix(String modelNamePrefix, Map dataModelsExternalTypesMap, Iterable models) {
for (ObjectDataModel model : models) {
if (!dataModelsExternalTypesMap.containsKey(model.getTypeName())) {
model.changeTypeName(modelNamePrefix + model.getTypeName(), false);
}
}
}
@Override
protected PropertyNameResolver setupPropertyNameResolver() {
return PluginsHelper.instantiatePlugin(getOptions().getPropertyNameResolverClassName(),
DefaultPropertyAndParameterNameResolver::new);
}
@Override
protected RequestsHandlerMethodNameResolver setupRequestsHandlerMethodNameResolver() {
return PluginsHelper.instantiatePlugin(getOptions().getRequestsHandlerMethodNameResolverClassName(),
DefaultRequestsHandlerMethodNameResolver::new);
}
@Override
protected RequestsHandlerTypeNameResolver setupRequestsHandlerTypeNameResolver() {
return PluginsHelper.instantiatePlugin(getOptions().getRequestsHandlerTypeNameResolverClassName(),
DefaultRequestsHandlerTypeNameResolver::new);
}
@Override
protected ParameterNameResolver setupParameterNameResolver() {
return PluginsHelper.instantiatePlugin(getOptions().getParameterNameResolverClassName(),
DefaultPropertyAndParameterNameResolver::new);
}
protected Handlebars setupHandlebarsTemplates() {
ClassPathTemplateLoader defaultTemplatesLoader = setupDefaultTemplatesLoader();
if (alternativeTemplatesPath != null && !alternativeTemplatesPath.isEmpty()) {
List effectiveTemplatesClassPath = new ArrayList<>();
for (String path : alternativeTemplatesPath) {
if (path.startsWith("classpath:")) {
effectiveTemplatesClassPath.add(new ClassPathTemplateLoader(path.substring(10), ".hbs"));
} else {
effectiveTemplatesClassPath.add(new FileTemplateLoader(path, ".hbs"));
}
}
effectiveTemplatesClassPath.add(defaultTemplatesLoader);
return HandlebarsFactory.setupHandlebars(effectiveTemplatesClassPath);
} else {
return HandlebarsFactory.setupHandlebars(Collections.singletonList(defaultTemplatesLoader));
}
}
protected abstract ClassPathTemplateLoader setupDefaultTemplatesLoader();
protected void initSourceCodeTemplates(Handlebars templatesEngine) throws IOException {
this.requestsHandlerSourceCodeTemplate = templatesEngine.compile("requestsHandler");
this.dataModelSourceCodeTemplate = templatesEngine.compile("dataModel");
}
protected void writeModelsSources(File modelsPackageDir, List models) throws IOException {
log.info("Writing API data models...");
for (ObjectDataModel model : models) {
File sourceFile = new File(modelsPackageDir, model.getTypeName() + ".java");
try (FileOutputStream out = new FileOutputStream(sourceFile)) {
PrintWriter sourcePrintWriter = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
writeDataModel(model, sourcePrintWriter);
}
}
}
protected void writeHandlersSources(File handlersPackageDir, File modelsPackageDir, List handlers) throws IOException {
log.info("Writing API handlers...");
for (RequestsHandler handler : handlers) {
File handlerInterfaceSourceFile = new File(handlersPackageDir, handler.getTypeName() + ".java");
try (FileOutputStream out = new FileOutputStream(handlerInterfaceSourceFile)) {
PrintWriter sourcePrintWriter = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
writeRequestsHandler(handler, sourcePrintWriter);
}
}
}
protected void writeRequestsHandler(RequestsHandler requestsHandler, PrintWriter out) throws IOException {
Context context = buildTemplateContext(requestsHandler, apiHandlerPackage);
writeSource(out, requestsHandlerSourceCodeTemplate.apply(context));
}
protected void writeDataModel(ObjectDataModel model, PrintWriter out) throws IOException {
Context context = buildTemplateContext(model, apiModelPackage);
writeSource(out, dataModelSourceCodeTemplate.apply(context));
}
protected Map buildGeneratorOpts() {
HashMap generatorOpts = new HashMap<>();
generatorOpts.put("useJsonNullable", useJsonNullable);
generatorOpts.put("generatorClassName", this.getClass().getName());
generatorOpts.put("generationDate", new Date());
return generatorOpts;
}
protected Context buildTemplateContext(Object model, String packageName) {
return Context
.newBuilder(model)
.combine("package", packageName)
.combine("generatorOpts", buildGeneratorOpts())
.build();
}
protected void writeSource(PrintWriter out, String source) {
String formattedSource;
try {
formattedSource = this.formatter.formatSource(source);
} catch (FormatterException e) {
log.error("Unable to format source:\n-------------------\n{}\n--------------------\n",
SourceCodeLineNumberUtil.addLineNumbers(source));
throw new CodeGeneratorException(e);
}
out.println(formattedSource);
out.flush();
}
protected String toFilePath(String packagePath) {
return packagePath.replaceAll("\\.", "//");
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy