mlowry
mlowry

Reputation: 171

Method to return object of subclass that is dynamically determined

In the CA Automic Automation Engine Java APIs, each AE object* type has its own subclass of UC4Object. Many of the operations for working with AE objects are specific to the AE object type. I want to write a method that takes a UC4Object as input, and returns an object of the class appropriate to the AE object type — e.g., the method returns an object of type JobPlan for a workflow.

The AE object type can be determined using UC4Object.getType(). Once one knows the AE object type, one can cast its UC4Object object to the class specific to the AE object type. E.g, if one is working with a UC4Object object called uc4Object, one might do something like this:

if ("JOBP".equals(uc4Object.getType())){
  JobPlan workflow = (JobPlan) uc4Object;
}

JOBP is the AE object type of workflows. I want to generalize this so that it works for all AE object types.

UC4Object.getType()    UC4Object Subclass
JSCH                   Schedule
JOBP                   JobPlan
EVNT_TIME              TimeEvent
EVNT_FILE              FileEvent
EVNT_DB                DatabaseEvent
EVNT_CONS              ConsoleEvent
SCRI                   Script
JOBS                   Job
JOBF                   FileTransfer

* By AE object, I mean objects in the Automation Engine. This is a different concept from objects in Java.

Update 1 I can get the name of the class to cast to as follows:

String uc4ObjectClassName = uc4Object.getClass().getSimpleName();
System.out.println(String.format("Object is an instance of class %s.", uc4ObjectClassName));

I am hoping there is a straightforward return an object of this class.

Upvotes: 1

Views: 100

Answers (2)

mlowry
mlowry

Reputation: 171

I realized that in this particular case, I can avoid the complexity introduced by the requirement that the method be able to return objects of different types.

The subclasses of UC4Object all share a header() method, and that method returns an object of class XHeader. That is what I need at the moment, so I can make XHeader the method's return type.

public static XHeader getObjectHeader(UC4Object uc4Object) {
    XHeader header = null;
    String objectName = uc4Object.getName();
    String uc4ObjectClassName = uc4Object.getClass().getSimpleName();
    System.out.println(String.format("Object is an instance of class %s.", uc4ObjectClassName));
    switch (uc4ObjectClassName) {
        case "JobPlan":
            System.out.println(String.format("Object %s is a standard workflow.", objectName));
            header = ((JobPlan)uc4Object).header();
            break;
        case "WorkflowIF":
            System.out.println(String.format("Object %s is an IF workflow.", objectName));
            header = ((WorkflowIF)uc4Object).header();
            break;
        case "WorkflowLoop":
            System.out.println(String.format("Object %s is a FOREACH workflow.", objectName));
            header = ((WorkflowLoop)uc4Object).header();
            break;
        case "Schedule":
            System.out.println(String.format("Object %s is a Schedule.", objectName));
            header = ((Schedule)uc4Object).header();
            break;
        case "Script":
            System.out.println(String.format("Object %s is a Script.", objectName));
            header = ((Script)uc4Object).header();
            break;
        case "TimeEvent":
            System.out.println(String.format("Object %s is a Time Event.", objectName));
            header = ((TimeEvent)uc4Object).header();
            break;
        case "FileEvent":
            System.out.println(String.format("Object %s is a File Event.", objectName));
            header = ((FileEvent)uc4Object).header();
            break;
        case "DatabaseEvent":
            System.out.println(String.format("Object %s is a DB Event.", objectName));
            header = ((DatabaseEvent)uc4Object).header();
            break;
        case "ConsoleEvent":
            System.out.println(String.format("Object %s is a Console Event.", objectName));
            header = ((ConsoleEvent)uc4Object).header();
            break;
        case "Job":
            System.out.println(String.format("Object %s is an OS Job.", objectName));
            header = ((Job)uc4Object).header();
            break;
        case "FileTransfer":
            System.out.println(String.format("Object %s is a File Transfer.", objectName));
            header = ((FileTransfer)uc4Object).header();
            break;
        default:
            System.out.println(String.format("Unknown object type."));
            header = null;
    }
    return header;
}

I’m glad to learn of ways to improve on this approach.

Upvotes: 0

davidxxx
davidxxx

Reputation: 131346

I want to write a method that takes a UC4Object as input, and returns an object of the class appropriate to the AE object type — e.g., the method returns an object of type JobPlan for a workflow.

You can create a method that downcasts the object according to the getType() value and returns it. But from the client side of the method, you could not manipulate directly this type as the client doesn't know the returned type.
That means that you should apply your processing/work with the cast object before returning it.

About the way to achieve the mapping, as you want to manipulate the specific subtype, you don't have other choice that using a series of conditional statements.


To allow the clients to manipulate the specific subtypes, you should probably redesign by casting first all objects to their subtypes and by storing all of them in a custom Workflow structure that contains fields with specific types.

That could look like :

public class Workflow{
    private List<JobPlan> jobPlans;
    private List<Schedule> schedules;
    ...
}

Load and store all UC4Object in a Workflow instance :

List<UC4Object> workflowObjects = ...;
Workflow myWorkflow  = new WorkflowMapper().create(workflowObjects);

In this way the client can so find all of them :

List<JobPlan> jobPlans = myWorkflow.getJobPlans();
List<Schedule> schedules = myWorkflow.getSchedules();

or individually (by id for example) :

int id = 1;
JobPlan jobPlan = myWorkflow.getJobPlan(id);
Schedule schedule = myWorkflow.getSchedule(id);

By using this way you have another advantage : you don't need any longer to use a series of conditional statements during the downcasts as the only processing now is adding them in the Workflow instance.

You could store a Map<String, Consumer<UC4Object>> in the mapper class where the Consumer is the setter method of the workflow.

It could give something as :

public class WorkflowMapper {

    Map<String, Consumer<UC4Object>> map = new HashMap<>();

    private Workflow workflow = new Workflow();

    {
        map.put("JOBP", (uc4Object) -> workflow.setJobPlan((JobPlan) uc4Object));
        map.put("EVNT_TIM", (uc4Object) -> workflow.setTimeEvent((TimeEvent) uc4Object));
        // and so for ...
    }

    public Workflow create(List<UC4Object> uc4Objects) {
        for (UC4Object o : uc4Objects) {

            final String type = o.getType();
            map.getOrDefault(type, (t) -> {
                throw new IllegalArgumentException("no matching for getType = " + t);
            })
               .accept(o);
        }
        return workflow;
    }
}

Upvotes: 1

Related Questions