Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow generation of default translations for automation modules #2966

Merged
merged 6 commits into from
May 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,10 @@ private String inferModuleTypeKey(String moduleTypeUID, String lastSegment) {
}

private String inferInputKey(String moduleTypeUID, String inputName, String lastSegment) {
return MODULE_TYPE + ".input." + moduleTypeUID + ".name." + inputName + "." + lastSegment;
return MODULE_TYPE + "." + moduleTypeUID + ".input." + inputName + "." + lastSegment;
}

private String inferOutputKey(String moduleTypeUID, String outputName, String lastSegment) {
return MODULE_TYPE + ".output." + moduleTypeUID + ".name." + outputName + "." + lastSegment;
return MODULE_TYPE + "." + moduleTypeUID + ".output." + outputName + "." + lastSegment;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
{
"name": "operator",
"type": "TEXT",
"label": "Operator",
"description": "the compare operator, allowed are <,>,=,!=,>=,<= matches",
"required": true,
"default": "="
Expand Down
5 changes: 5 additions & 0 deletions tools/i18n-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@
<artifactId>org.openhab.core.thing.xml</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
<dependency>
<groupId>org.openhab.core.bom</groupId>
<artifactId>org.openhab.core.bom.test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.openhab.core.thing.xml.internal.ChannelTypeXmlResult;
import org.openhab.core.thing.xml.internal.ThingTypeXmlResult;

import com.google.gson.JsonObject;

/**
* The bundle information provided by the openHAB XML files in the <code>OH-INF</code> directory.
*
Expand All @@ -39,6 +41,8 @@ public class BundleInfo {
private List<ChannelGroupTypeXmlResult> channelGroupTypesXml = new ArrayList<>(5);
private List<ChannelTypeXmlResult> channelTypesXml = new ArrayList<>(5);
private List<ThingTypeXmlResult> thingTypesXml = new ArrayList<>(5);
private List<JsonObject> moduleTypesJson = new ArrayList<>(5);
private List<JsonObject> ruleTemplateJson = new ArrayList<>(5);

public String getBindingId() {
return bindingId;
Expand Down Expand Up @@ -80,6 +84,22 @@ public void setChannelTypesXml(List<ChannelTypeXmlResult> channelTypesXml) {
this.channelTypesXml = channelTypesXml;
}

public List<JsonObject> getModuleTypesJson() {
return moduleTypesJson;
}

public void setModuleTypesJson(List<JsonObject> moduleTypesJson) {
this.moduleTypesJson = moduleTypesJson;
}

public List<JsonObject> getRuleTemplateJson() {
return ruleTemplateJson;
}

public void setRuleTemplateJson(List<JsonObject> ruleTemplateJson) {
this.ruleTemplateJson = ruleTemplateJson;
}

public List<ThingTypeXmlResult> getThingTypesXml() {
return thingTypesXml;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apache.maven.plugin.logging.Log;
import org.eclipse.jdt.annotation.NonNullByDefault;
Expand All @@ -30,6 +34,10 @@
import org.openhab.core.thing.xml.internal.ThingDescriptionReader;
import org.openhab.core.thing.xml.internal.ThingTypeXmlResult;

import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.thoughtworks.xstream.converters.ConversionException;

/**
Expand All @@ -42,6 +50,8 @@ public class BundleInfoReader {

private final Log log;

private final Predicate<Path> isJsonFile = path -> Files.isRegularFile(path) && path.toString().endsWith(".json");

public BundleInfoReader(Log log) {
this.log = log;
}
Expand All @@ -51,6 +61,8 @@ public BundleInfo readBundleInfo(Path ohinfPath) throws IOException {
readBindingInfo(ohinfPath, bundleInfo);
readConfigInfo(ohinfPath, bundleInfo);
readThingInfo(ohinfPath, bundleInfo);
readModuleTypeInfo(ohinfPath, bundleInfo);
readRuleTemplateInfo(ohinfPath, bundleInfo);
return bundleInfo;
}

Expand Down Expand Up @@ -129,4 +141,43 @@ private void readThingInfo(Path ohinfPath, BundleInfo bundleInfo) throws IOExcep
}
});
}

private void readModuleTypeInfo(Path ohinfPath, BundleInfo bundleInfo) throws IOException {
Path modulePath = ohinfPath.resolve("automation").resolve("moduletypes");
if (Files.exists(modulePath)) {
try (Stream<Path> files = Files.walk(modulePath)) {
List<JsonObject> moduleTypes = files.filter(isJsonFile).flatMap(this::readJsonElementsFromFile)
.map(JsonElement::getAsJsonObject).collect(Collectors.toList());
if (!moduleTypes.isEmpty()) {
bundleInfo.setModuleTypesJson(moduleTypes);
}
}
}
}

private void readRuleTemplateInfo(Path ohinfPath, BundleInfo bundleInfo) throws IOException {
Path template = ohinfPath.resolve("automation").resolve("templates");
if (Files.exists(template)) {
try (Stream<Path> files = Files.walk(template)) {
List<JsonObject> ruleTemplates = files.filter(isJsonFile).flatMap(this::readJsonElementsFromFile)
.map(JsonElement::getAsJsonObject).collect(Collectors.toList());
if (!ruleTemplates.isEmpty()) {
bundleInfo.setRuleTemplateJson(ruleTemplates);
}
}
}
}

private Stream<JsonElement> readJsonElementsFromFile(Path path) {
try {
JsonElement element = JsonParser.parseString(Files.readString(path));
if (element.isJsonObject()) {
return element.getAsJsonObject().entrySet().stream().map(Map.Entry::getValue)
.filter(JsonElement::isJsonArray)
.flatMap(a -> StreamSupport.stream(a.getAsJsonArray().spliterator(), false));
}
} catch (IOException ignored) {
}
return Stream.of(JsonNull.INSTANCE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ protected String generateDefaultTranslations(Path defaultTranslationsPath) {
XmlToTranslationsConverter xmlConverter = new XmlToTranslationsConverter();
Translations generatedTranslations = xmlConverter.convert(bundleInfo);

JsonToTranslationsConverter jsonConverter = new JsonToTranslationsConverter();
jsonConverter.convert(bundleInfo).sections.forEach(generatedTranslations::addSection);

PropertiesToTranslationsConverter propertiesConverter = new PropertiesToTranslationsConverter(getLog());
Translations existingTranslations = propertiesConverter.convert(defaultTranslationsPath);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.tools.i18n.plugin;

import static org.openhab.core.tools.i18n.plugin.Translations.TranslationsEntry.entry;
import static org.openhab.core.tools.i18n.plugin.Translations.TranslationsGroup.group;

import java.util.Comparator;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.Stream.Builder;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.tools.i18n.plugin.Translations.TranslationsEntry;
import org.openhab.core.tools.i18n.plugin.Translations.TranslationsGroup;
import org.openhab.core.tools.i18n.plugin.Translations.TranslationsSection;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

/**
* Converts JSON based {@link BundleInfo} to {@link Translations}.
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class JsonToTranslationsConverter {

private static final Pattern OPTION_ESCAPE_PATTERN = Pattern.compile("([ :=])");

public Translations convert(BundleInfo bundleInfo) {
return new Translations(Stream.of( //
moduleTypeSection(bundleInfo), ruleTemplateSection(bundleInfo)//
).flatMap(Function.identity()).collect(Collectors.toList()));
}

private Stream<TranslationsSection> moduleTypeSection(BundleInfo bundleInfo) {
Builder<TranslationsSection> sectionBuilder = Stream.builder();

bundleInfo.getModuleTypesJson().stream().flatMap(this::getModuleType).forEach(sectionBuilder::add);
return sectionBuilder.build().sorted(Comparator.comparing(s -> s.header));
}

private Stream<TranslationsSection> ruleTemplateSection(BundleInfo bundleInfo) {
Builder<TranslationsSection> sectionBuilder = Stream.builder();

bundleInfo.getRuleTemplateJson().stream().flatMap(this::getRuleTemplate).forEach(sectionBuilder::add);
return sectionBuilder.build().sorted(Comparator.comparing(s -> s.header));
}

private Stream<TranslationsSection> getModuleType(JsonObject moduleType) {
String uid = getAsString(moduleType, "uid").orElse("");
if (uid.isBlank()) {
return Stream.of();
}

String globalPrefix = "module-type." + uid + ".";
Builder<TranslationsGroup> groupBuilder = Stream.builder();

// global entries
Builder<TranslationsEntry> entriesBuilder = Stream.builder();
getAsString(moduleType, "label").ifPresent(label -> entriesBuilder.add(entry(globalPrefix + "label", label)));
getAsString(moduleType, "description")
.ifPresent(description -> entriesBuilder.add(entry(globalPrefix + "description", description)));
groupBuilder.add(group(entriesBuilder.build()));

// configDescriptions
JsonElement configDescriptionsElement = moduleType.get("configDescriptions");
if (configDescriptionsElement != null) {
String prefix = globalPrefix + "config.";
groupBuilder.add(getGroupFromArray(configDescriptionsElement, prefix));
}

// inputs
JsonElement inputsElement = moduleType.get("inputs");
if (inputsElement != null) {
String prefix = globalPrefix + "input.";
groupBuilder.add(getGroupFromArray(inputsElement, prefix));
}

// outputs
JsonElement outputsElement = moduleType.get("outputs");
if (outputsElement != null) {
String prefix = globalPrefix + "output.";
groupBuilder.add(getGroupFromArray(outputsElement, prefix));
}

return Stream.of(new TranslationsSection(uid, groupBuilder.build().collect(Collectors.toList())));
}

private Stream<TranslationsSection> getRuleTemplate(JsonObject ruleTemplate) {
String uid = getAsString(ruleTemplate, "uid").orElse("");
if (uid.isBlank()) {
return Stream.of();
}

String globalPrefix = "rule-template." + uid + ".";
Builder<TranslationsGroup> groupBuilder = Stream.builder();

// global entries
Builder<TranslationsEntry> entriesBuilder = Stream.builder();
getAsString(ruleTemplate, "label").ifPresent(label -> entriesBuilder.add(entry(globalPrefix + "label", label)));
getAsString(ruleTemplate, "description")
.ifPresent(description -> entriesBuilder.add(entry(globalPrefix + "description", description)));
groupBuilder.add(group(entriesBuilder.build()));

return Stream.of(new TranslationsSection(uid, groupBuilder.build().collect(Collectors.toList())));
}

private TranslationsGroup getGroupFromArray(JsonElement parentElement, String prefix) {
Builder<TranslationsEntry> entriesBuilder = Stream.builder();
for (JsonElement jsonElement : parentElement.getAsJsonArray()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
getAsString(jsonObject, "name").ifPresent(name -> {
String namePrefix = prefix + name + ".";
getAsString(jsonObject, "label")
.ifPresent(label -> entriesBuilder.add(entry(namePrefix + "label", label)));
getAsString(jsonObject, "description")
.ifPresent(description -> entriesBuilder.add(entry(namePrefix + "description", description)));
JsonElement optionsElement = jsonObject.get("options");
if (optionsElement != null) {
for (JsonElement optionElement : optionsElement.getAsJsonArray()) {
JsonObject optionObject = optionElement.getAsJsonObject();
getAsString(optionObject, "value")
.ifPresent(value -> getAsString(optionObject, "label").ifPresent(label -> {
String optionKey = namePrefix + "option."
+ OPTION_ESCAPE_PATTERN.matcher(value).replaceAll("\\\\$1");
entriesBuilder.add(entry(optionKey, label));
}));
}
}
});
}
return group(entriesBuilder.build());
}

private Optional<String> getAsString(JsonObject object, String key) {
JsonElement element = object.get(key);
return element == null ? Optional.empty() : Optional.of(element.getAsString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public void readBindingInfo() throws IOException {
assertThat(bundleInfo.getChannelTypesXml().size(), is(2));
assertThat(bundleInfo.getConfigDescriptions().size(), is(1));
assertThat(bundleInfo.getThingTypesXml().size(), is(2));
assertThat(bundleInfo.getModuleTypesJson().size(), is(2));
assertThat(bundleInfo.getRuleTemplateJson().size(), is(0));
}

@Test
Expand All @@ -62,6 +64,8 @@ public void readGenericBundleInfo() throws IOException {
assertThat(bundleInfo.getChannelTypesXml().size(), is(0));
assertThat(bundleInfo.getConfigDescriptions().size(), is(1));
assertThat(bundleInfo.getThingTypesXml().size(), is(0));
assertThat(bundleInfo.getModuleTypesJson().size(), is(0));
assertThat(bundleInfo.getRuleTemplateJson().size(), is(0));
}

@Test
Expand All @@ -75,5 +79,7 @@ public void readPathWithoutAnyInfo() throws IOException {
assertThat(bundleInfo.getChannelTypesXml().size(), is(0));
assertThat(bundleInfo.getConfigDescriptions().size(), is(0));
assertThat(bundleInfo.getThingTypesXml().size(), is(0));
assertThat(bundleInfo.getModuleTypesJson().size(), is(0));
assertThat(bundleInfo.getRuleTemplateJson().size(), is(0));
}
}
Loading