Skip to content

A light extensible library for creating composites of terms which are evaluatable using a highly customizable visitor pattern.

License

Notifications You must be signed in to change notification settings

Hakenadu/composite-terms

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

81 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

composite-terms

A light extensible java 8+ library for creating composites of terms which are evaluatable using a visitor pattern. Serialization to json and deserialization from json are supported by default using gson.

Next to gson no other libraries are needed.

Table of Contents

Maven Dependency

<dependency>
  <groupId>com.github.hakenadu</groupId>
  <artifactId>composite-terms</artifactId>
  <version>0.0.3</version>
</dependency>

JSON

This is an example term:

{
	"type": "operation",
	"operator": "subtract",
	"operands": [
		{
			"type": "constant",
			"datatype": "number",
			"value": 1337
		},
		{
			"type": "operation",
			"operator": "add",
			"operands": [
				{
					"type": "constant",
					"datatype": "number",
					"value": 500
				},
				{
					"type": "variable",
					"name": "x"
				}
			]
		}
	]
}

Deserialize a Term

The term mentioned above is deserialized as follows (let json be a string constant containing the mentioned structure):

Term deserializedTerm = TermsGson.createGson().fromJson(json, Term.class);

Create a Term

Terms may be created directly as json but also from java.

Creating a Term using a TermBuilder

// 1337 - (500 + x)
Term myTerm = PrefixTermBuilder.create()
	.beginOperation(Operators.SUBTRACT)
	.constant(1337)
	.beginOperation(Operators.ADD)
	.constant(500)
	.variable("x")
	.endOperation()
	.endOperation()
	.build();

Creating the same Term in a basic way

// 1337 - (500 + x)
Term myTerm = new Operation(Operators.SUBTRACT,
	new Constant(1337),
	new Operation(Operators.ADD,
		new Constant(500),
		new Variable("x")));

Evaluate a Term

A composite of Terms is evaluated using a visitor pattern. The concrete Visitor must be able to provide values for all variable names in a term.

/*
 * a SimpleEvaluationVisitor is an implementation of the EvaluationVisitor
 * which uses a java.util.Map to map concrete variable names to values.
 */
SimpleEvaluationVisitor evaluationVisitor = new SimpleEvaluationVisitor();

// the only variable in myTerm is "x" so lets apply the value -163 to it.
evaluationVisitor.getVariableValues().put("x", -163);

// an instance of the EvaluationContext is passed on visit to retrieve values
EvaluationContext evaluationContext = new EvaluationContext();

// lets evaluate our Term...
myTerm.accept(evaluationVisitor, evaluationContext);

// ...done: result == 1000
Number result = (Number) evaluationContext.getValue();

Serialize a Term

Serialization is also done via gson.

// again we'll use myTerm from above ;-)
String json = TermsGson.createGson().toJson(myTerm);

Add a custom Operator

Multiple basic operators are already implemented and accessible via the Operators class.

Own operators only have to registered if evaluations have to be performed. In this case an implementation of EvaluationVisitor is used and evaluation rules for custom operators are then added as follows:

Creating a custom OperationEvaluator:

/**
 * an OperationEvaluator for our custom operator "foo" which  operates on any type of operands and returns
 * "foo" if at least one {@link Number} operand is passed and "bar" otherwise.
 */
public class FooOperationEvaluator implements OperationEvaluator {

	@Override
	public String evaluate(final List<Object> operandValues) {
		final boolean foo = operandValues.stream().anyMatch(Number.class::isInstance);
		return foo ? "foo" : "bar";
	}
}

Registering the custom OperationEvaluator:

EvaluationVisitor visitor = new SimpleEvaluationVisitor();
// ...
visitor.getOperationEvaluators().put("foo", new FooOperationEvaluator());

This example is contained in the CustomOperatorTest.

Add a custom Constant Datatype

Instances of Constant are serialized to JSON with an additional "datatype" member. This is necessary to enable the correct deserialization using the gson ConstantTypeHierarchyAdapter.

The registration is done as follows:

/** create a custom datatype class */
public class Foo {
	private String bar;
	private String bas;
}

/** create a ConstantValueConverter for the custom datatype */
public class FooConstantValueConverter implements ConstantValueConverter<Foo> {

	@Override
	public String getTypeName() {
		return "foo"; // is contained in json as value of the "datatype" member
	}

	@Override
	public Class<Foo> getTypeClass() {
		return Foo.class; // for typesafe deserialization
	}

	@Override
	public JsonElement toJson(Foo foo) {
		/*
		 * It is also possible to create a JsonObject by hand. In this case we want to
		 * rely on Gson's default serialization.
		 */
		return new Gson().toJsonTree(foo, Foo.class);
	}

	@Override
	public Foo fromJson(JsonElement jsonElement) {
		// for our custom deserialization we also want to use Gson's default mechanism
		return new Gson().fromJson(jsonElement, Foo.class);
	}
}

Then register the custom ConstantValueConverter on Gson-Creation:

ConstantTypeHierarchyAdapter customConstantTypeHierarchyAdapter = new ConstantTypeHierarchyAdapter()
		.withValueConverter(new FooConstantValueConverter());

Gson mainGson = TermsGson.createGson(customConstantTypeHierarchyAdapter);

The CustomConstantValueConverterTest contains the above explained example with more details.

About

A light extensible library for creating composites of terms which are evaluatable using a highly customizable visitor pattern.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages