From 7fb9922dd7e9e97dbdf697d98e1e47ff00f35c5a Mon Sep 17 00:00:00 2001 From: Fabrice Bellingard Date: Wed, 5 Sep 2012 16:05:35 +0200 Subject: Refactor and fix violations --- src/main/java/org/sonar/runner/Launcher.java | 120 -------------------- src/main/java/org/sonar/runner/Main.java | 6 +- src/main/java/org/sonar/runner/Runner.java | 58 ++-------- .../sonar/runner/bootstrapper/Bootstrapper.java | 4 +- .../runner/bootstrapper/BootstrapperIOUtils.java | 11 +- .../runner/bootstrapper/BootstrapperVersion.java | 52 --------- src/main/java/org/sonar/runner/model/Launcher.java | 122 +++++++++++++++++++++ .../sonar/runner/model/SonarProjectBuilder.java | 7 +- .../org/sonar/runner/utils/SonarRunnerVersion.java | 54 +++++++++ 9 files changed, 208 insertions(+), 226 deletions(-) delete mode 100644 src/main/java/org/sonar/runner/Launcher.java delete mode 100644 src/main/java/org/sonar/runner/bootstrapper/BootstrapperVersion.java create mode 100644 src/main/java/org/sonar/runner/model/Launcher.java create mode 100644 src/main/java/org/sonar/runner/utils/SonarRunnerVersion.java (limited to 'src/main/java/org/sonar/runner') diff --git a/src/main/java/org/sonar/runner/Launcher.java b/src/main/java/org/sonar/runner/Launcher.java deleted file mode 100644 index 2549300..0000000 --- a/src/main/java/org/sonar/runner/Launcher.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Sonar Standalone Runner - * Copyright (C) 2011 SonarSource - * dev@sonar.codehaus.org - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ - -package org.sonar.runner; - -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.joran.JoranConfigurator; -import ch.qos.logback.core.joran.spi.JoranException; -import com.google.common.annotations.VisibleForTesting; -import org.apache.commons.configuration.CompositeConfiguration; -import org.apache.commons.configuration.Configuration; -import org.apache.commons.configuration.EnvironmentConfiguration; -import org.apache.commons.configuration.MapConfiguration; -import org.apache.commons.configuration.SystemConfiguration; -import org.apache.commons.io.IOUtils; -import org.slf4j.LoggerFactory; -import org.sonar.api.batch.bootstrap.ProjectDefinition; -import org.sonar.api.batch.bootstrap.ProjectReactor; -import org.sonar.api.utils.SonarException; -import org.sonar.batch.Batch; -import org.sonar.batch.bootstrapper.EnvironmentInformation; -import org.sonar.runner.model.SonarProjectBuilder; - -import java.io.File; -import java.io.InputStream; -import java.util.Properties; - -/** - * Contrary to {@link org.sonar.runner.Runner}, this class is executed within the classloader - * provided by the server. It contains the installed plugins and the same version of sonar-batch as the server. - */ -public class Launcher { - - private Properties propertiesFromRunner; - - public Launcher(Properties properties) { - this.propertiesFromRunner = properties; - } - - /** - * This method invoked from {@link Main}. Do not rename it. - */ - public void execute() { - File baseDir = new File(propertiesFromRunner.getProperty(Runner.PROPERTY_PROJECT_DIR)); - ProjectDefinition project = SonarProjectBuilder.create(baseDir, propertiesFromRunner).generateProjectDefinition(); - Configuration initialConfiguration = getInitialConfiguration(project); - initLogging(initialConfiguration); - executeBatch(project, initialConfiguration); - } - - private void executeBatch(ProjectDefinition project, Configuration initialConfiguration) { - ProjectReactor reactor = new ProjectReactor(project); - String runnerVersion = propertiesFromRunner.getProperty(Runner.PROPERTY_RUNNER_VERSION); - Batch batch = Batch.create(reactor, initialConfiguration, new EnvironmentInformation("Runner", runnerVersion)); - batch.execute(); - } - - private void initLogging(Configuration initialConfiguration) { - LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); - JoranConfigurator jc = new JoranConfigurator(); - jc.setContext(context); - context.reset(); - InputStream input = Batch.class.getResourceAsStream("/org/sonar/batch/logback.xml"); - System.setProperty("ROOT_LOGGER_LEVEL", isDebug() ? "DEBUG" : "INFO"); - context.putProperty("SQL_LOGGER_LEVEL", getSqlLevel(initialConfiguration));// since 2.14. Ignored on previous versions. - context.putProperty("SQL_RESULTS_LOGGER_LEVEL", getSqlResultsLevel(initialConfiguration));// since 2.14. Ignored on previous versions. - try { - jc.doConfigure(input); - - } catch (JoranException e) { - throw new SonarException("can not initialize logging", e); - - } finally { - IOUtils.closeQuietly(input); - } - } - - @VisibleForTesting - protected boolean isDebug() { - return Boolean.parseBoolean(propertiesFromRunner.getProperty(Runner.PROPERTY_VERBOSE, propertiesFromRunner.getProperty(Runner.PROPERTY_OLD_DEBUG_MODE, "false"))); - } - - @VisibleForTesting - protected static String getSqlLevel(Configuration config) { - boolean showSql = config.getBoolean("sonar.showSql", false); - return showSql ? "DEBUG" : "WARN"; - } - - @VisibleForTesting - protected static String getSqlResultsLevel(Configuration config) { - boolean showSql = config.getBoolean("sonar.showSqlResults", false); - return showSql ? "DEBUG" : "WARN"; - } - - private Configuration getInitialConfiguration(ProjectDefinition project) { - CompositeConfiguration configuration = new CompositeConfiguration(); - configuration.addConfiguration(new SystemConfiguration()); - configuration.addConfiguration(new EnvironmentConfiguration()); - configuration.addConfiguration(new MapConfiguration(project.getProperties())); - return configuration; - } - -} diff --git a/src/main/java/org/sonar/runner/Main.java b/src/main/java/org/sonar/runner/Main.java index d133b5f..1bfcf7d 100644 --- a/src/main/java/org/sonar/runner/Main.java +++ b/src/main/java/org/sonar/runner/Main.java @@ -20,6 +20,8 @@ package org.sonar.runner; +import org.sonar.runner.utils.SonarRunnerVersion; + import org.sonar.runner.bootstrapper.BootstrapException; import org.sonar.runner.bootstrapper.BootstrapperIOUtils; @@ -50,7 +52,7 @@ public final class Main { try { Properties props = loadProperties(args); Runner runner = Runner.create(props); - log("Runner version: " + runner.getRunnerVersion()); + log("Runner version: " + SonarRunnerVersion.getVersion()); log("Java version: " + System.getProperty("java.version", "") + ", vendor: " + System.getProperty("java.vendor", "")); log("OS name: \"" + System.getProperty("os.name") + "\", version: \"" + System.getProperty("os.version") + "\", arch: \"" + System.getProperty("os.arch") + "\""); @@ -58,7 +60,7 @@ public final class Main { log("Other system properties:"); log(" - sun.arch.data.model: \"" + System.getProperty("sun.arch.data.model") + "\""); } - log("Server: " + runner.getServerURL()); + log("Server: " + runner.getSonarServerURL()); try { log("Work directory: " + runner.getWorkDir().getCanonicalPath()); } catch (IOException e) { diff --git a/src/main/java/org/sonar/runner/Runner.java b/src/main/java/org/sonar/runner/Runner.java index 130bc8c..0be391f 100644 --- a/src/main/java/org/sonar/runner/Runner.java +++ b/src/main/java/org/sonar/runner/Runner.java @@ -19,14 +19,13 @@ */ package org.sonar.runner; +import org.sonar.runner.utils.SonarRunnerVersion; + import org.sonar.runner.bootstrapper.BootstrapClassLoader; import org.sonar.runner.bootstrapper.BootstrapException; import org.sonar.runner.bootstrapper.Bootstrapper; -import org.sonar.runner.bootstrapper.BootstrapperIOUtils; import java.io.File; -import java.io.IOException; -import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -63,11 +62,6 @@ public final class Runner { */ private static final String[] UNSUPPORTED_VERSIONS = {"1", "2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6", "2.7", "2.8", "2.9", "2.10"}; - /** - * Array of all mandatory properties required to execute runner. - */ - private static final String[] MANDATORY_PROPERTIES = {"sonar.projectKey", "sonar.projectName", "sonar.projectVersion", "sources"}; - private File projectDir; private File workDir; private Properties properties; @@ -82,28 +76,14 @@ public final class Runner { } public void execute() { - checkMandatoryProperties(); - Bootstrapper bootstrapper = new Bootstrapper("SonarRunner/" + getRunnerVersion(), getServerURL(), getWorkDir()); + String sonarRunnerVersion = SonarRunnerVersion.getVersion(); + properties.put(PROPERTY_RUNNER_VERSION, sonarRunnerVersion); + Bootstrapper bootstrapper = new Bootstrapper("SonarRunner/" + sonarRunnerVersion, getSonarServerURL(), getWorkDir()); checkSonarVersion(bootstrapper); delegateExecution(createClassLoader(bootstrapper)); } - void checkMandatoryProperties() { - StringBuilder missing = new StringBuilder(); - for (String mandatoryProperty : MANDATORY_PROPERTIES) { - if (!properties.containsKey(mandatoryProperty)) { - if (missing.length() > 0) { - missing.append(", "); - } - missing.append(mandatoryProperty); - } - } - if (missing.length() != 0) { - throw new RunnerException("You must define mandatory properties: " + missing); - } - } - - public String getServerURL() { + protected String getSonarServerURL() { return properties.getProperty("sonar.host.url", "http://localhost:9000"); } @@ -113,7 +93,7 @@ public final class Runner { if (!projectDir.isDirectory() || !projectDir.exists()) { throw new IllegalArgumentException("Project home must be an existing directory: " + path); } - // project home exist, add its absolute path as "sonar.runner.projectDir" property + // project home exists: add its absolute path as "sonar.runner.projectDir" property properties.put(PROPERTY_PROJECT_DIR, projectDir.getAbsolutePath()); workDir = initWorkDir(); } @@ -133,38 +113,24 @@ public final class Runner { return new File(projectDir, customWorkDir.getPath()); } - public File getProjectDir() { + protected File getProjectDir() { return projectDir; } /** * @return work directory, default is ".sonar" in project directory */ - public File getWorkDir() { + protected File getWorkDir() { return workDir; } /** * @return global properties, project properties and command-line properties */ - public Properties getProperties() { + protected Properties getProperties() { return properties; } - public String getRunnerVersion() { - InputStream in = null; - try { - in = Runner.class.getResourceAsStream("/org/sonar/runner/version.txt"); - Properties props = new Properties(); - props.load(in); - return props.getProperty("version"); - } catch (IOException e) { - throw new BootstrapException("Could not load the version information for Sonar Standalone Runner", e); - } finally { - BootstrapperIOUtils.closeQuietly(in); - } - } - protected void checkSonarVersion(Bootstrapper bootstrapper) { String serverVersion = bootstrapper.getServerVersion(); if (isUnsupportedVersion(serverVersion)) { @@ -194,7 +160,7 @@ public final class Runner { } /** - * Loads {@link Launcher} from specified {@link org.sonar.batch.bootstrapper.BootstrapClassLoader} and passes control to it. + * Loads Launcher class from specified {@link org.sonar.batch.bootstrapper.BootstrapClassLoader} and passes control to it. * * @see Launcher#execute() */ @@ -202,7 +168,7 @@ public final class Runner { ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(sonarClassLoader); - Class launcherClass = sonarClassLoader.findClass("org.sonar.runner.Launcher"); + Class launcherClass = sonarClassLoader.findClass("org.sonar.runner.model.Launcher"); Constructor constructor = launcherClass.getConstructor(Properties.class); Object launcher = constructor.newInstance(getProperties()); Method method = launcherClass.getMethod("execute"); diff --git a/src/main/java/org/sonar/runner/bootstrapper/Bootstrapper.java b/src/main/java/org/sonar/runner/bootstrapper/Bootstrapper.java index d8e2187..d033364 100644 --- a/src/main/java/org/sonar/runner/bootstrapper/Bootstrapper.java +++ b/src/main/java/org/sonar/runner/bootstrapper/Bootstrapper.java @@ -19,6 +19,8 @@ */ package org.sonar.runner.bootstrapper; +import org.sonar.runner.utils.SonarRunnerVersion; + import java.io.*; import java.net.HttpURLConnection; import java.net.MalformedURLException; @@ -137,7 +139,7 @@ public class Bootstrapper { * By convention, the product tokens are listed in order of their significance for identifying the application. */ String getUserAgent() { - return "sonar-bootstrapper/" + BootstrapperVersion.getVersion() + " " + productToken; + return "sonar-bootstrapper/" + SonarRunnerVersion.getVersion() + " " + productToken; } HttpURLConnection newHttpConnection(URL url) throws IOException { diff --git a/src/main/java/org/sonar/runner/bootstrapper/BootstrapperIOUtils.java b/src/main/java/org/sonar/runner/bootstrapper/BootstrapperIOUtils.java index c47186a..af55370 100644 --- a/src/main/java/org/sonar/runner/bootstrapper/BootstrapperIOUtils.java +++ b/src/main/java/org/sonar/runner/bootstrapper/BootstrapperIOUtils.java @@ -19,7 +19,14 @@ */ package org.sonar.runner.bootstrapper; -import java.io.*; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; public final class BootstrapperIOUtils { @@ -40,7 +47,7 @@ public final class BootstrapperIOUtils { if (closeable != null) { closeable.close(); } - } catch (IOException ioe) { // NOSONAR + } catch (IOException ioe) { } } diff --git a/src/main/java/org/sonar/runner/bootstrapper/BootstrapperVersion.java b/src/main/java/org/sonar/runner/bootstrapper/BootstrapperVersion.java deleted file mode 100644 index b50c03b..0000000 --- a/src/main/java/org/sonar/runner/bootstrapper/BootstrapperVersion.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Sonar Standalone Runner - * Copyright (C) 2011 SonarSource - * dev@sonar.codehaus.org - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ -package org.sonar.runner.bootstrapper; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; - -public enum BootstrapperVersion { - - INSTANCE; - - private static final String PROPERTIES_PATH = "/org/sonar/runner/version.txt"; - private String version; - - public static String getVersion() { - return INSTANCE.version; - } - - private BootstrapperVersion() { - InputStream input = getClass().getResourceAsStream(PROPERTIES_PATH); - try { - Properties properties = new Properties(); - properties.load(input); - this.version = properties.getProperty("version"); - - } catch (IOException e) { - // Can not load the version - this.version = ""; - - } finally { - BootstrapperIOUtils.closeQuietly(input); - } - } -} diff --git a/src/main/java/org/sonar/runner/model/Launcher.java b/src/main/java/org/sonar/runner/model/Launcher.java new file mode 100644 index 0000000..948a6c0 --- /dev/null +++ b/src/main/java/org/sonar/runner/model/Launcher.java @@ -0,0 +1,122 @@ +/* + * Sonar Standalone Runner + * Copyright (C) 2011 SonarSource + * dev@sonar.codehaus.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ + +package org.sonar.runner.model; + +import org.sonar.runner.Main; +import org.sonar.runner.Runner; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; +import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.EnvironmentConfiguration; +import org.apache.commons.configuration.MapConfiguration; +import org.apache.commons.configuration.SystemConfiguration; +import org.apache.commons.io.IOUtils; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.api.utils.SonarException; +import org.sonar.batch.Batch; +import org.sonar.batch.bootstrapper.EnvironmentInformation; + +import java.io.File; +import java.io.InputStream; +import java.util.Properties; + +/** + * Contrary to {@link org.sonar.runner.Runner}, this class is executed within the classloader + * provided by the server. It contains the installed plugins and the same version of sonar-batch as the server. + */ +public class Launcher { + + private Properties propertiesFromRunner; + + public Launcher(Properties properties) { + this.propertiesFromRunner = properties; + } + + /** + * This method invoked from {@link Main}. Do not rename it. + */ + public void execute() { + File baseDir = new File(propertiesFromRunner.getProperty(Runner.PROPERTY_PROJECT_DIR)); + ProjectDefinition project = SonarProjectBuilder.create(baseDir, propertiesFromRunner).generateProjectDefinition(); + Configuration initialConfiguration = getInitialConfiguration(project); + initLogging(initialConfiguration); + executeBatch(project, initialConfiguration); + } + + private void executeBatch(ProjectDefinition project, Configuration initialConfiguration) { + ProjectReactor reactor = new ProjectReactor(project); + String runnerVersion = propertiesFromRunner.getProperty(Runner.PROPERTY_RUNNER_VERSION); + Batch batch = Batch.create(reactor, initialConfiguration, new EnvironmentInformation("Runner", runnerVersion)); + batch.execute(); + } + + private void initLogging(Configuration initialConfiguration) { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + JoranConfigurator jc = new JoranConfigurator(); + jc.setContext(context); + context.reset(); + InputStream input = Batch.class.getResourceAsStream("/org/sonar/batch/logback.xml"); + System.setProperty("ROOT_LOGGER_LEVEL", isDebug() ? "DEBUG" : "INFO"); + context.putProperty("SQL_LOGGER_LEVEL", getSqlLevel(initialConfiguration));// since 2.14. Ignored on previous versions. + context.putProperty("SQL_RESULTS_LOGGER_LEVEL", getSqlResultsLevel(initialConfiguration));// since 2.14. Ignored on previous versions. + try { + jc.doConfigure(input); + + } catch (JoranException e) { + throw new SonarException("can not initialize logging", e); + + } finally { + IOUtils.closeQuietly(input); + } + } + + @VisibleForTesting + protected boolean isDebug() { + return Boolean.parseBoolean(propertiesFromRunner.getProperty(Runner.PROPERTY_VERBOSE, propertiesFromRunner.getProperty(Runner.PROPERTY_OLD_DEBUG_MODE, "false"))); + } + + @VisibleForTesting + protected static String getSqlLevel(Configuration config) { + boolean showSql = config.getBoolean("sonar.showSql", false); + return showSql ? "DEBUG" : "WARN"; + } + + @VisibleForTesting + protected static String getSqlResultsLevel(Configuration config) { + boolean showSql = config.getBoolean("sonar.showSqlResults", false); + return showSql ? "DEBUG" : "WARN"; + } + + private Configuration getInitialConfiguration(ProjectDefinition project) { + CompositeConfiguration configuration = new CompositeConfiguration(); + configuration.addConfiguration(new SystemConfiguration()); + configuration.addConfiguration(new EnvironmentConfiguration()); + configuration.addConfiguration(new MapConfiguration(project.getProperties())); + return configuration; + } + +} diff --git a/src/main/java/org/sonar/runner/model/SonarProjectBuilder.java b/src/main/java/org/sonar/runner/model/SonarProjectBuilder.java index 1fd4e70..d1c5fde 100644 --- a/src/main/java/org/sonar/runner/model/SonarProjectBuilder.java +++ b/src/main/java/org/sonar/runner/model/SonarProjectBuilder.java @@ -40,8 +40,10 @@ import java.util.Properties; /** * Class that creates a Sonar project definition based on a set of properties. + * + * @since 1.5 */ -public class SonarProjectBuilder { +public final class SonarProjectBuilder { private static final String PROPERTY_SONAR_MODULES = "sonar.modules"; private static final String PROPERTY_MODULE_FILE = "file"; @@ -76,8 +78,7 @@ public class SonarProjectBuilder { } public static SonarProjectBuilder create(File baseDir, Properties properties) { - SonarProjectBuilder builder = new SonarProjectBuilder(baseDir, properties); - return builder; + return new SonarProjectBuilder(baseDir, properties); } public ProjectDefinition generateProjectDefinition() { diff --git a/src/main/java/org/sonar/runner/utils/SonarRunnerVersion.java b/src/main/java/org/sonar/runner/utils/SonarRunnerVersion.java new file mode 100644 index 0000000..12ebbef --- /dev/null +++ b/src/main/java/org/sonar/runner/utils/SonarRunnerVersion.java @@ -0,0 +1,54 @@ +/* + * Sonar Standalone Runner + * Copyright (C) 2011 SonarSource + * dev@sonar.codehaus.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.runner.utils; + +import org.sonar.runner.bootstrapper.BootstrapperIOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public enum SonarRunnerVersion { + + INSTANCE; + + private static final String PROPERTIES_PATH = "/org/sonar/runner/version.txt"; + private String version; + + public static String getVersion() { + return INSTANCE.version; + } + + private SonarRunnerVersion() { + InputStream input = getClass().getResourceAsStream(PROPERTIES_PATH); + try { + Properties properties = new Properties(); + properties.load(input); + this.version = properties.getProperty("version"); + + } catch (IOException e) { + // Can not load the version + this.version = ""; + + } finally { + BootstrapperIOUtils.closeQuietly(input); + } + } +} -- cgit v1.2.3