Skip to content

Commit

Permalink
Merge pull request #63 from querdenker2k/generate_actions
Browse files Browse the repository at this point in the history
generate actions
  • Loading branch information
seaside1 authored Oct 31, 2022
2 parents 9a1b906 + c9cf0d6 commit 8a9be94
Show file tree
Hide file tree
Showing 17 changed files with 638 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* 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.automation.jrule.actions;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.IntFunction;

import org.openhab.core.model.script.actions.Things;
import org.openhab.core.thing.binding.ThingActions;

/**
* The {@link JRuleAbstractAction}
*
* @author Robert Delbrück - Initial contribution
*/
public abstract class JRuleAbstractAction {
private final ThingActions thingActions;
private final String scope;
private final String thingUID;

protected JRuleAbstractAction(String scope, String thingUID) {
this.scope = scope;
this.thingUID = thingUID;
thingActions = Objects.requireNonNull(Things.getActions(scope, thingUID),
String.format("action for '%s' with uid '%s' could not be found", scope, thingUID));
}

protected Object invokeMethod(String methodName, Object... args) {
try {
Class<?>[] parameterTypes = Arrays.stream(args).map(Object::getClass)
.toArray((IntFunction<Class<?>[]>) value -> new Class[args.length]);
Method method = thingActions.getClass().getDeclaredMethod(methodName, parameterTypes);
return method.invoke(thingActions, args);
} catch (NoSuchMethodException e) {
throw new RuntimeException("method not found", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("error invoking method", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("cannot access method", e);
}
}

public String getThingUID() {
return thingUID;
}

public String getScope() {
return scope;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/**
* 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.automation.jrule.actions;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.openhab.automation.jrule.internal.JRuleConfig;
import org.openhab.automation.jrule.internal.JRuleConstants;
import org.openhab.automation.jrule.internal.JRuleLog;
import org.openhab.automation.jrule.internal.generator.JRuleAbstractClassGenerator;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
* The {@link JRuleActionClassGenerator} Class Generator for actions
*
* @author Robert Delbrück - Initial contribution
*/
public class JRuleActionClassGenerator extends JRuleAbstractClassGenerator {

private static final String TEMPLATE_SUFFIX = ".ftlh";

protected static final String LOG_NAME_CLASS_GENERATOR = "JRuleActionClassGen";

private final Logger logger = LoggerFactory.getLogger(JRuleActionClassGenerator.class);

public JRuleActionClassGenerator(JRuleConfig jRuleConfig) {
super(jRuleConfig);
}

public boolean generateActionSource(Thing thing) {
try {
Map<String, Object> processingModel = new HashMap<>();

Map<String, Object> actionModel = createActionModel(thing);
processingModel.put("action", actionModel);

File targetSourceFile = new File(new StringBuilder().append(jRuleConfig.getActionsDirectory())
.append(File.separator).append(jRuleConfig.getGeneratedItemPrefix())
.append(getActionFriendlyName(thing.getUID().toString())).append(JRuleConstants.JAVA_FILE_TYPE)
.toString());

try (FileWriter fileWriter = new FileWriter(targetSourceFile)) {
Template template = freemarkerConfiguration
.getTemplate("actions/" + actionModel.get("templateName") + TEMPLATE_SUFFIX);
template.process(processingModel, fileWriter);
}

JRuleLog.debug(logger, LOG_NAME_CLASS_GENERATOR, "Wrote Generated class: {}",
targetSourceFile.getAbsolutePath());
return true;

} catch (TemplateException | IOException e) {
JRuleLog.error(logger, LOG_NAME_CLASS_GENERATOR,
"Internal error when generating java source for action {}: {}", thing.getUID().toString(),
e.toString());

}

return false;
}

public boolean generateActionsSource(Collection<Thing> things) {
List<Map<String, Object>> model = things.stream()
.sorted(Comparator.comparing(e -> getActionFriendlyName(e.getUID().toString())))
.map(this::createActionsModel).collect(Collectors.toList());
Map<String, Object> processingModel = new HashMap<>();
processingModel.put("actions", model);
processingModel.put("packageName", jRuleConfig.getGeneratedActionPackage());

File targetSourceFile = new File(new StringBuilder().append(jRuleConfig.getActionsDirectory())
.append(File.separator).append("JRuleActions.java").toString());

try {
try (FileWriter fileWriter = new FileWriter(targetSourceFile)) {
Template template = freemarkerConfiguration.getTemplate("actions/Actions" + TEMPLATE_SUFFIX);
template.process(processingModel, fileWriter);
}

JRuleLog.debug(logger, LOG_NAME_CLASS_GENERATOR, "Wrote Generated class: {}",
targetSourceFile.getAbsolutePath());
return true;
} catch (TemplateException | IOException e) {
JRuleLog.error(logger, LOG_NAME_CLASS_GENERATOR,
"Internal error when generating java source for JRuleActions.java: {}", e.toString());

}
return false;
}

private Map<String, Object> createActionsModel(Thing thing) {
Map<String, Object> freemarkerModel = new HashMap<>();
freemarkerModel.put("id", thing.getUID().toString());
freemarkerModel.put("scope", thing.getUID().getBindingId());
freemarkerModel.put("name", StringUtils.uncapitalize(getActionFriendlyName(thing.getUID().toString())));
freemarkerModel.put("package", jRuleConfig.getGeneratedActionPackage());
freemarkerModel.put("class",
jRuleConfig.getGeneratedItemPrefix() + getActionFriendlyName(thing.getUID().toString()));
freemarkerModel.put("label", thing.getLabel());
freemarkerModel.put("templateName", "Actions");
freemarkerModel.put("parentClass", "JRuleAbstractAction");
return freemarkerModel;
}

private Map<String, Object> createActionModel(Thing thing) {
Map<String, Object> freemarkerModel = new HashMap<>();
freemarkerModel.put("id", thing.getUID().toString());
freemarkerModel.put("name", getActionFriendlyName(thing.getUID().toString()));
freemarkerModel.put("package", jRuleConfig.getGeneratedActionPackage());
freemarkerModel.put("class",
jRuleConfig.getGeneratedItemPrefix() + getActionFriendlyName(thing.getUID().toString()));
freemarkerModel.put("label", thing.getLabel());
freemarkerModel.put("templateName", "Action");
freemarkerModel.put("parentClass", "JRuleAbstractAction");

List<Object> methodList = new ArrayList<>();
freemarkerModel.put("methods", methodList);

if (thing.getHandler() != null) {
Class<? extends ThingHandlerService> thingActionsClass = thing.getHandler().getServices().stream()
.filter(ThingActions.class::isAssignableFrom).findFirst()
.orElseThrow(() -> new IllegalStateException("should not occur here"));

freemarkerModel.put("type", thingActionsClass.getTypeName());
Arrays.stream(thingActionsClass.getDeclaredMethods())
.filter(method -> method.getAnnotation(RuleAction.class) != null).collect(Collectors.toSet())
.forEach(method -> {
Map<Object, Object> methodMap = new HashMap<>();
methodMap.put("name", method.getName());
methodMap.put("returnType", method.getReturnType().getTypeName());
methodMap.put("import", !method.getReturnType().isPrimitive());
methodMap.put("hasReturnType", !method.getReturnType().getTypeName().equalsIgnoreCase("void"));
List<Object> args = new ArrayList<>();
methodMap.put("args", args);
Arrays.stream(method.getParameters()).forEach(parameter -> {
Map<Object, Object> arg = new HashMap<>();
arg.put("type", parameter.getType().getTypeName());
arg.put("name", Objects.requireNonNull(parameter.getAnnotation(ActionInput.class),
"ActionInput not set on action method parameter").name());
args.add(arg);
});
methodList.add(methodMap);
});
}
return freemarkerModel;
}

public static String getActionFriendlyName(String thingUid) {
return Arrays.stream(thingUid.split("[:\\-]")).map(StringUtils::capitalize).collect(Collectors.joining(""));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class JRuleConfig {

private static final String JAR_DIR = "jar";

public static final String ITEMS_PACKAGE = "org.openhab.automation.jrule.items.generated.";
public static final String GENERATED_PACKAGE = "org.openhab.automation.jrule.generated.";
public static final String RULES_PACKAGE = "org.openhab.automation.jrule.rules.user.";
private static final String WORKING_DIR_PROPERTY = "org.openhab.automation.jrule.directory";
private static final String GENERATED_ITEM_PREFIX_PROPERTY = "org.openhab.automation.jrule.itemprefix";
Expand All @@ -47,8 +47,6 @@ public class JRuleConfig {
private static final String EXECUTORS_MAX_PROPERTY = "org.openhab.automation.jrule.engine.executors.max";
private static final String EXECUTORS_ENABLE_PROPERTY = "org.openhab.automation.jrule.engine.executors.enable";
private static final String EXECUTORS_THREAD_KEEPALIVE_PROPERTY = "org.openhab.automation.jrule.engine.executors.keepalive";
public static final String ITEMS_DIR_START = "items";
public static final String THINGS_DIR_START = "things";

private static final int DEFAULT_MIN_EXECUTORS = 2;
private static final int DEFAULT_MAX_EXECUTORS = 10;
Expand All @@ -63,8 +61,9 @@ public class JRuleConfig {
private static final String DEFAULT_WORKING_DIR = File.separator + "etc" + File.separator + "openhab"
+ File.separator + "automation" + File.separator + "jrule";
private static final String DEFAULT_GENERATED_ITEM_PREFIX = "_";
private static final String DEFAULT_GENERATED_ITEM_PACKAGE = "org.openhab.automation.jrule.items.generated";
private static final String DEFAULT_GENERATED_THING_PACKAGE = "org.openhab.automation.jrule.things.generated";
private static final String DEFAULT_GENERATED_ITEM_PACKAGE = "org.openhab.automation.jrule.generated.items";
private static final String DEFAULT_GENERATED_THING_PACKAGE = "org.openhab.automation.jrule.generated.things";
private static final String DEFAULT_GENERATED_ACTION_PACKAGE = "org.openhab.automation.jrule.generated.actions";

private static final String CLASS_DIR = "class";

Expand All @@ -80,6 +79,7 @@ public class JRuleConfig {
private static final int DEFAULT_RULES_INIT_DELAY = 2;
private static final String DEFAULT_ITEMS_RECOMPILATION_DELAY_PROPERTY = "org.openhab.automation.jrule.engine.itemsrecompilationdelay";
private static final int DEFAULT_ITEMS_RECOMPILATION_DELAY = 5;
public static final String GEN = "gen";
private final Map<String, Object> properties;

private final Properties jRuleProperties;
Expand Down Expand Up @@ -131,20 +131,28 @@ public String getClassDirectory() {

public String getItemsDirectory() {
final StringBuilder sb = new StringBuilder(getWorkingDirectory());
sb.append(File.separator).append(ITEMS_DIR_START).append(File.separator);
sb.append(File.separator).append(GEN).append(File.separator);
final String p = JRuleUtil.packageNameToPath(getGeneratedItemPackage());
sb.append(p);
return sb.toString();
}

public String getThingsDirectory() {
final StringBuilder sb = new StringBuilder(getWorkingDirectory());
sb.append(File.separator).append(ITEMS_DIR_START).append(File.separator);
sb.append(File.separator).append(GEN).append(File.separator);
final String p = JRuleUtil.packageNameToPath(getGeneratedThingPackage());
sb.append(p);
return sb.toString();
}

public String getActionsDirectory() {
final StringBuilder sb = new StringBuilder(getWorkingDirectory());
sb.append(File.separator).append(GEN).append(File.separator);
final String p = JRuleUtil.packageNameToPath(getGeneratedActionPackage());
sb.append(p);
return sb.toString();
}

public String getJarDirectory() {
return new StringBuilder().append(getWorkingDirectory()).append(File.separator).append(JAR_DIR).toString();
}
Expand All @@ -158,9 +166,8 @@ public String getConfigFile() {
.toString();
}

public String getItemsRootDirectory() {
return new StringBuilder().append(getWorkingDirectory()).append(File.separator).append(ITEMS_DIR_START)
.toString();
public String getSourceDirectory() {
return new StringBuilder().append(getWorkingDirectory()).append(File.separator).append(GEN).toString();
}

public String getRulesRootDirectory() {
Expand Down Expand Up @@ -235,6 +242,10 @@ public String getGeneratedThingPackage() {
return getConfigPropertyOrDefaultValue(GENERATED_THING_PACKAGE_PROPERTY, DEFAULT_GENERATED_THING_PACKAGE);
}

public String getGeneratedActionPackage() {
return getConfigPropertyOrDefaultValue(GENERATED_THING_PACKAGE_PROPERTY, DEFAULT_GENERATED_ACTION_PACKAGE);
}

public int getRulesInitDelaySeconds() {
return getIntConfigProperty(INIT_RULES_DELAY_PROPERTY, DEFAULT_RULES_INIT_DELAY);
}
Expand Down
Loading

0 comments on commit 8a9be94

Please sign in to comment.