package com.codahale.dropwizard.testing.junit;

import com.codahale.dropwizard.Application;
import com.codahale.dropwizard.Configuration;
import com.codahale.dropwizard.cli.ServerCommand;
import com.codahale.dropwizard.lifecycle.ServerLifecycleListener;
import com.codahale.dropwizard.setup.Bootstrap;
import com.codahale.dropwizard.setup.Environment;
import com.google.common.collect.ImmutableMap;
import net.sourceforge.argparse4j.inf.Namespace;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.util.Enumeration;

/**
 * A JUnit rule for starting and stopping your application at the start and end of a test class.
 * @param <C> the configuration type
 */
public class DropwizardAppRule<C extends Configuration> implements TestRule {

    private final Class<? extends Application<C>> applicationClass;
    private final String configPath;

    private C configuration;
    private Application<C> application;
    private Environment environment;
    private Server jettyServer;

    public DropwizardAppRule(Class<? extends Application<C>> applicationClass,
                             String configPath,
                             ConfigOverride... configOverrides) {
        this.applicationClass = applicationClass;
        this.configPath = configPath;
        for (ConfigOverride configOverride: configOverrides) {
            configOverride.addToSystemProperties();
        }
    }

    @Override
    public Statement apply(final Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                startIfRequired();
                try {
                    base.evaluate();
                } finally {
                    resetConfigOverrides();
                    jettyServer.stop();
                }
            }
        };
    }

    private void resetConfigOverrides() {
        for (Enumeration<?> props = System.getProperties().propertyNames(); props.hasMoreElements();) {
            String keyString = (String) props.nextElement();
            if (keyString.startsWith("dw.")) {
                System.clearProperty(keyString);
            }
        }
    }

    private void startIfRequired() {
        if (jettyServer != null) {
            return;
        }

        try {
            application = applicationClass.newInstance();

            final Bootstrap<C> bootstrap = new Bootstrap<C>(application) {
                @Override
                public void run(C configuration, Environment environment) throws Exception {
                    environment.lifecycle().addServerLifecycleListener(new ServerLifecycleListener() {
                                    @Override
                                    public void serverStarted(Server server) {
                                        jettyServer = server;
                                    }
                                });
                    DropwizardAppRule.this.configuration = configuration;
                    DropwizardAppRule.this.environment = environment;
                    super.run(configuration, environment);
                }
            };

            application.initialize(bootstrap);
            final ServerCommand<C> command = new ServerCommand<>(application);
            final Namespace namespace = new Namespace(ImmutableMap.<String, Object>of("file", configPath));
            command.run(bootstrap, namespace);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public C getConfiguration() {
        return configuration;
    }

    public int getLocalPort() {
        return ((ServerConnector) jettyServer.getConnectors()[0]).getLocalPort();
    }

    @SuppressWarnings("unchecked")
    public <S extends Application<C>> S getApplication() {
        return (S) application;
    }

    public Environment getEnvironment() {
        return environment;
    }
}

@@ -638,24 +638,25 @@
 .Statement;%0A
+%0A
 import java.
@@ -775,16 +775,315 @@
  class.%0A
+ * %3Cp/%3E%0A * By default, the %7B@link Application%7D will be constructed using reflection to invoke the nullary%0A * constructor. If your application does not provide a public nullary constructor, you will need to%0A * override the %7B@link #newApplication()%7D method to provide your application instance(s).%0A *%0A
  * @para
@@ -2784,17 +2784,20 @@
 ation = 
-a
+newA
 pplicati
@@ -2802,25 +2802,8 @@
 tion
-Class.newInstance
 ();%0A
@@ -4159,24 +4159,226 @@
 t();%0A    %7D%0A%0A
+    public Application%3CC%3E newApplication() %7B%0A        try %7B%0A            return applicationClass.newInstance();%0A        %7D catch (Exception e) %7B%0A            throw new RuntimeException(e);%0A        %7D%0A    %7D%0A%0A
     @Suppres
@@ -4412,17 +4412,17 @@
 public %3C
-S
+A
  extends
@@ -4438,17 +4438,17 @@
 ion%3CC%3E%3E 
-S
+A
  getAppl
@@ -4475,17 +4475,17 @@
 return (
-S
+A
 ) applic

Generalize DropwizardAppRule