likern
likern

Reputation: 3964

Conditional lambda execution in Java 8

I have a method create(Environment env) in Java 8, which have multiple statements. Now I need to rewrite method to add support of new migration functionality.

To add support of migration just means:

If Environment env object has field migration set to true don't execute some code in create function.

That's why I wrap these code blocks:

protected Environment create(Environment env) 
{
    statements;
    if (!env.isForMigrate()) {
         // executed only if it's NOT a migration
        statements;
        // for example: imh.create(ve)
        // or: newEnv.setAps(env.getAps());
    }
    ...
    statements;
    if (!env.isForMigrate()) {
         // executed only if it's NOT a migration
        statements;
    }
    ...
    and so on...
}

These code blocks I widespread all over create function. Thus I have to add conditional execution for multiple code blocks.

Can I get some advantages in case of using lambda expressions for this? Is there any pattern in Java 8?

My intention is to write something like this:

final Predicate<T> forMigrate = (func) -> {
    // closure for Environment env
    if (env.isForMigrate()) {
        func(); // execute passed statements
    }
}

... 
forMigrate({
    Environment newEnv = apsh().envh().im2aps(ve);
    newEnv.setAps(env.getAps());
    newEnv.setOsId(env.getOsId());
});

Thus I want to get lambda expression, to which I could pass any block of code. And lambda expression will execute these statements only if it's not a migration.

  1. How can I write this forMigrate lambda function?
  2. Is there any advantages of using lambda expressions vs old if (...) {} statements in this example?

Note:

  1. I don't control Environment class, it's auto-generated from XML file.
  2. I want to maximum restrict scope of forMigration - only inside create (don't make it visible anywhere) - that's why I want to assign lambda expression to variable: final ... forMigrate = (...) -> { ... }.
  3. I want to use lexical scoping for Environment, dont' pass it directly to lambda. Use it from where lambda is defined.

Original function create:

protected Environment create(Environment env)
    {
        if(env.getHostname()!=null && env.getHostname().endsWith(".")){
            String normalizedHostname = env.getHostname().substring(0, env.getHostname().length() - 1);
            env.setHostname(normalizedHostname);
        }
        Ve ve = apsh().envh().aps2im(env);
        if (ve.getHostname() == null) {
            ve.setHostname(ve.getName());
        }
        List<String> apps = env.getApps();
        Boolean passwordSet = false;
        imh.create(ve);
        ve = imh.getVe(ve.getCustomerId().intValue(), ve.getName());
        if(env.getPassword()!= null && !env.getPassword().isEmpty()){
            try{
                imh.setVePassword(ve.getCustomerId(), ve.getName(), env.getPassword());
                passwordSet = true;
            } catch(Exception ex){
                logger.error("Failed to set password for VE: " +  env.getName(), ex);
            }
        }
        if (!apps.isEmpty()) {
            try {
                imh.setVeApps(ve.getCustomerId().intValue(), ve.getName(), apps);
            } catch (Exception ex) {
                logger.error("Failed to install applications VE: {}", ex);
            }
        }
        VeFacade vef = vehFactory.create(ve.getCustomerId(), ve.getName());
        vef.operation("start");

        ve = imh.getVe(ve.getCustomerId().intValue(), ve.getName());
        Environment newEnv = apsh().envh().im2aps(ve);
        newEnv.setAps(env.getAps());
        newEnv.setOsId(env.getOsId());
        newEnv.setSample(env.getSample());
        newEnv.setHosting(env.getHosting());
        newEnv.setDomain(env.getDomain());
        newEnv.getStatus().setUptime(Long.valueOf(new Date().getTime()));
        newEnv.setPassword(null);  //prevent password from being saved in DB    
        newEnv.setPasswordSet(passwordSet);
        apsh().envh().fillOsData(newEnv, apsh().teh().getOs(newEnv.getOsId()));
        apsh().envh().synchPublicAddresses(newEnv, ve);
        apsh().dnsh().synchDomainRecords(newEnv);
        logger.info("Environment '{}' successfully created", newEnv.getName());
        return newEnv;
    }

How I would rewrite it in old Java 7 style:

protected Environment create(Environment env)
    {
        if(env.getHostname()!=null && env.getHostname().endsWith(".")){
            String normalizedHostname = env.getHostname().substring(0, env.getHostname().length() - 1);
            env.setHostname(normalizedHostname);
        }
        Ve ve = apsh().envh().aps2im(env);
        if (ve.getHostname() == null) {
            ve.setHostname(ve.getName());
        }
        List<String> apps = env.getApps();
        Boolean passwordSet = false;

        // NOTE: Wrap block of code
        if (env.isForMigrate() == false) {
            imh.create(ve);
        }
        ve = imh.getVe(ve.getCustomerId().intValue(), ve.getName());
        if(env.getPassword()!= null && !env.getPassword().isEmpty()){
            try{
                imh.setVePassword(ve.getCustomerId(), ve.getName(), env.getPassword());
                passwordSet = true;
            } catch(Exception ex){
                logger.error("Failed to set password for VE: " +  env.getName(), ex);
            }
        }
        if (!apps.isEmpty()) {
            try {
                imh.setVeApps(ve.getCustomerId().intValue(), ve.getName(), apps);
            } catch (Exception ex) {
                logger.error("Failed to install applications VE: {}", ex);
            }
        }

       // NOTE: Wrap block of code
       if (env.isForMigrate() == false) {
           VeFacade vef = vehFactory.create(ve.getCustomerId(), ve.getName());
           vef.operation("start");
       }

        ve = imh.getVe(ve.getCustomerId().intValue(), ve.getName());
        Environment newEnv = apsh().envh().im2aps(ve);
        // NOTE: Wrap block of code
        if (env.isForMigrate() == false) {
            newEnv.setAps(env.getAps());
            newEnv.setOsId(env.getOsId());
            newEnv.setSample(env.getSample());
        }
        newEnv.setHosting(env.getHosting());
        newEnv.setDomain(env.getDomain());
        newEnv.getStatus().setUptime(Long.valueOf(new Date().getTime()));
        newEnv.setPassword(null);  //prevent password from being saved in DB    
        newEnv.setPasswordSet(passwordSet);
        apsh().envh().fillOsData(newEnv, apsh().teh().getOs(newEnv.getOsId()));
        apsh().envh().synchPublicAddresses(newEnv, ve);
        apsh().dnsh().synchDomainRecords(newEnv);
        logger.info("Environment '{}' successfully created", newEnv.getName());
        return newEnv;
    }

Upvotes: 2

Views: 6004

Answers (3)

user4235730
user4235730

Reputation: 2619

I don't know if this is an option for you, but in create you could write:

Consumer<Runnable> forMigration = runnable -> {
    if (environment.isForMigrate()) runnable.run();
};

and then call it like this:

forMigration.accept(() -> System.out.println("migrating"));

You will not easily get rid of that functional interface method call, though, because you can only use function call syntax (with parentheses) on functions, which cannot capture the call site's environment.

Upvotes: 0

Giovanni
Giovanni

Reputation: 4015

this is a sample based on your code

private static class Environment {
    private String aps;
    private String osId;
    private String sample;
    private boolean forMigrate;

    public String getAps() {
        return aps;
    }
    public void setAps(String aps) {
        this.aps = aps;
    }
    public String getOsId() {
        return osId;
    }
    public void setOsId(String osId) {
        this.osId = osId;
    }
    public String getSample() {
        return sample;
    }
    public void setSample(String sample) {
        this.sample = sample;
    }

    private void forMigration(Environment e, Consumer<Environment> con) {
        if (!e.isForMigrate()) {
            con.accept(e);
        }
    }

    public boolean isForMigrate() {
        return forMigrate;
    }
    public void setForMigrate(boolean isForMigrate) {
        this.forMigrate = isForMigrate;
    }

    protected Environment create(Environment env) {
        Environment newEnv= new Environment();
        List<String> imh=new ArrayList<>();
        forMigration(env, e -> {newEnv.setAps(e.getAps());newEnv.setOsId(e.getOsId()); });
        forMigration(env, e -> {imh.add("test for generic call"); });
        return newEnv;
    }
}

Using a Consumer you can reference the same Enviroment you use for the check in the lambda (if a Enviroment is needed).

Upvotes: 3

assylias
assylias

Reputation: 328659

You could write something like:

private static void forMigrate(Environnement env, Runnable r) {
  if (!env.isForMigrate()) r.run();
}

And in your code:

forMigrate(env, () -> {
    newEnv.setAps(env.getAps());
    newEnv.setOsId(env.getOsId());
    newEnv.setSample(env.getSample());
  }
);

Upvotes: 2

Related Questions