user2989124
user2989124

Reputation: 183

Design - Check a condition before executing every method

I have a POJO named Document.java with 100+ member variables. There is a transformation layer, where I get the required data, transform it and store it in the Document class.

In the tranformation layer, I would like to set a member variable only if satisfies a certain criteria (based on available context).

So it would look something like this:

if(shouldGetExecuted1(context.getXXX())){
  document.setField1(tranformDataForField1(availableData1));
}

if(shouldGetExecuted2(context.getXXX())){
  document.setField2(tranformDataForField2(availableData2));
}

I want to do this for all the 100+ fields. Is there a clean way to do this?

Additional information

I don't want to use Strategy here as it would create too many classes as the no of strategies grow.

Upvotes: 7

Views: 1929

Answers (3)

Max Fichtelmann
Max Fichtelmann

Reputation: 3504

If you are consistent in the naming of your methods, reflection could help a lot.

The following code assumes the following:

  • A Document class with fields like xxx or xxYy (getters/setters would be usually present but are not required for the code to work)
  • A Transformer class that has
    • the capability to determine based on context information, if a field should be processed. These methods are named shouldTransformXxx(context).
    • the capability to transform the content of the field (with input and output of the same type as the corresponding field in Document). These methods are named T transformXxx(T).
  • A DataProvider class that has methods to provide the untransformed data. these methods are named findXxx()

The code below is pretty optimistic - it will fail, if a shouldTransformXxx for any field misses, or if it returns true, the same applies for the findXxx and transformXxx methods. So you would have to create classes with 100 methods each, which seems non-ideal for me. But on the other hand, having a class with 100 members seems to lead to awkward situations anyway...

So here's the code:

public class Document {
    private String name;
    private int size;

    @Override
    public String toString() {
        return "Document [name=" + name + ", size=" + size + "]";
    }
}

public class Transformer {
    public enum ContextType {
        NAME, SIZE
    }

    public boolean shouldTransformName(Set<ContextType> context) {
        return context.contains(ContextType.NAME);
    }

    public boolean shouldTransformSize(Set<ContextType> context) {
        return context.contains(ContextType.SIZE);
    }

    public String transformName(String name) {
        return "::" + name;
    }

    public int transformSize(int size) {
        return size + 1;
    }
}

public class DataProvider {
    private final String name;
    private final int size;

    public DataProvider(String name, int size) {
        this.name = name;
        this.size = size;
    }

    public String findName() {
        return name;
    }

    public int findSize() {
        return size;
    }
}

public class Main {

    private static final String TRANSFORM_METHOD_PREFIX = "transform";
    private static final String CHECK_METHOD_PREFIX = "shouldTransform";
    private static final String DATAPROVIDER_METHOD_PREFIX = "find";

    private final DataProvider dataProvider;
    private final Transformer transformer;

    public Main(DataProvider dataProvider, Transformer transformer) {
        this.dataProvider = dataProvider;
        this.transformer = transformer;
    }

    public Document transformFields(Set<ContextType> context)
            throws ReflectiveOperationException {
        Document document = new Document();

        for (Field field : Document.class.getDeclaredFields()) {

            String capitalizedfieldName = capitalize(field.getName());
            Class<?> fieldType = field.getType();

            if (shouldTransform(context, capitalizedfieldName)) {

                Object data = findData(capitalizedfieldName);
                Object transformed = transformData(capitalizedfieldName,
                        fieldType, data);

                // in presence of a security manager, a reflective call of the
                // setter could be performed
                field.setAccessible(true);
                field.set(document, transformed);
            }
        }

        return document;
    }

    private Object transformData(String capitalizedfieldName,
            Class<?> fieldType, Object data)
            throws ReflectiveOperationException {

        String methodName = TRANSFORM_METHOD_PREFIX + capitalizedfieldName;
        Method method = Transformer.class.getMethod(methodName, fieldType);
        return method.invoke(transformer, data);
    }

    private Object findData(String capitalizedfieldName)
            throws ReflectiveOperationException {

        String methodName = DATAPROVIDER_METHOD_PREFIX + capitalizedfieldName;
        Method method = DataProvider.class.getMethod(methodName);
        return method.invoke(dataProvider);
    }

    private boolean shouldTransform(Set<ContextType> context,
            String capitalizedfieldName) throws ReflectiveOperationException {

        String methodName = CHECK_METHOD_PREFIX + capitalizedfieldName;
        Method method = Transformer.class.getMethod(methodName, Set.class);
        return (Boolean) method.invoke(transformer, context);
    }

    private String capitalize(String fieldName) {
        char upperCaseFirstChar = Character.toUpperCase(fieldName.charAt(0));

        if (fieldName.length() > 1) {
            return upperCaseFirstChar + fieldName.substring(1);
        } else {
            return Character.toString(upperCaseFirstChar);
        }
    }

    public static void main(String[] args) throws ReflectiveOperationException {
        DataProvider dataProvider = new DataProvider("sample", 1);
        Set<ContextType> context = EnumSet.of(ContextType.NAME,
                ContextType.SIZE);

        Main main = new Main(dataProvider, new Transformer());
        Document document = main.transformFields(context);

        System.out.println(document);
    }
}

Upvotes: 0

ka4eli
ka4eli

Reputation: 5424

Try to use AOP. AspectJ allows you to define pointcuts (for example, some filtered set of methods) and control their execution via advices (before method call, after, around):

@Aspect
class ClassName {
...

@PointCut("call(public void ClassName.*(..))") //includes all void methods of ClassName object 
public void myPointCut(){}

@Around("myPointCut()")
public void myLogicMethod(ProceedingJoinPoint thisJoinPoint) {

    if(shouldGetExecuted1(context.getXXX())){
        thisJoinPoint.proceed()
    }
}
}

Here thisJoinPoint.proceed() will execute the body of the intercepted method.

Read docs about how to define pointcuts. In this example the same logic will be applied to all void methods of this class. You can define more accurate pointcuts via special expressions to provide different logic for each.

Upvotes: 1

Akira
Akira

Reputation: 4071

No, there is no clean way to do it in Java. You can find methods using reflection but there is no way to find variables such as "availableDataN". So you necessarily need to make "availableDataN" a field in order to find it using reflection.

The final code would be something as ugly as the following:

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class X {

    public static void main(String[] args) {

        for (int i = 0; i < 100; i++) {

            Method shouldGetExecuted = X.class.getMethod("shouldGetExecuted" + i, String.class);
            boolean b = (boolean) shouldGetExecuted.invoke(null, context.getXXX());
            if (b) {
                Method tranformDataForField = X.class.getMethod("tranformDataForField");
                Field data = X.class.getField("availableData" + i); 
                Object result = tranformDataForField.invoke(null, data.get(null));
                Method set = X.class.getMethod("setField" + i, TransformDataType.class);
                set.invoke(null, result);
            }
        }
    }

}

You need to adapt to your specific case. For instance, here I am assuming all fields and methods are static. If they are not, then you need to replace null with an instance reference.

Upvotes: 0

Related Questions