org.jboss.byteman.contrib.dtest.RuleConstructor Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source
* Copyright 2016, Red Hat, Inc. and/or its affiliates,
* and individual contributors as indicated by the @author tags.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2016,
* @author JBoss, by Red Hat.
*/
package org.jboss.byteman.contrib.dtest;
import java.util.concurrent.atomic.AtomicReference;
import org.jboss.byteman.rule.helper.Helper;
/**
*
* Provides a fluent API for creating Byteman rules without needing
* to mess around with String concatenation.
*
* Example:
*
*
* RuleConstructor rb = RuleConstructor.createRule("myRule")
* .onClass("org.jboss.byteman.ExampleClass")
* .inMethod("doInterestingStuff")
* .atEntry()
* .ifTrue()
* .doAction("myAction()");
* System.out.println(rb.build());
*
*
* will print:
*
*
* RULE myRule
* CLASS org.jboss.byteman.ExampleClass
* METHOD doInterestingStuff
* AT ENTRY
* IF true
* DO myAction()
* ENDRULE
*
*/
public final class RuleConstructor {
private static AtomicReference defaultInstrumentor = new AtomicReference();
private static final String LINEBREAK = String.format("%n");
private static final String CONSTRUCTOR_METHOD = "";
private static final String CLASS_CONSTRUCTOR = "";
private String ruleName;
private String className;
private boolean isInterface;
private boolean isIncludeSubclases;
private String methodName;
private String helperName = Helper.class.getName();
private String where = "AT ENTRY";
private String bind;
private String ifcondition = "true";
private String action;
private String imports;
private String compile;
/**
* No use of constructor, new rule creation with:
* RuleConstructor.createRule("myRule")
*/
private RuleConstructor(String ruleName) {
this.ruleName = ruleName;
}
/**
*
* Setting default initialize instance of {@link Instrumentor} class
* that will be used when {@link #install()}/{@link #submit()} method
* is used for the created rule.
* You can define this default Instrumentor which could be used whenever
* the new rule is submitted to the Byteman agent.
* null
is permitted then {@link #install()} method throws exception
*
*
* @param instrumentor initiated instrumentor instance or null
*/
public static final void setDefaultInstrumentor(Instrumentor instrumentor) {
defaultInstrumentor.set(instrumentor);
}
/**
* Undefinining value of default instrumentor.
* Methods {@link #install()}/{@link #submit()} are illegal to be used from now
* up to time it's set again.
*/
public static final void undefineDefaultInstrumentor() {
RuleConstructor.setDefaultInstrumentor(null);
}
/**
* Returning value of the previously set default {@link Instrumentor} instance.
*
* @return instrumentor instance or null, when was not set
*/
public static final Instrumentor getDefaultInstrumentor() {
return defaultInstrumentor.get();
}
/**
*
* This is where you start.
*
* Byteman
rule builder initialization method.
*
* @param ruleName name of rule is required to construct any rule
* @return a rule constructor ready to have its class or interface specified
*/
public static final RuleConstructor.ClassClause createRule(String ruleName) {
return new RuleConstructor(ruleName).new ClassClause();
}
/**
*
* Installing/submitting rule to the Byteman agent via instance of instrumentor
* defined as default to the {@link RuleConstructor} class.
*
*
* Internally this:
*
* - build the rule where {@link #build()} is called to generate rule as {@link String}
* - calling submit of the rule over instance of {@link Instrumentor}
*
* Prerequisite: you need
* set up the instrumentor by call {@link #setDefaultInstrumentor(Instrumentor)}
*
* @return rule constructor if expected to be used later again
* @throws IllegalStateException if default instrumentor is not set
* @throws RuntimeException if error happens during installation rule
* via default instrumentor instance
*/
public RuleConstructor install() {
if(defaultInstrumentor.get() == null)
throw new IllegalStateException("Can't install rule '" + this.getRuleName()
+ "' as default instrumentor was not set-up");
return install(defaultInstrumentor.get());
}
/**
*
* Installing/submitting rule to the Byteman agent via instance of instrumentor.
*
*
* Internally this:
*
* - build the rule where {@link #build()} is called to generate rule as {@link String}
* - calling submit of the rule over instance of {@link Instrumentor}
*
*
* @param instrumentor instance of instrumentor to be used to submit the rule to
* @return rule constructor if expected to be used later again
* @throws NullPointerException if instrumentor param is provided as null
* @throws RuntimeException if error happens during installation rule
* via default instrumentor instance
*/
public RuleConstructor install(Instrumentor instrumentor) {
if(instrumentor == null)
throw new NullPointerException("instrumentor");
try {
instrumentor.installRule(this);
} catch (Exception e) {
throw new RuntimeException("Can't install rule '" + this.getRuleName() + "'", e);
}
return this;
}
/**
* Facade to method {@link #install()}.
*
* @return rule constructor, if expected to be used later again
* @throws IllegalStateException if default instrumentor is not set
* @throws RuntimeException if error happens during installation rule
*/
public RuleConstructor submit() {
return install();
}
/**
* Facade to method {@link #install(Instrumentor)}.
*
* @param instrumentor instance of instrumentor to be used to submit the rule to
* @return rule constructor, if expected to be used later again
* @throws NullPointerException if instrumentor param is provided as null
* @throws RuntimeException if error happens during installation rule
*/
public RuleConstructor submit(Instrumentor instrumentor) {
return install(instrumentor);
}
/**
* Builds the rule defined by this instance of {@link RuleConstructor}
* and returns its representation as string.
*
* @return the rule as a string
*/
public String build() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("RULE ");
stringBuilder.append(ruleName);
stringBuilder.append(LINEBREAK);
if(isInterface) {
stringBuilder.append("INTERFACE ");
} else {
stringBuilder.append("CLASS ");
}
if(isIncludeSubclases) {
stringBuilder.append("^");
}
stringBuilder.append(className);
stringBuilder.append(LINEBREAK);
stringBuilder.append("METHOD ");
stringBuilder.append(methodName);
stringBuilder.append(LINEBREAK);
stringBuilder.append(where);
stringBuilder.append(LINEBREAK);
if(helperName != null && !helperName.isEmpty()) {
stringBuilder.append("HELPER ");
stringBuilder.append(helperName);
stringBuilder.append(LINEBREAK);
}
if(imports != null) {
stringBuilder.append(imports);
}
if(compile != null && !compile.isEmpty()) {
stringBuilder.append(compile);
stringBuilder.append(LINEBREAK);
}
if(bind != null && !bind.isEmpty()) {
stringBuilder.append("BIND ");
stringBuilder.append(bind);
stringBuilder.append(LINEBREAK);
}
stringBuilder.append("IF ");
stringBuilder.append(ifcondition);
stringBuilder.append(LINEBREAK);
stringBuilder.append("DO ");
stringBuilder.append(action);
stringBuilder.append(LINEBREAK);
stringBuilder.append("ENDRULE");
stringBuilder.append(LINEBREAK);
return stringBuilder.toString();
}
public final class ClassClause {
/**
* Class that rule event is associated to.
*
* Example:
*
*
* new RuleBuilder("rule name")
* .onClass("java.lang.String.class")
* ...
*
*
* @param clazz class as target of rule injection
* @return this, for having fluent api
*/
public RuleConstructor.MethodClause onClass(Class> clazz) {
return onSpecifier(clazz.getCanonicalName(), false);
}
/**
* Class name that rule event is associated to.
*
* Example:
*
*
* new RuleBuilder("rule name")
* .onClass("java.lang.String")
* ...
*
*
* @param className class name as target of rule injection
* @return this, for having fluent api
*/
public RuleConstructor.MethodClause onClass(String className) {
return onSpecifier(className, false);
}
/**
* Interface that rule event is associated to.
*
* Example:
*
*
* new RuleBuilder("rule name")
* .onInterface("javax.transaction.xa.XAResource.class")
* ...
*
*
* @param clazz interface class as target of rule injection
* @return this, for having fluent api
*/
public RuleConstructor.MethodClause onInterface(Class> clazz) {
return onSpecifier(clazz.getCanonicalName(), true);
}
/**
* Interface name that rule event is associated to.
*
* Example:
*
*
* new RuleBuilder("rule name")
* .onInterface("javax.transaction.xa.XAResource")
* ...
*
*
* @param className interface class name as target of rule injection
* @return this, for having fluent api
*/
public RuleConstructor.MethodClause onInterface(String className) {
return onSpecifier(className, true);
}
private RuleConstructor.MethodClause onSpecifier(String className, boolean isInterface) {
RuleConstructor.this.className = className;
RuleConstructor.this.isInterface = isInterface;
return RuleConstructor.this.new MethodClause();
}
}
public final class MethodClause {
/**
*
* Defining that the rule will be injected to all sub-classes
* or classes implementing the interface.
*
* By default Byteman
injects the rule only to the specified
* class and children classes are not instrumented.
*
* The rule class definition is enriched with ^
.
* As example: CLASS ^org.jboss.byteman.ExampleClass
.
*
* @return this, for having fluent api
*/
public RuleConstructor.MethodClause includeSubclases() {
RuleConstructor.this.isIncludeSubclases = true;
return this;
}
/**
*
* Defining method where the rule is injected to.
*
* Example:
*
*
* new RuleBuilder("rule name")
* .onInterface("javax.transaction.xa.XAResource")
* .inMethod("commit")
* ...
*
*
* @param methodName method name for rule injection
* @return this, for having fluent api
*/
public RuleConstructor.LocationClause inMethod(String methodName) {
RuleConstructor.this.methodName = methodName;
return RuleConstructor.this.new LocationClause();
}
/**
*
* Defining method specified by argument types where the rule is injected to.
* Arguments restrict which methods are instrumented based on parameters definition.
*
* Example:
*
*
* new RuleBuilder("rule name")
* .onInterface("javax.transaction.xa.XAResource")
* .inMethod("commit", "Xid" , "boolean")
* ...
*
*
* @param methodName method name for rule injection
* @param argTypes method argument types to closer specify what method is instrumented
* @return this, for having fluent api
*/
public RuleConstructor.LocationClause inMethod(String methodName, String... argTypes) {
RuleConstructor.this.methodName = methodName + "(" + stringJoin(",", argTypes) + ")";
return RuleConstructor.this.new LocationClause();
}
/**
* Defining constructor, special method type,
* as place for rule injection.
*
* @return this, for having fluent api
*/
public RuleConstructor.LocationClause inConstructor() {
return inMethod(CONSTRUCTOR_METHOD);
}
/**
*
* Defining constructor, special method type,
* as place for rule injection.
*
* The type of constructor method is specified by its arguments.
*
* @param argTypes method argument types to closer specify which method
* @return this, for having fluent api
*/
public RuleConstructor.LocationClause inConstructor(String... argTypes) {
return inMethod(CONSTRUCTOR_METHOD, argTypes);
}
/**
* Defining class initialization method as place for rule injection.
*
* @return this, for having fluent api
*/
public RuleConstructor.LocationClause inClassInitMethod() {
return inMethod(CLASS_CONSTRUCTOR);
}
/**
* Defining class initialization method as place for rule injection.
*
* @param argTypes method argument types to closer specify which method
* @return this, for having fluent api
*/
public RuleConstructor.LocationClause inClassInitMethod(String... argTypes) {
return inMethod(CLASS_CONSTRUCTOR, argTypes);
}
}
public final class LocationClause {
/**
* Byteman helper class to be used in rule definition.
*
* @param helperClass byteman helper class
* @return this, for having fluent api
*/
public RuleConstructor.LocationClause helper(Class> helperClass) {
return helper(helperClass.getCanonicalName());
}
/**
* Class name of Byteman helper class.
*
* @param helperClassName byteman helper class name
* @return this, for having fluent api
*/
public RuleConstructor.LocationClause helper(String helperClassName) {
RuleConstructor.this.helperName = helperClassName;
return this;
}
/**
*
* Rule is invoked at entry point of method.
*
* Location specifier is set as AT ENTRY
.
*
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause atEntry() {
return at("ENTRY");
}
/**
*
* Rule is invoked at exit point of method.
*
* Location specifier is set as AT EXIT
.
*
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause atExit() {
return at("EXIT");
}
/**
*
* Rule is invoked at specific line of code
* within the method.
*
* Location specifier is set as AT LINE <line>
.
*
* @param line line number to be rule injection point
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause atLine(int line) {
return at("LINE " + line);
}
/**
*
* Rule is invoked at point where method reads a variable.
*
* Location specifier is set as AT READ <variable>
.
*
* @param variable rule is triggered at write from this variable happen
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause atRead(String variable) {
return at("READ " + variable);
}
/**
*
* Rule is invoked at point where method reads a variable
* where occurencePosition
defines Nth
* textual occurrence of the field access.
*
* Location specifier is set as AT READ <variable> <occurencePosition>
.
*
* @param variable rule is triggered at write from this variable happen
* @param occurencePosition Nth textual occurrence of reading the field
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause atRead(String variable, int occurencePosition) {
return at("READ " + variable + " " + occurencePosition);
}
/**
*
* Rule is invoked after point where method reads a variable.
*
* Location specifier is set as AFTER READ <variable>
.
*
* @param variable rule is triggered after write from this variable happen
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause afterRead(String variable) {
return after("READ " + variable);
}
/**
*
* Rule is invoked after point where method reads a variable where occurencePosition
* defines Nth
textual occurrence of the field access.
*
* Location specifier is set as AFTER READ <variable> <occurencePosition>
.
*
* @param variable rule is triggered after write from this variable happen
* @param occurencePosition Nth textual occurrence of reading the field
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause afterRead(String variable, int occurencePosition) {
return after("READ " + variable + " " + occurencePosition);
}
/**
*
* Rule is invoked at point where method writes to a variable.
*
* Location specifier is set as AT WRITE <variable>
.
*
* @param variable rule is triggered at write to this variable happen
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause atWrite(String variable) {
return at("WRITE " + variable);
}
/**
*
* Rule is invoked at point where method writes to a variable where occurencePosition
* defines Nth
textual occurrence of the field write.
*
* Location specifier is set as AT WRITE <variable> <occurencePosition>
.
*
* @param variable rule is triggered at write to this variable happen
* @param occurencePosition Nth textual occurrence of the field write
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause atWrite(String variable, int occurencePosition) {
return at("WRITE " + variable + " " + occurencePosition);
}
/**
*
* Rule is invoked after point where method writes to a variable.
*
* Location specifier is set as AFTER WRITE <variable>
.
*
* @param variable rule is triggered after write to this variable happen
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause afterWrite(String variable) {
return after("WRITE " + variable);
}
/**
*
* Rule is invoked after point where method writes to a variable where occurencePosition
* defines Nth
textual occurrence of the field write.
*
* Location specifier is set as AFTER WRITE <variable> <occurencePosition>
.
*
* @param variable rule is triggered after write to this variable happen
* @param occurencePosition Nth textual occurrence of the field write
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause afterWrite(String variable, int occurencePosition) {
return after("WRITE " + variable + " " + occurencePosition);
}
/**
*
* Rule is invoked at point of invocation of method within the trigger method.
*
* Location specifier is set as AT INVOKE <variable>
.
*
* @param method method name after which invocation the rule is executed
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause atInvoke(String method) {
return at("INVOKE " + method);
}
/**
*
* Rule is invoked at point of invocation of method within the trigger method
* where occurencePosition
defines Nth
textual occurrence
* of the method invocation.
*
* Location specifier is set as AT INVOKE <variable> <occurencePosition>
.
*
* @param method method name after which invocation the rule is executed
* @param occurencePosition Nth textual occurrence of the method invocation
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause atInvoke(String method, int occurencePosition) {
return at("INVOKE " + method + " " + occurencePosition);
}
/**
*
* Rule is invoked after invocation of method within the trigger method.
*
* Location specifier is set as AFTER INVOKE <variable>
.
*
* @param method method name after which invocation the rule is executed
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause afterInvoke(String method) {
return after("INVOKE " + method);
}
/**
*
* Rule is invoked after invocation of method within the trigger method
* where occurencePosition
defines Nth
textual occurrence
* of the method invocation.
*
* Location specifier is set as AFTER INVOKE <variable> <occurencePosition>
.
*
* @param method method name after which invocation the rule is executed
* @param occurencePosition Nth textual occurrence of the method invocation
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause afterInvoke(String method, int occurencePosition) {
return after("INVOKE " + method + " " + occurencePosition);
}
/**
*
* Rule is invoked at entry of synchronization block in the target method.
*
* Location specifier is set as AT SYNCHRONIZE
.
*
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause atSynchronize() {
return at("SYNCHRONIZE");
}
/**
*
* Rule is invoked at point of invocation of method within the trigger method
* where occurencePosition
defines Nth
textual
* occurrence of the method invocation.
*
* Location specifier is set as AT SYNCHRONIZE <occurencePosition>
.
*
* @param occurencePosition Nth textual occurrence of the method invocation
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause atSynchronize(int occurencePosition) {
return at("SYNCHRONIZE " + occurencePosition);
}
/**
* Rule is invoked after invocation of method within the trigger method.
* Location specifier is set as AFTER SYNCHRONIZE
.
*
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause afterSynchronize() {
return after("SYNCHRONIZE");
}
/**
*
* Rule is invoked after invocation of method within the trigger method
* where occurencePosition
defines Nth
textual occurrence
* of the method invocation.
*
* Location specifier is set as AFTER SYNCHRONIZE <occurencePosition>
.
*
* @param occurencePosition Nth textual occurrence of the method invocation
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause afterSynchronize(int occurencePosition) {
return after("SYNCHRONIZE " + occurencePosition);
}
/**
*
* Identifies a throw operation within the trigger method.
*
* Location specifier is set as AT THROW
.
*
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause atThrow() {
return at("THROW");
}
/**
*
* Identifies a throw operation within the trigger method,
* specified with count as Nth textual occurrence of a throw
* inside of the method defining only that occurrence to trigger
* execution of the rule.
*
* Location specifier is set as AT THROW <occurencePosition>
.
*
* @param occurencePosition which Nth textual occurrence of a throw triggers the rule
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause atThrow(int occurencePosition) {
return at("THROW " + occurencePosition);
}
/**
*
* Identifies the point where a method returns control back to its caller via
* unhandled exceptional control flow.
*
* Location specifier is set as AT EXCEPTION EXIT
.
*
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause atExceptionExit() {
return at("EXCEPTION EXIT");
}
/**
*
* Rule is invoked AT
point which is specified with parameter.
*
* Location specifier is predefined with AT
keyword
* and the rest is up to parameter you provide.
*
* When you provide LINE 123
as parameter the location specifier
* is set as AT LINE 123
.
*
* @param at specifying rule injection point that is enriched
* with AT
keyword
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause at(String at) {
return where("AT " + at);
}
/**
*
* Rule is invoked AFTER
point which is specified with parameter.
*
* Location specifier is predefined with AFTER
keyword
* and the rest is up to parameter you provide.
*
* When you provide READ $0
as parameter the location specifier
* is set as AFTER READ $0
.
*
* @param after specifying rule injection point that is enriched
* with AFTER
keyword
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause after(String after) {
return where("AFTER " + after);
}
/**
*
* Location specifier definition.
*
* Defined string is directly used in rule definition.
*
* The parameter is expected to be defined as whole location specifier.
* For example when you provide AT LINE 123
as parameter
* the location specifier is set as AT LINE 123
.
*
* @param where location specifier
* @return this, for having fluent api
*/
RuleConstructor.ConditionClause where(String where) {
RuleConstructor.this.where = where;
return RuleConstructor.this.new ConditionClause();
}
}
public final class ConditionClause {
/**
* Byteman helper class to be used in rule definition.
*
* @param helperClass byteman helper class
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause helper(Class> helperClass) {
return helper(helperClass.getCanonicalName());
}
/**
* Class name of Byteman helper class.
*
* @param helperClassName byteman helper class name
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause helper(String helperClassName) {
RuleConstructor.this.helperName = helperClassName;
return this;
}
/**
*
* Definition of bind clause.
*
* When called as
* bind("engine:CoordinatorEngine = $0", "identifier:String = engine.getId()")
*
* rule looks
*
*
* BIND bind("engine:CoordinatorEngine = $0";
* "identifier:String = engine.getId()
*
*
* @param bindClauses bind clauses to be part of the rule
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause bind(String... bindClauses) {
RuleConstructor.this.bind = stringifyClauses(bindClauses);
return this;
}
/**
*
* Setting module import definition for the rule.
*
* For module import functionality works you need to use parameter
* -javaagent modules:
. The only provided implementation class
* which is to manage module imports is
* org.jboss.byteman.modules.jbossmodules.JbossModulesSystem
*
* @param imports specifying imports clauses which will be added
* to byteman rule configuration
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause imports(String... imports) {
StringBuffer importsBuf = new StringBuffer();
for(String importString: imports) {
importsBuf
.append("IMPORT ")
.append(importString)
.append(LINEBREAK);
}
RuleConstructor.this.imports = importsBuf.toString();
return this;
}
/**
*
* Defines rule for being compiled.
*
* Default behaviour is to use the interpreter.
*
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause compile() {
RuleConstructor.this.compile = "COMPILE";
return this;
}
/**
*
* Defines rule for not being compiled.
*
* Default behaviour is to use the interpreter
* but byteman system property could be used to change
* the default behaviour for rules being compiled every time
* then this settings could be useful.
*
* @return this, for having fluent api
*/
public RuleConstructor.ConditionClause nocompile() {
RuleConstructor.this.compile = "NOCOMPILE";
return this;
}
/**
*
* Rule condition when rule will be executed.
*
* Defined string is directly used in rule definition.
*
* Rule condition is set as IF <condition>
.
*
* @param condition rule condition string that is used for the rule
* @return this, for having fluent api
*/
public RuleConstructor.ActionClause ifCondition(String condition) {
RuleConstructor.this.ifcondition = condition;
return RuleConstructor.this.new ActionClause();
}
/**
*
* Condition ensuring that rule will be executed.
*
* Rule condition is set as IF true
.
*
* @return this, for having fluent api
*/
public RuleConstructor.ActionClause ifTrue() {
return ifCondition("true");
}
/**
*
* Condition ensuring that rule won't be executed.
*
* Rule condition is set as IF false
.
*
* @return this, for having fluent api
*/
public RuleConstructor.ActionClause ifFalse() {
return ifCondition("false");
}
}
public final class ActionClause {
/**
*
* Definition of actions for the rule.
*
* When called as
*
* doAction("debug(\"killing JVM\")", "killJVM()")
*
* rule looks
*
*
* DO debug("killing JVM");
* killJVM()
*
*
* @param actions actions definitions to be part of the rule
* @return this, for having fluent api
*/
public RuleConstructor doAction(String... actions) {
RuleConstructor.this.action = stringifyClauses(actions);
return RuleConstructor.this;
}
}
String getRuleName() {
return RuleConstructor.this.ruleName;
}
private String stringJoin(String join, String... strings) {
if (strings == null || strings.length == 0) {
return "";
} else if (strings.length == 1) {
return strings[0];
} else {
StringBuilder sb = new StringBuilder();
sb.append(strings[0]);
for (int i = 1; i < strings.length; i++) {
sb.append(join).append(strings[i]);
}
return sb.toString();
}
}
private String stringifyClauses(String... clauses) {
StringBuffer actionsBuffer = new StringBuffer();
boolean isFirstAddition = true;
boolean isSemicolon = true;
for(String clause: clauses) {
if(!isFirstAddition && !isSemicolon) actionsBuffer.append(";");
if(!isFirstAddition) actionsBuffer.append(LINEBREAK);
if(!clause.trim().endsWith(";")) isSemicolon = false;
if(isFirstAddition) isFirstAddition = false;
actionsBuffer.append(clause.trim());
}
return actionsBuffer.toString();
}
}