From e2cf94b928f74b81db01f72325c3538b30d9f04b Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Sat, 18 Sep 2021 00:20:46 +1200 Subject: [PATCH] first cut --- build.gradle | 2 +- src/main/java/voruti/json2config/Starter.java | 15 +++ .../voruti/json2config/model/IAppendable.java | 11 ++ .../model/json/JsonChannelLink.java | 16 ++- .../json2config/model/json/JsonMetadata.java | 44 ++++++++ .../voruti/json2config/service/Appender.java | 103 ++++++++++++++++++ .../json2config/service/ChannelAppender.java | 90 +-------------- .../voruti/json2config/service/Constants.java | 1 + .../json2config/service/MetadataAppender.java | 75 +++++++++++++ .../json2config/service/SharedService.java | 20 ++-- .../java/voruti/json2config/service/Type.java | 2 +- .../voruti/json2config/IntegrationTest.java | 4 +- .../resources/openhab2_example1.Metadata.json | 41 +++++++ src/test/resources/openhab2_example1.items | 4 +- ...hab2_multipleChannelsOneItem.Metadata.json | 28 +++++ .../openhab2_multipleChannelsOneItem.items | 2 +- 16 files changed, 353 insertions(+), 105 deletions(-) create mode 100644 src/main/java/voruti/json2config/model/IAppendable.java create mode 100644 src/main/java/voruti/json2config/model/json/JsonMetadata.java create mode 100644 src/main/java/voruti/json2config/service/Appender.java create mode 100644 src/main/java/voruti/json2config/service/MetadataAppender.java create mode 100644 src/test/resources/openhab2_example1.Metadata.json create mode 100644 src/test/resources/openhab2_multipleChannelsOneItem.Metadata.json diff --git a/build.gradle b/build.gradle index ed105b8..bbba84a 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java' id 'application' id 'com.github.johnrengelman.shadow' version '7.0.0' - id "io.freefair.lombok" version "6.1.0-m3" +// id "io.freefair.lombok" version "6.1.0-m3" } group = 'voruti' diff --git a/src/main/java/voruti/json2config/Starter.java b/src/main/java/voruti/json2config/Starter.java index 00c0780..1e6a3b6 100644 --- a/src/main/java/voruti/json2config/Starter.java +++ b/src/main/java/voruti/json2config/Starter.java @@ -6,6 +6,7 @@ import voruti.json2config.service.ChannelAppender; import voruti.json2config.service.Constants; import voruti.json2config.service.Converter; +import voruti.json2config.service.MetadataAppender; import voruti.json2config.service.Type; /** @@ -22,6 +23,10 @@ public class Starter implements Runnable { @Option(names = {"-c", "--channel", "--channel-link", "--create-channels", "--create-channel-links"}, description = "enable the appending feature") private boolean doChannelLinks; + + @Option(names = {"-m", "--metadata", "--append-metadata"}, + description = "enable the metadata appending feature") + private boolean doMetadata; @Option(names = {"-3", "--openhab3", "--v3", "--openhab-v3", "--openhabv3", "--openhab-3"}, description = "set default file names used since openHAB version 3.X") @@ -38,6 +43,11 @@ public class Starter implements Runnable { description = "specify the .json file location containing the channel links") private String channelFile; + @Option(names = {"--metadata-file"}, + defaultValue = Constants.DEFAULT_V2_METADATA_FILE, + description = "specify the .json file location containing the metadata") + private String metadataFile; + @Option(names = {"-o", "--out", "--items"}, defaultValue = "json.items", description = "specify the output file") @@ -78,5 +88,10 @@ public void run() { if (doChannelLinks) { ChannelAppender.start(channelFile, directory); } + + // start MetadataAppender: + if (doMetadata) { + MetadataAppender.start(metadataFile, directory); + } } } diff --git a/src/main/java/voruti/json2config/model/IAppendable.java b/src/main/java/voruti/json2config/model/IAppendable.java new file mode 100644 index 0000000..2b2112b --- /dev/null +++ b/src/main/java/voruti/json2config/model/IAppendable.java @@ -0,0 +1,11 @@ +package voruti.json2config.model; + +public interface IAppendable extends IConvertible { + + /** + * + * @return + */ + String getItemName(); + +} diff --git a/src/main/java/voruti/json2config/model/json/JsonChannelLink.java b/src/main/java/voruti/json2config/model/json/JsonChannelLink.java index aa1e331..e67b313 100644 --- a/src/main/java/voruti/json2config/model/json/JsonChannelLink.java +++ b/src/main/java/voruti/json2config/model/json/JsonChannelLink.java @@ -1,15 +1,16 @@ package voruti.json2config.model.json; -import com.google.gson.Gson; -import lombok.Getter; -import voruti.json2config.model.IConvertible; - import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import com.google.gson.Gson; + +import lombok.Getter; +import voruti.json2config.model.IAppendable; + @Getter -public class JsonChannelLink implements IConvertible { +public class JsonChannelLink implements IAppendable { private static final Gson GSON = new Gson(); private Value value; @@ -58,4 +59,9 @@ private static class Configuration { private Map properties; } } + + @Override + public String getItemName() { + return value.itemName; + } } diff --git a/src/main/java/voruti/json2config/model/json/JsonMetadata.java b/src/main/java/voruti/json2config/model/json/JsonMetadata.java new file mode 100644 index 0000000..269ac0a --- /dev/null +++ b/src/main/java/voruti/json2config/model/json/JsonMetadata.java @@ -0,0 +1,44 @@ +package voruti.json2config.model.json; + +import java.util.List; + +import com.google.gson.Gson; + +import lombok.Getter; +import voruti.json2config.model.IAppendable; + +@Getter +public class JsonMetadata implements IAppendable { + + private Value value; + + @Override + public String toConfigLine(String lineBefore) { + // first metadata or append: + String format = "%s=\"%s\"}"; + if (lineBefore.endsWith("}")) { + lineBefore = lineBefore.substring(0, lineBefore.length() - 1); + format = "%s, " + format; + } else { + format = "%s {" + format; + } + return String.format(format, lineBefore, value.key.segments.get(0), value.value).strip(); + } + + @Getter + public static class Value { + private Key key; + private String value; + + private static class Key { + private List segments; + } + + } + + @Override + public String getItemName() { + return value.key.segments.get(1); + } + +} diff --git a/src/main/java/voruti/json2config/service/Appender.java b/src/main/java/voruti/json2config/service/Appender.java new file mode 100644 index 0000000..5811f08 --- /dev/null +++ b/src/main/java/voruti/json2config/service/Appender.java @@ -0,0 +1,103 @@ +package voruti.json2config.service; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; +import voruti.json2config.model.IAppendable; + +@Slf4j +public class Appender { + + private Appender() { + } + + /** + * Returns a {@link List} of Strings containing the names of all items in + * {@code fileName}. + * + * @param fileName the file(-Name) to open and search for items + * @return a {@link List} containing the names of the items + */ + public static List getItemNamesFromFile(String fileName) { + try { + return Arrays.stream(SharedService.openFileToString(fileName).split("\n")) + .filter(line -> !line.isEmpty() && !line.toLowerCase().startsWith("group")) + .map(Appender::searchNameInLine) + .filter(itemName -> !itemName.isEmpty()) + .collect(Collectors.toList()); + } catch (IOException e) { + log.error(Constants.LOG_CANT_OPEN_FILE, fileName); + } + + return List.of(); + } + + /** + * Appends the {@code appendable} after the item in {@code fileName}. + * + * @param appendable the data to append after the item + * @param fileName the file to search for the item + * @return {@code true} if the {@code appendable} could be appended, {@code false} otherwise + */ + public static boolean appendToItemInFile(IAppendable appendable, String fileName) { + boolean successful = false; + + try { + List originalLines = Arrays.asList(SharedService.openFileToString(fileName).split("\n")); + List modifiedLines = originalLines.stream() + .map(line -> { + if (!line.isEmpty() && !line.toLowerCase().startsWith("group")) { + String readItemName = searchNameInLine(line); + if (!readItemName.isEmpty() && readItemName.equals(appendable.getItemName())) { + return appendable.toConfigLine(line); + } + } + // return unmodified line: + return line; + }) + .collect(Collectors.toList()); + + if (!originalLines.equals(modifiedLines)) { + successful = SharedService.writeLinesToFile(modifiedLines, fileName); + } + } catch (IOException e) { + log.error(Constants.LOG_CANT_OPEN_FILE, fileName); + } + + return successful; + } + + /** + * Searches for an item name in the {@code line}. + * + * @param line the line to search in + * @return name of the item if found, {@code null} otherwise + */ + public static String searchNameInLine(String line) { + String[] arr = line.strip().split("\\s+"); + + String itemName = ""; + if (arr.length >= 2) { + itemName = arr[1]; + } + + return itemName; + } + + /** + * Searches for ".items" files in the {@code directory}. + * + * @param directory the directory to search in + * @return a {@link List} with all file paths of ".items" files + */ + public static List findItemsFilesInDir(String directory) { + return Arrays.stream(Objects.requireNonNull(new File(directory).listFiles((dir, filename) -> filename.endsWith(".items")))) + .map(File::getAbsolutePath) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/voruti/json2config/service/ChannelAppender.java b/src/main/java/voruti/json2config/service/ChannelAppender.java index 5f3d403..f185c5d 100644 --- a/src/main/java/voruti/json2config/service/ChannelAppender.java +++ b/src/main/java/voruti/json2config/service/ChannelAppender.java @@ -43,10 +43,10 @@ public static void start(String channelLinkFile, String directory) { log.trace("channelLinkList={}", channelLinkList); // search items files: - List itemsFiles = findItemsFilesInDir(directory); + List itemsFiles = Appender.findItemsFilesInDir(directory); // get names of all items: List itemNamesList = itemsFiles.stream() - .map(ChannelAppender::getItemNamesFromFile) + .map(Appender::getItemNamesFromFile) .flatMap(Collection::stream) .collect(Collectors.toList()); log.info("Found {} items", itemNamesList.size()); @@ -63,7 +63,7 @@ public static void start(String channelLinkFile, String directory) { int count = 0; for (JsonChannelLink channel : relevantChannelLinkList) { for (String iFile : itemsFiles) { - if (setChannelToItemInFile(channel, iFile)) { + if (Appender.appendToItemInFile(channel, iFile)) { count++; } } @@ -76,88 +76,4 @@ public static void start(String channelLinkFile, String directory) { } } - /** - * Returns a {@link List} of Strings containing the names of all items in - * {@code fileName}. - * - * @param fileName the file(-Name) to open and search for items - * @return a {@link List} containing the names of the items - */ - public static List getItemNamesFromFile(String fileName) { - try { - return Arrays.stream(SharedService.openFileToString(fileName).split("\n")) - .filter(line -> !line.isEmpty() && !line.toLowerCase().startsWith("group")) - .map(ChannelAppender::searchNameInLine) - .filter(itemName -> !itemName.isEmpty()) - .collect(Collectors.toList()); - } catch (IOException e) { - log.error(Constants.LOG_CANT_OPEN_FILE, fileName); - } - - return List.of(); - } - - /** - * Appends the {@code channelLink} after the item in {@code fileName}. - * - * @param channelLink the channel link to append after the item - * @param fileName the file to search for the item - * @return {@code true} if the {@code channelLink} could be appended, {@code false} otherwise - */ - public static boolean setChannelToItemInFile(JsonChannelLink channelLink, String fileName) { - boolean successful = false; - - try { - List originalLines = Arrays.asList(SharedService.openFileToString(fileName).split("\n")); - List modifiedLines = originalLines.stream() - .map(line -> { - if (!line.isEmpty() && !line.toLowerCase().startsWith("group")) { - String readItemName = searchNameInLine(line); - if (!readItemName.isEmpty() && readItemName.equals(channelLink.getValue().getItemName())) { - return channelLink.toConfigLine(line); - } - } - // return unmodified line: - return line; - }) - .collect(Collectors.toList()); - - if (!originalLines.equals(modifiedLines)) { - successful = SharedService.writeLinesToFile(modifiedLines, fileName); - } - } catch (IOException e) { - log.error(Constants.LOG_CANT_OPEN_FILE, fileName); - } - - return successful; - } - - /** - * Searches for an item name in the {@code line}. - * - * @param line the line to search in - * @return name of the item if found, {@code null} otherwise - */ - public static String searchNameInLine(String line) { - String[] arr = line.strip().split("\\s+"); - - String itemName = ""; - if (arr.length >= 2) { - itemName = arr[1]; - } - - return itemName; - } - - /** - * Searches for ".items" files in the {@code directory}. - * - * @param directory the directory to search in - * @return a {@link List} with all file paths of ".items" files - */ - public static List findItemsFilesInDir(String directory) { - return Arrays.stream(Objects.requireNonNull(new File(directory).listFiles((dir, filename) -> filename.endsWith(".items")))) - .map(File::getAbsolutePath) - .collect(Collectors.toList()); - } } diff --git a/src/main/java/voruti/json2config/service/Constants.java b/src/main/java/voruti/json2config/service/Constants.java index 8bd4718..a013074 100644 --- a/src/main/java/voruti/json2config/service/Constants.java +++ b/src/main/java/voruti/json2config/service/Constants.java @@ -10,6 +10,7 @@ public final class Constants { public static final String DEFAULT_V3_JSON_FILE = "org.openhab.core.items.Item.json"; public static final String DEFAULT_V2_CHANNEL_FILE = "org.eclipse.smarthome.core.thing.link.ItemChannelLink.json"; public static final String DEFAULT_V3_CHANNEL_FILE = "org.openhab.core.thing.link.ItemChannelLink.json"; + public static final String DEFAULT_V2_METADATA_FILE = "org.eclipse.smarthome.core.items.Metadata.json"; private Constants() { diff --git a/src/main/java/voruti/json2config/service/MetadataAppender.java b/src/main/java/voruti/json2config/service/MetadataAppender.java new file mode 100644 index 0000000..1bce1ff --- /dev/null +++ b/src/main/java/voruti/json2config/service/MetadataAppender.java @@ -0,0 +1,75 @@ +package voruti.json2config.service; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; +import voruti.json2config.model.json.JsonChannelLink; +import voruti.json2config.model.json.JsonMetadata; + +/** + * @author sbholmes + */ +@Slf4j +public class MetadataAppender { + + private MetadataAppender() { + } + + /** + * Appends the metadata from {@code metadataFile} to all ".items" files + * in the {@code directory}. + * + * @param metadataFile path to the file which contains the metadata in + * JSON format + * @param directory the directory in which to search for ".items" files + */ + public static void start(String metadataFile, String directory) { + log.debug("Starting MetadataAppender with metadataFile={}, directory={}", metadataFile, directory); + + try { + // open file: + String content = SharedService.openFileToString(metadataFile); + // map to list of metadata: + List metadataList = SharedService.jsonToConvertibleMap(content, Type.METADATA).values().stream() + .map(JsonMetadata.class::cast) + .collect(Collectors.toList()); + log.info("Found {} metadata", metadataList.size()); + log.trace("metadataList={}", metadataList); + + // search items files: + List itemsFiles = Appender.findItemsFilesInDir(directory); + // get names of all items: + List itemNamesList = itemsFiles.stream() + .map(Appender::getItemNamesFromFile) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + log.info("Found {} items", itemNamesList.size()); + log.trace("itemNamesList={}", itemNamesList); + + // only channels present in both lists: + List relevantMetadataList = metadataList.stream() + .filter(metadata -> itemNamesList.stream() + .anyMatch(itemName -> itemName.equals(metadata.getItemName()))) + .collect(Collectors.toList()); + log.info("{} match with each other", relevantMetadataList.size()); + log.trace("relevantChannelLinkList={}", relevantMetadataList); + + int count = 0; + for (JsonMetadata channel : relevantMetadataList) { + for (String iFile : itemsFiles) { + if (Appender.appendToItemInFile(channel, iFile)) { + count++; + } + } + } + log.info("Successfully appended {} metadata!", count); + + } catch (IOException e) { + log.error(Constants.LOG_CANT_OPEN_FILE, metadataFile); + } + } + +} diff --git a/src/main/java/voruti/json2config/service/SharedService.java b/src/main/java/voruti/json2config/service/SharedService.java index 78bd390..ea50342 100644 --- a/src/main/java/voruti/json2config/service/SharedService.java +++ b/src/main/java/voruti/json2config/service/SharedService.java @@ -1,12 +1,5 @@ package voruti.json2config.service; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import lombok.extern.slf4j.Slf4j; -import voruti.json2config.model.IConvertible; -import voruti.json2config.model.json.JsonChannelLink; -import voruti.json2config.model.json.JsonItem; - import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; @@ -15,6 +8,15 @@ import java.util.List; import java.util.Map; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import lombok.extern.slf4j.Slf4j; +import voruti.json2config.model.IConvertible; +import voruti.json2config.model.json.JsonChannelLink; +import voruti.json2config.model.json.JsonItem; +import voruti.json2config.model.json.JsonMetadata; + @Slf4j public final class SharedService { @@ -60,6 +62,10 @@ public static Map jsonToConvertibleMap(String json, Type t mapType = new TypeToken>() { }.getType(); break; + case METADATA: + mapType = new TypeToken>() { + }.getType(); + break; } return GSON.fromJson(json, mapType); diff --git a/src/main/java/voruti/json2config/service/Type.java b/src/main/java/voruti/json2config/service/Type.java index 6868a61..b50e891 100644 --- a/src/main/java/voruti/json2config/service/Type.java +++ b/src/main/java/voruti/json2config/service/Type.java @@ -1,5 +1,5 @@ package voruti.json2config.service; public enum Type { - ITEM, THING, CHANNEL + ITEM, THING, CHANNEL, METADATA } diff --git a/src/test/java/voruti/json2config/IntegrationTest.java b/src/test/java/voruti/json2config/IntegrationTest.java index 30f2ef4..1b8c590 100644 --- a/src/test/java/voruti/json2config/IntegrationTest.java +++ b/src/test/java/voruti/json2config/IntegrationTest.java @@ -36,8 +36,10 @@ void itemsAndChannels(String testName) throws IOException { Starter.main(new String[]{"-i", RESOURCES + testName + ".Item.json", "-o", generatedItemsFile, "-c", + "-m", "-d", TEMPORARY, - "--channel-file", RESOURCES + testName + ".ItemChannelLink.json"}); + "--channel-file", RESOURCES + testName + ".ItemChannelLink.json", + "--metadata-file", RESOURCES + testName + ".Metadata.json"}); // assert: // load generated .items file: diff --git a/src/test/resources/openhab2_example1.Metadata.json b/src/test/resources/openhab2_example1.Metadata.json new file mode 100644 index 0000000..169daef --- /dev/null +++ b/src/test/resources/openhab2_example1.Metadata.json @@ -0,0 +1,41 @@ +{ + "ga:GF_Corridor": { + "class": "org.eclipse.smarthome.core.items.Metadata", + "value": { + "key": { + "segments": [ + "ga", + "GF_Corridor" + ] + }, + "value": "Light", + "configuration": {} + } + }, + "ga:GF_Corridor_Light": { + "class": "org.eclipse.smarthome.core.items.Metadata", + "value": { + "key": { + "segments": [ + "ga", + "GF_Corridor_Light" + ] + }, + "value": "Light", + "configuration": {} + } + }, + "ga:GF_Kitchen_Light": { + "class": "org.eclipse.smarthome.core.items.Metadata", + "value": { + "key": { + "segments": [ + "ga", + "GF_Kitchen_Light" + ] + }, + "value": "Light", + "configuration": {} + } + } +} \ No newline at end of file diff --git a/src/test/resources/openhab2_example1.items b/src/test/resources/openhab2_example1.items index 8684ce2..a5688ff 100644 --- a/src/test/resources/openhab2_example1.items +++ b/src/test/resources/openhab2_example1.items @@ -30,7 +30,7 @@ Switch FF_Library_Power "Power O Switch FF_Toilet_Light "Light" (FF_Toilet, gLight) ["Lighting", "Switchable"] {channel="mqtt:topic:52b61fd6:light"} Switch GF_Bathroom_Light "Light" (GF_Bathroom, gLight) ["Lighting", "Switchable"] Switch GF_Bedroom_Light "Light" (GF_Bedroom, gLight) ["Lighting", "Switchable"] -Switch GF_Corridor_Light "Light" (GF_Corridor, gLight) ["Lighting", "Switchable"] -Switch GF_Kitchen_Light "Light" (GF_Kitchen, gLight) ["Lighting", "Switchable"] {channel="mqtt:topic:40d4c19b:light"} +Switch GF_Corridor_Light "Light" (GF_Corridor, gLight) ["Lighting", "Switchable"] {ga="Light"} +Switch GF_Kitchen_Light "Light" (GF_Kitchen, gLight) ["Lighting", "Switchable"] {channel="mqtt:topic:40d4c19b:light", ga="Light"} Switch GF_LivingRoom_Light "Light" (GF_LivingRoom, gLight) ["Lighting", "Switchable"] Switch GF_LivingRoom_Power "Power Outlet" (GF_LivingRoom, gPower) ["Switch", "Switchable"] diff --git a/src/test/resources/openhab2_multipleChannelsOneItem.Metadata.json b/src/test/resources/openhab2_multipleChannelsOneItem.Metadata.json new file mode 100644 index 0000000..5326d36 --- /dev/null +++ b/src/test/resources/openhab2_multipleChannelsOneItem.Metadata.json @@ -0,0 +1,28 @@ +{ + "ga:MultiItem": { + "class": "org.eclipse.smarthome.core.items.Metadata", + "value": { + "key": { + "segments": [ + "ga", + "MultiItem" + ] + }, + "value": "Switch", + "configuration": {} + } + }, + "ga:GF_Kitchen_Light": { + "class": "org.eclipse.smarthome.core.items.Metadata", + "value": { + "key": { + "segments": [ + "ga", + "GF_Kitchen_Light" + ] + }, + "value": "Light", + "configuration": {} + } + } +} \ No newline at end of file diff --git a/src/test/resources/openhab2_multipleChannelsOneItem.items b/src/test/resources/openhab2_multipleChannelsOneItem.items index 0ada3f9..3d3d01c 100644 --- a/src/test/resources/openhab2_multipleChannelsOneItem.items +++ b/src/test/resources/openhab2_multipleChannelsOneItem.items @@ -1 +1 @@ -Switch MultiItem {channel="mqtt:topic:52b61fd6:light", channel="mqtt:topic:d589b50d:power"[profile="system:follow"], channel="mqtt:topic:3621578b:switch"[sourceFormat="%.1f °C", profile="transform:REGEX", function=".*\u003d(\\\\d*.\\\\d*).*"], channel="mqtt:topic:1c4c5e84:light"} +Switch MultiItem {channel="mqtt:topic:52b61fd6:light", channel="mqtt:topic:d589b50d:power"[profile="system:follow"], channel="mqtt:topic:3621578b:switch"[sourceFormat="%.1f °C", profile="transform:REGEX", function=".*\u003d(\\\\d*.\\\\d*).*"], channel="mqtt:topic:1c4c5e84:light", ga="Switch"}