Apache Freemarker
对于 Freemarker 语言输出,我们使用未经修改的 Apache Freemarker 版本来生成输出。
提供 PLC4X 语言模块的样板代码位于 org.apache.plc4x.plugins:plc4x-code-generation-language-base-freemarker maven 模块中,具体在 FreemarkerLanguageOutput 类中。
该类配置了 Freemarker 上下文,并在其中提供标准化属性:
- packageName:Java 风格的包名,可用于创建某种形式的目录结构。
- typeName:简单的字符串类型名。
- type:
ComplexTypeDefinition实例,包含应为其生成代码的类型的全部信息。 - helper:由于有时在 Freemarker 中生成所有输出非常复杂,helper 允许提供模板所使用的代码,以辅助生成输出。
一个基于 Freemarker 的输出模块,必须提供一组 Template 实例以及一个 FreemarkerLanguageTemplateHelper 实例。
总体上,我们将模板分为以下几类:
Spec Templates(整个驱动程序总共生成一次的全局输出)Complex Type Templates(为复杂类型生成输出)Enum Templates(为枚举类型生成输出)DataIO Templates(为读取和写入 PlcValue 生成输出,PlcValue 是我们 PLC4X 用来向用户呈现输入和输出数据的形式)
对于每一类,开发者可以提供一个模板列表,从而为每个类型生成多个文件(这对于 C 这类语言很重要,因为每个类型我们都必须生成一个 Header file (.h) 和一个 Implementation (.c))。
FreemarkerLanguageOutput 所做的,是遍历协议模块提供的所有类型,然后遍历当前语言定义的所有模板。
此工具中使用的唯一约定是:模板生成的第一行输出将被视为相对于基础输出目录的路径。
它会自动创建所需的所有中间目录,并将输入的其余部分生成到第一行所指定的文件中。
如果该行为空,则跳过该类型的输出。
示例 Java 输出
package org.apache.plc4x.language.java;
import com.google.googlejavaformat.java.Formatter;
import com.google.googlejavaformat.java.FormatterException;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.io.FileUtils;
import org.apache.plc4x.plugins.codegenerator.protocol.freemarker.FreemarkerLanguageOutput;
import org.apache.plc4x.plugins.codegenerator.protocol.freemarker.FreemarkerLanguageTemplateHelper;
import org.apache.plc4x.plugins.codegenerator.types.definitions.TypeDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class JavaLanguageOutput extends FreemarkerLanguageOutput {
private static final Logger LOGGER = LoggerFactory.getLogger(JavaLanguageOutput.class);
private final Formatter formatter = new Formatter();
@Override
public String getName() {
return "Java";
}
@Override
public Set<String> supportedOptions() {
return Collections.singleton("package");
}
@Override
public List<String> supportedOutputFlavors() {
return Arrays.asList("read-write", "read-only", "passive");
}
@Override
protected List<Template> getSpecTemplates(Configuration freemarkerConfiguration) {
return Collections.emptyList();
}
@Override
protected List<Template> getComplexTypeTemplates(Configuration freemarkerConfiguration) throws IOException {
return Arrays.asList(
freemarkerConfiguration.getTemplate("templates/java/pojo-template.java.ftlh"),
freemarkerConfiguration.getTemplate("templates/java/io-template.java.ftlh"));
}
@Override
protected List<Template> getEnumTypeTemplates(Configuration freemarkerConfiguration) throws IOException {
return Collections.singletonList(
freemarkerConfiguration.getTemplate("templates/java/enum-template.java.ftlh"));
}
@Override
protected List<Template> getDataIoTemplates(Configuration freemarkerConfiguration) throws IOException {
return Collections.singletonList(
freemarkerConfiguration.getTemplate("templates/java/data-io-template.java.ftlh"));
}
@Override
protected FreemarkerLanguageTemplateHelper getHelper(TypeDefinition thisType, String protocolName, String flavorName, Map<String, TypeDefinition> types,
Map<String, String> options) {
return new JavaLanguageTemplateHelper(thisType, protocolName, flavorName, types, options);
}
@Override
protected void postProcessTemplateOutput(File outputFile) {
try {
FileUtils.writeStringToFile(
outputFile,
formatter.formatSourceAndFixImports(
FileUtils.readFileToString(outputFile, StandardCharsets.UTF_8)
),
StandardCharsets.UTF_8
);
} catch (IOException | FormatterException e) {
LOGGER.error("Error formatting {}", outputFile, e);
}
}
}getName 方法返回 Java,这正是需要在 plc4x-maven-plugin 配置的 language 选项中定义的内容,用于选择该输出格式。
supportedOptions 告诉插件此代码生成输出支持哪些 option 标签。对于 Java 输出,只有 package 选项,它定义了生成输出的包名。
通过 supportedOutputFlavors,我们告诉用户,总体上我们支持三个选项:read-write、read-only 和 passive,它们是代码生成插件 outputFlavor 配置选项的有效输入。
在这种情况下,Java 不需要为 java 生成任何全局文件,因此我们只需返回一个空集合。
对于复杂类型,我们目前使用两个模板(不过这很快会减少为一个)。因此,对于协议定义中的每个复杂类型,都会执行模板 templates/java/pojo-template.java.ftlh 和 templates/java/io-template.java.ftlh。
对于枚举类型,只使用一个模板。
与 data-io 的处理方式相同。
下一个重要的方法是 getHelper 方法,它返回一个对象,该对象以 helper 为名传递给模板。如前所述,许多操作用纯 Freemarker 代码实现会过于复杂,因此借助这些辅助工具,每种语言都可以提供一个辅助工具类来处理这些复杂操作。
下面是一个用于生成 Java POJO 的模板片段示例:
${helper.packageName(protocolName, languageName, outputFlavor)?replace(".", "/")}/${type.name}.java
/*
* 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package ${helper.packageName(protocolName, languageName, outputFlavor)};
... imports ...
// Code generated by code-generation. DO NOT EDIT.
public<#if type.isDiscriminatedParentTypeDefinition()> abstract</#if> class ${type.name}<#if type.parentType??> extends ${type.parentType.name}</#if> implements Message {
... SNIP ...
}如你所见,第一行将生成待生成输出文件的文件路径。
随着我们为不同语言生成越来越多的输出,我们发现 Helper 工具中需要的大量代码存在重复,因此我们引入了一个所谓的 BaseFreemarkerLanguageTemplateHelper,其中包含了在生成新的语言输出时非常重要的一大批内容。
评论
登录后参与评论
KnowForge