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

generate actions #63

Merged
merged 10 commits into from
Oct 31, 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
@@ -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