I want to write a class with more than 1 fields of different types but at any time, there is one and only one field of an instance object having non null value.
What I did so far does not look really clean.
class ExclusiveField {
private BigInteger numericParam;
private String stringParam;
private LocalDateTime dateParam;
public void setNumericParam(BigInteger numericParam) {
unsetAll();
this.numericParam = Objects.requireNonNull(numericParam);
}
public void setStringParam(String stringParam) {
unsetAll();
this.stringParam = Objects.requireNonNull(stringParam);
}
public void setDateParam(LocalDateTime dateParam) {
unsetAll();
this.dateParam = Objects.requireNonNull(dateParam);
}
private void unsetAll() {
this.numericParam = null;
this.stringParam = null;
this.dateParam = null;
}
}
Does Java support this pattern somehow or is there a more decent way to do it?
The simplest approach for an object to have only one non-null field, is to actually have only one field and assume all others to be null implicitly. You only need another tag field, to determine which field is non-null.
Since in your example, all alternatives seem to be about the type of the value, the type itself could be the tag value, e.g.
class ExclusiveField {
private Class<?> type;
private Object value;
private <T> void set(Class<T> t, T v) {
value = Objects.requireNonNull(v);
type = t;
}
private <T> T get(Class<T> t) {
return type == t? t.cast(value): null;
}
public void setNumericParam(BigInteger numericParam) {
set(BigInteger.class, numericParam);
}
public BigInteger getNumericParam() {
return get(BigInteger.class);
}
public void setStringParam(String stringParam) {
set(String.class, stringParam);
}
public String getStringParam() {
return get(String.class);
}
public void setDateParam(LocalDateTime dateParam) {
set(LocalDateTime.class, dateParam);
}
public LocalDateTime getDateParam() {
return get(LocalDateTime.class);
}
}
If the type is not the only differentiator, you need to define distinct key values. An enum would be a natural choice, but unfortunately, enum constants can not provide the type safety. So, the alternative would look like:
class ExclusiveField {
private static final class Key<T> {
static final Key<String> STRING_PROPERTY_1 = new Key<>();
static final Key<String> STRING_PROPERTY_2 = new Key<>();
static final Key<BigInteger> BIGINT_PROPERTY = new Key<>();
static final Key<LocalDateTime> DATE_PROPERTY = new Key<>();
}
private Key<?> type;
private Object value;
private <T> void set(Key<T> t, T v) {
value = Objects.requireNonNull(v);
type = t;
}
#SuppressWarnings("unchecked") // works if only set() and get() are used
private <T> T get(Key<T> t) {
return type == t? (T)value: null;
}
public void setNumericParam(BigInteger numericParam) {
set(Key.BIGINT_PROPERTY, numericParam);
}
public BigInteger getNumericParam() {
return get(Key.BIGINT_PROPERTY);
}
public void setString1Param(String stringParam) {
set(Key.STRING_PROPERTY_1, stringParam);
}
public String getString1Param() {
return get(Key.STRING_PROPERTY_1);
}
public void setString2Param(String stringParam) {
set(Key.STRING_PROPERTY_2, stringParam);
}
public String getString2Param() {
return get(Key.STRING_PROPERTY_2);
}
public void setDateParam(LocalDateTime dateParam) {
set(Key.DATE_PROPERTY, dateParam);
}
public LocalDateTime getDateParam() {
return get(Key.DATE_PROPERTY);
}
}
Change your unsetAll method to setAll:
private void setAll(BigInteger numericParam, String stringParam, LocalDateTime dateParam) {
this.numericParam = numericParam;
this.stringParam = stringParam;
this.dateParam = dateParam;
}
Then invoke from your public setters like:
public void setNumericParam(BigInteger numericParam) {
setAll(Objects.requireNonNull(numericParam), null, null);
}
Note that Objects.requireNonNull is evaluated before setAll, so if you were to pass in a null numericParam, this would fail without changing any internal state.
preface: My answer is more theoretical, and the practices it describes aren't really practical in Java. They're simply not as well supported, and you would be "going against the grain", conventionally speaking. Regardless, I think it's a neat pattern to know about, and I thought I would share.
Java's classes are product types. When a class C contains members of types T1, T2, ..., Tn, then the valid values for objects of class C are the Cartesian product of the values of T1, T2, ..., Tn. For example, if class C contains a bool (which has 2 values) and byte (which has 256 values), then there are 512 possible values of C objects:
(false, -128)
(false, -127)
...
(false, 0)
...
(false, 127)
(true, -128)
(true, -127)
...
(true, 0)
...
(true, 127)
In your example, the theoretical possible values of ExclusiveField is equal to numberOfValuesOf(BigInteger.class) * numberOfValuesOf(String) * numberOfValuesOf(LocalDateTime) (notice the multiplication, that's why it's called a product type), but that's not really what you want. You're looking for ways to eliminate a huge set of these combinations so that the only values are when one field is non-null, and the others are null. There are numberOfValuesOf(BigInteger.class) + numberOfValuesOf(String) + numberOfValuesOf(LocalDateTime). Notice the addition, this indicates that what you're looking for is a "sum type".
Formally speaking, what you're looking for here is a tagged union (also called a variant, variant record, choice type, discriminated union, disjoint union, or sum type). A tagged union is a type whose values are a choice between one value of the members. In the previous example, if C was a sum type, there would be only 258 possible values: -128, -127, ..., 0, 127, true, false.
I recommend you check out unions in C, to build an understanding of how this works. The issue with C is that its unions had no way of "remembering" which "case" was active at any given point, which mostly defeats the whole purpose of a "sum type". To remedy this, you would add a "tag", which was an enum, whose value tells you what the state of the union is. "Union" stores the payload, and the "tag" tells you to the type of the payload, hence "tagged union".
The problem is, Java doesn't really have such a feature built in. Luckily, we can harness class hierarchies (or interfaces) to implement this. You essentially have to roll your own every time you need it, which is a pain because it takes a lot of boilerplate, but it's conceptually simple:
For n different cases, you make n different private classes, each storing the members pertinent to that case
You unify these private classes under a common base class (typically abstract) or interface
You wrap these classes in a forwarding class that exposes a public API all while hiding the private internals (to ensure that no one else can implement your interface).
Your interface could have n methods, each something like getXYZValue(). These methods could be made as default methods, where the default implementation returns null (for Object values, but doesn't work for primitives, Optional.empty() (for Optional<T> values), or throw an exception (gross, but there's no better way for primitive values like int). I don't like this approach, because the interface is rather disingenuous. Conforming types don't really conform to the interface, only ¹/n th of it.
Instead, you can use a pattern matching uhhh, pattern. You make a method (e.g. match) that takes n different Function parameters, whose types correspond to the types of cases of the discriminated union. To use a value of the discriminated union, you match it and provide n lambda expressions, each of which acts like the cases in a switch statement. When invoked, the dynamic dispatch system calls the match implementation associated with the particular storage object, which calls the correct one of the n functions and passes its value.
Here's an example:
import java.util.Optional;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Consumer;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import java.math.BigInteger;
class Untitled {
public static void main(String[] args) {
List<ExclusiveField> exclusiveFields = Arrays.asList(
ExclusiveField.withBigIntegerValue(BigInteger.ONE),
ExclusiveField.withDateValue(LocalDateTime.now()),
ExclusiveField.withStringValue("ABC")
);
for (ExclusiveField field : exclusiveFields) {
field.consume(
i -> System.out.println("Value was a BigInteger: " + i),
d -> System.out.println("Value was a LocalDateTime: " + d),
s -> System.out.println("Value was a String: " + s)
);
}
}
}
class ExclusiveField {
private ExclusiveFieldStorage storage;
private ExclusiveField(ExclusiveFieldStorage storage) { this.storage = storage; }
public static ExclusiveField withBigIntegerValue(BigInteger i) { return new ExclusiveField(new BigIntegerStorage(i)); }
public static ExclusiveField withDateValue(LocalDateTime d) { return new ExclusiveField(new DateStorage(d)); }
public static ExclusiveField withStringValue(String s) { return new ExclusiveField(new StringStorage(s)); }
private <T> Function<T, Void> consumerToVoidReturningFunction(Consumer<T> consumer) {
return arg -> {
consumer.accept(arg);
return null;
};
}
// This just consumes the value, without returning any results (such as for printing)
public void consume(
Consumer<BigInteger> bigIntegerMatcher,
Consumer<LocalDateTime> dateMatcher,
Consumer<String> stringMatcher
) {
this.storage.match(
consumerToVoidReturningFunction(bigIntegerMatcher),
consumerToVoidReturningFunction(dateMatcher),
consumerToVoidReturningFunction(stringMatcher)
);
}
// Transform 'this' according to one of the lambdas, resuling in an 'R'.
public <R> R map(
Function<BigInteger, R> bigIntegerMatcher,
Function<LocalDateTime, R> dateMatcher,
Function<String, R> stringMatcher
) {
return this.storage.match(bigIntegerMatcher, dateMatcher, stringMatcher);
}
private interface ExclusiveFieldStorage {
public <R> R match(
Function<BigInteger, R> bigIntegerMatcher,
Function<LocalDateTime, R> dateMatcher,
Function<String, R> stringMatcher
);
}
private static class BigIntegerStorage implements ExclusiveFieldStorage {
private BigInteger bigIntegerValue;
BigIntegerStorage(BigInteger bigIntegerValue) { this.bigIntegerValue = bigIntegerValue; }
public <R> R match(
Function<BigInteger, R> bigIntegerMatcher,
Function<LocalDateTime, R> dateMatcher,
Function<String, R> stringMatcher
) {
return bigIntegerMatcher.apply(this.bigIntegerValue);
}
}
private static class DateStorage implements ExclusiveFieldStorage {
private LocalDateTime dateValue;
DateStorage(LocalDateTime dateValue) { this.dateValue = dateValue; }
public <R> R match(
Function<BigInteger, R> bigIntegerMatcher,
Function<LocalDateTime, R> dateMatcher,
Function<String, R> stringMatcher
) {
return dateMatcher.apply(this.dateValue);
}
}
private static class StringStorage implements ExclusiveFieldStorage {
private String stringValue;
StringStorage(String stringValue) { this.stringValue = stringValue; }
public <R> R match(
Function<BigInteger, R> bigIntegerMatcher,
Function<LocalDateTime, R> dateMatcher,
Function<String, R> stringMatcher
) {
return stringMatcher.apply(this.stringValue);
}
}
}
Why not simply?
public void setNumericParam(BigInteger numericParam) {
this.numericParam = Objects.requireNonNull(numericParam);
this.stringParam = null;
this.dateParam = null;
}
Your goal
You mention in the comments that your goal is to write SQL requests for a legacy DB:
type:VARCHAR, numeric: INT, string: VARCHAR, date: DATETIME and
ExclusiveField will be used as getQueryRunner().query("CALL
sp_insert_parametter(?, ?, ?, ?, ?)", param.getNumericParam(), id,
type, param.getStringParam(), param.getDateParam())
So your goal really isn't to create a class with only one non-null field.
Alternative
You could define an abstract class Field with id, type, value attributes:
public abstract class Field
{
private int id;
private Class<?> type;
private Object value;
public Field(int id, Object value) {
this.id = id;
this.type = value.getClass();
this.value = value;
}
public abstract int getPosition();
}
For each column in your database, you create a small corresponding class, extending Field. Each class defines its desired type and its position in the SQL command:
import java.math.BigInteger;
public class BigIntegerField extends Field
{
public BigIntegerField(int id, BigInteger numericParam) {
super(id, numericParam);
}
#Override
public int getPosition() {
return 0;
}
}
You can define Field#toSQL:
public String toSQL(int columnsCount) {
List<String> rows = new ArrayList<>(Collections.nCopies(columnsCount, "NULL"));
rows.set(getPosition(), String.valueOf(value));
return String.format("SOME SQL COMMAND (%d, %s, %s)", id, type.getName(), String.join(", ", rows));
}
Which will output NULLS everywhere except at the desired position.
That's it.
Complete code
Field.java
package com.stackoverflow.legacy_field;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public abstract class Field
{
private int id;
private Class<?> type;
private Object value;
public Field(int id, Object value) {
this.id = id;
this.type = value.getClass();
this.value = value;
}
public abstract int getPosition();
public static void main(String[] args) {
List<Field> fields = Arrays.asList(new BigIntegerField(3, BigInteger.TEN),
new StringField(17, "FooBar"),
new DateTimeField(21, LocalDateTime.now()));
for (Field field : fields) {
System.out.println(field.toSQL(3));
}
}
public String toSQL(int columnsCount) {
List<String> rows = new ArrayList<>(Collections.nCopies(columnsCount, "NULL"));
rows.set(getPosition(), String.valueOf(value));
return String.format("SOME SQL COMMAND (%d, %s, %s)", id, type.getName(), String.join(", ", rows));
}
}
BigIntegerField.java
package com.stackoverflow.legacy_field;
import java.math.BigInteger;
public class BigIntegerField extends Field
{
public BigIntegerField(int id, BigInteger numericParam) {
super(id, numericParam);
}
#Override
public int getPosition() {
return 0;
}
}
StringField.java
package com.stackoverflow.legacy_field;
public class StringField extends Field
{
public StringField(int id, String stringParam) {
super(id, stringParam);
}
#Override
public int getPosition() {
return 1;
}
}
DateTimeField.java
package com.stackoverflow.legacy_field;
import java.time.LocalDateTime;
public class DateTimeField extends Field
{
public DateTimeField(int id, LocalDateTime value) {
super(id, value);
}
#Override
public int getPosition() {
return 2;
}
}
Result
Launching Field#main outputs:
SOME SQL COMMAND (3, java.math.BigInteger, 10, NULL, NULL)
SOME SQL COMMAND (17, java.lang.String, NULL, FooBar, NULL)
SOME SQL COMMAND (21, java.time.LocalDateTime, NULL, NULL, 2019-05-09T09:39:56.062)
Which should be really close to your desired output. You could probably find better names and define specific toString() methods if needed.
You could use reflection. Two functions and you're done. Add a new field? No problem. You don't even have to change anything.
public void SetExclusiveValue(String param, Object val){
this.UnsetAll();
Class cls = this.getClass();
Field fld = cls.getDeclaredField(param); //Maybe need to set accessibility temporarily? Or some other kind of check.
//Also need to add check for fld existence!
fld.set(this, Objects.requireNonNull(val));
}
private void UnsetAll(){
Class cls = this.getClass();
Field[] flds = cls.getDeclaredFields();
for (Field fld : flds){
fld.set(this,null);
}
}
If accessibiility is an issue, you could simply add a list of accessible fields and check param against that
class Value<T> {
T value;
}
This question is related to Issue in abstracting common code using function interface and Exception handling in Function interface. Getting idea from those I have written like below:
public void act(Input1 input) throws NonRetriableException, InvalidInputException {
Function<UpdateTaskInput, Boolean> func = item -> {
try {
activityManager.update(item);
return true;
} catch (InterruptedException | JSONException e) {
throw new NonRetriableException(e);
} catch (LockUnavailableException e) {
throw new NonRetriableException(e);
}
};
try {
lockManager.executeWithLock(input.getTaskID(), input, func);
} catch (LockUnavailableException e) {
log.error("{}",e);
throw new NonRetriableException(e);
}
}
and:
public void perform()
throws AutoAllocationException {
Function<UpdateTaskInput, Boolean> func = item -> {
try {
activityManager.allocateTask(item);
return true;
} catch (AutoAllocationException ex) {
log.error("{}",ex);
}
return false;
};
try {
lockManager.executeWithLock(input.getTaskID(), input, func);
} catch (LockUnavailableException e) {
log.error("{}",e);
}
}
executeWithLock() in LockManager is as follows:
#Override
public <T,R> R executeWithLock(String lockName, T input, Function<T,R> func) throws LockUnavailableException {
LockItem lockItem = acquireLock(lockName);
try {
R output = func.apply(input);
return output;
} finally {
releaseLock(lockItem);
}
}
Now the issue with the executeWithLock() function is, it always expects input as an argument, I cannot invoke this for any other function which does not have any input like doStuff().
But I would like to do it using Function interface only with something like following ( so you can see, T is omitted).
#Override
public <R> R executeWithLock(String lockName, Function<R> func) throws LockUnavailableException {
LockItem lockItem = acquireLock(lockName);
try {
R output = func.apply(input);
return output;
} finally {
releaseLock(lockItem);
}
}
Is there any way to do that with Function interface?
You could use a intermediate helper method, which accepts a Supplier<R> but no input. Which then creates a Function<?, R> and delegates to the other method:
public <R> R executeWithLock(String lockName, Supplier<R> supplier) throws LockUnavailableException{
return executeWithLock(
lockName,
null, // input is ignored by our function
ignored -> supplier.get() // this lambda will create Function<?, R> from the Supplier<R>
);
}
This could then be used like this:
executeWithLock("mylock", () -> "Some Value");
Whereas () -> "Some Value" is a Supplier<String>.
If you can't change that code, and thus not be able to add a intermediate helper method. You might just want to pass null as an argument, and then ignore it in the lambda:
executeWithLock("myLock", null, ignored -> doStuff());
You need two methods. The first should take a Supplier rather than a Function and the input. Using Supplier is preferable because it gives you the flexibility to use zero or more input arguments. Using Function means you are limited to one.
You also need a second method which uses Runnable which supports zero or more input arguments and a void return:
public static <R> R executeWithLock(String lockName, Supplier<R> func) {
Lock lockItem = acquireLock(lockName);
try {
return func.get();
} finally {
releaseLock(lockItem);
}
}
public static void executeWithLock(String lockName, Runnable func) {
Lock lockItem = acquireLock(lockName);
try {
func.run();
} finally {
releaseLock(lockItem);
}
}
Sample usage for these example methods:
private static String foo(String input) { return input; }
private static void bar(String input) { }
would look like:
String ret = executeWithLock("lockName", () -> foo("someInput"));
executeWithLock("lockName", () -> bar("someInput")); // void return
Java is able to infer which version of executeWithLock is required based on whether or not there's a return type. If you wanted to be explicit, you could give the methods different names.
Our code has several processors, each one having several api methods, where each method is overloaded also with same method that can accept collection.
For example:
public class Foo {
public X foo(Y y){...}
public Collection<X> foo(Collection<Y> y){... // iterate and execute foo(y) ... }
public Z bar(W w){...}
public Collection<Z> bar(Collection<W> w){... // iterate and execute bar(w) ... }
}
public class Other{
// also method and method on collection
}
Naturally, those methods on collections are actually duplication code of iteration.
What we are looking for, is kind of way to make some pattern or use generics, so the iteration over collection will be implemented once, also for that need a way to somehow pass the method name.
I'd suggest Startegy pattern. And do something like:
public interface Transformer<X, Y> {
Y transform( X input );
}
class Processor {
public <X,Y> Collection<Y> process( Collection<X> input, Transformer<X, Y> transformer) {
Collection<Y> ret = new LinkedList<Y>();
// generic loop, delegating transformation to specific transformer
for( X x : input) {
ret.add( transformer.transform( x ) );
}
return ret;
}
}
Example:
public static void main( String[] args ) {
List<String> strings = new LinkedList<String>();
strings.add( "1" );
strings.add( "2" );
strings.add( "3" );
Processor p = new Processor();
Collection<Integer> numbers = p.process( strings, new Transformer<String, Integer>() {
#Override
public Integer transform( String input ) {
return Integer.parseInt( input );
}
} );
}
I can't see how reflection could help here. You're trying to replace something as trivial as
public Collection<X> foo(Collection<Y> y) {
List<X> result = Lists.newArrayList();
for (Y e : y) result.add(foo(e));
return result;
}
by something probably much slower. I don't think that saving those 3 lines (several times) is worth it, but you might want to try either annotation processing (possibly without using annotations) or dynamic code generation. In both cases you'd write the original class as is without the collection methods and use a different one containing both the scalar and the collection methods.
Or you might want to make it more functionally styled:
public class Foo {
public final RichFunction<Y, X> foo = new RichFunction<Y, X>() {
X apply(Y y) {
return foo(y);
}
}
// after some refactoring the original method can be made private
// or inlined into the RichFunction
public X foo(Y y){...}
// instead of calling the original method like
// foo.foo(y)
// you'd use
// foo.foo.apply(y)
// which would work for both the scalar and collection methods
}
public abstract class RichFunction<K, V> extends com.google.common.base.Function<K, V> {
Collection<V> apply(Collection<K> keys) {
List<V> result = Lists.newArrayList();
for (K k : keys) result.add(apply(k));
return result;
}
}
RUAKH - I chosed to implement your suggestion for reflection (although, admit, I don't like reflection). So, I did something like the code below THANKS :)
public class Resource {
private static final int CLIENT_CODE_STACK_INDEX;
static {
// Finds out the index of "this code" in the returned stack trace - funny but it differs in JDK 1.5 and 1.6
int i = 0;
for (StackTraceElement ste : Thread.currentThread().getStackTrace()) {
i++;
if (ste.getClassName().equals(Resource.class.getName())) {
break;
}
}
CLIENT_CODE_STACK_INDEX = i;
}
public static String getCurrentMethodName() {
return Thread.currentThread().getStackTrace()[CLIENT_CODE_STACK_INDEX].getMethodName();
}
protected <IN,OUT> Collection<OUT> doMultiple(String methodName, Collection<IN> inCol, Class<?>... parameterTypes){
Collection<OUT> result = new ArrayList<OUT>();
try {
Method m = this.getClass().getDeclaredMethod(methodName, parameterTypes);
if (inCol==null || inCol.size()==0){
return result;
}
for (IN in : inCol){
Object o = m.invoke(this, in);
result.add((OUT) o);
}
}catch (Exception e){
e.printStackTrace();
}
return result;
}
}
public class FirstResource extends Resource{
public String doSomeThing(Integer i){
// LOTS OF LOGIC
return i.toString();
}
public Collection<String> doSomeThing(Collection<Integer> ints){
return doMultiple(getCurrentMethodName(), ints, Integer.class);
}
}
You should use Strategy pattern. By using Strategy pattern you can omit the usage if/else which makes the code more complex. Where strategy pattern creates less coupled code which is much simpler. By using Strategy pattern you can achieve more ways to configure code dynamically. So I would like to suggest you to use Strategy pattern.
I am trying to write some general code to do the following. Given two kinds of "operations", (a) validation (eg. input: object & context -> output: boolean), and (b) transformation (eg. input: object_A, context -> output: object_B) -objects of any type-.
I want to be able to build chains of "operations", in which an input object and its context can be submitted through (eg. to validate and transform the object). Returning immediately if the object is "invalid" and being able to get the transformed object if it finished "valid".
Idea is that "validations" and "transformations" can be "plugable" functions that other people write and assemble in a chain (eg. they build chains and submit objects through them).
I managed to do the following code, which compiles and seems to work. However, I'm not an expert on generics and would like to hear feedback about possible pitfalls, enhancements, or even maybe some other better/easier approach to the problem. Thanks in advance.
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
interface Operation<T, U, V> {
U execute(T a, V context);
}
abstract class Validation<T, V> implements Operation<T, Boolean, V> {
#Override
public Boolean execute(T a, V context) {
return executeValidation(a, context);
}
public abstract Boolean executeValidation(T a, V context);
}
abstract class Transformation<T, U, V> implements Operation<T, U, V> {
#Override
public U execute(T a, V context) {
return executeTransformation(a, context);
}
public abstract U executeTransformation(T a, V context);
}
class OperationsChain {
List<Operation<Object, Object, Object>> operations = new ArrayList<Operation<Object, Object, Object>>();
Object currentObj;
public <T, V> Boolean run(T a, V context) {
Boolean valid = false;
currentObj = a;
for (Operation<Object, Object, Object> operation : operations) {
if (operation instanceof Validation) {
valid = (Boolean) operation.execute(currentObj, context);
} else if (operation instanceof Transformation) {
currentObj = operation.execute(currentObj, context);
}
if (!valid) {
break;
}
}
return valid;
}
#SuppressWarnings("unchecked")
public <T, U, V> void addOperation(Operation<T, U, V> operation) {
operations.add((Operation<Object, Object, Object>) operation);
}
public Object getCurrentObject() {
return currentObj;
}
}
class ValidationOne extends Validation<MapObject, Map<String, Object>> {
public Boolean executeValidation(MapObject a, Map<String, Object> context) {
if (context.containsKey("validation 1")) {
return (Boolean) context.get("validation 1");
} else {
return false;
}
}
}
class ValidationTwo extends Validation<MapObject, Map<String, Object>> {
public Boolean executeValidation(MapObject a, Map<String, Object> context) {
if (context.containsKey("validation 2")) {
return (Boolean) context.get("validation 2");
} else {
return false;
}
}
}
class TransformationOne extends Transformation<MapObject, MapObject, Map<String, Object>> {
public MapObject executeTransformation(MapObject a, Map<String, Object> context) {
if (context.containsKey("transformation 1")) {
a.addField("data", (String) context.get("transformation 1"));
}
return a;
}
}
class MapObject {
Map<String, String> fields = new HashMap<String, String>();
public void addField(String key, String value) {
fields.put(key, value);
}
public String getField(String key, String value) {
if (fields.containsKey(key)) {
return fields.get(key);
} else {
return null;
}
}
public String toString() {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : fields.entrySet()) {
sb.append(entry.getKey());
sb.append(": ");
sb.append(entry.getValue());
sb.append("\n");
}
return sb.toString();
}
}
class OperationsChainDriver {
public static void main(String[] args) {
OperationsChain oc = new OperationsChain();
oc.addOperation(new ValidationOne());
oc.addOperation(new TransformationOne());
oc.addOperation(new ValidationTwo());
oc.addOperation(new TransformationOne());
Map<String, Object> context = new HashMap<String, Object>();
context.put("validation 1", true);
context.put("validation 2", false);
context.put("transformation 1", "aloha");
MapObject mapObject = new MapObject();
mapObject.addField("field 1", "hello");
Boolean result = oc.run(mapObject, context);
if (result == true) {
System.out.println("valid\n"+oc.getCurrentObject().toString());
} else {
System.out.println("invalid\n"+oc.getCurrentObject().toString());
}
}
}
Generics are about type safety - not having to cast, because as you surely know casts are risks proved runtime. You have a very generic design yet get very concrete to and the like and have to cast a lot - this shouldn't happen since it defeats the reason to use generics at all.
As as side note: why not give an operation a method isValid that has always a return type of Boolean, a transformation can fail, too, so yo don't have to make a difference between validation and transformation. Or let it put a value in a context - the operation could know its context and could use it without casts. An operation chain could know its context and could get the results without casts.
Anyway - as long as you code has casts you are still not finished with it.
This kind of task is one that I think a functional language would be ideal for, e.g. Scala (which runs on the JVM and is perfect for interoperating with Java code), or Haskell (which doesn't run on the JVM, but has some other advantages).
OK, I understand if you don't want to learn a new programming language. But one of the key advantages would be that your code should be shorter and easier to read and reason about.