You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by pk...@apache.org on 2024/04/01 12:51:42 UTC
(logging-log4j2) 01/01: Remove CLI tools from `log4j-core` and `log4j-1.2-api`
This is an automated email from the ASF dual-hosted git repository.
pkarwasz pushed a commit to branch feature/remove-picocli
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 4d62b6d4afc150395f58a54c40defc940bfeb097
Author: Piotr P. Karwasz <pi...@karwasz.org>
AuthorDate: Mon Apr 1 14:48:45 2024 +0200
Remove CLI tools from `log4j-core` and `log4j-1.2-api`
We remove the command line tools to generate logger wrappers and convert
Log4j 1.x configuration.
The tools have been submitted to Log4j Transform:
* apache/logging-log4j-transform#92
* apache/logging-log4j-transform#93
and are not necessary at runtime.
The `Log4j1ConfigurationFactory` that has been removed from
`log4j-1.2-api` is an **older** and deprecated version of the
`PropertiesConfigurationFactory`.
---
.../apache/log4j/config/InputStreamWrapper.java | 91 -
.../log4j/config/Log4j1ConfigurationConverter.java | 249 -
.../log4j/config/Log4j1ConfigurationFactory.java | 57 -
.../log4j/config/Log4j1ConfigurationParser.java | 474 --
.../AbstractLog4j1ConfigurationConverterTest.java | 86 -
.../Log4j1ConfigurationConverterHadoopTest.java | 36 -
.../Log4j1ConfigurationConverterSparkTest.java | 36 -
.../config/Log4j1ConfigurationFactoryTest.java | 191 -
.../src/main/resources/log4j.properties | 23 -
.../hadoop-common/src/main/conf/log4j.properties | 324 --
.../src/test/resources/log4j.properties | 24 -
.../hadoop-kms/src/test/resources/log4j.properties | 30 -
.../src/main/resources/log4j.properties | 30 -
.../hadoop-nfs/src/test/resources/log4j.properties | 24 -
.../src/test/resources/log4j.properties | 50 -
.../bkjournal/src/test/resources/log4j.properties | 51 -
.../src/test/resources/log4j.properties | 50 -
.../src/test/resources/log4j.properties | 24 -
.../src/test/resources/log4j.properties | 24 -
.../src/test/resources/log4j.properties | 24 -
.../src/test/resources/log4j.properties | 24 -
.../src/test/resources/log4j.properties | 24 -
.../src/test/resources/log4j.properties | 24 -
.../src/test/resources/log4j.properties | 24 -
.../hadoop-aws/src/test/resources/log4j.properties | 29 -
.../src/test/resources/log4j.properties | 26 -
.../src/test/resources/log4j.properties | 28 -
.../src/main/sample-conf/log4j.properties | 23 -
.../src/test/resources/log4j.properties | 24 -
.../src/test/resources/log4j.properties | 24 -
.../src/test/resources/log4j.properties | 24 -
.../src/test/resources/log4j.properties | 64 -
.../src/test/resources/log4j.properties | 24 -
.../src/test/resources/log4j.properties | 24 -
.../src/test/resources/log4j.properties | 24 -
.../src/test/resources/log4j.properties | 24 -
.../src/test/resources/log4j.properties | 24 -
.../src/test/resources/log4j.properties | 24 -
.../resources/config-1.2/spark/R/log4j.properties | 28 -
.../src/test/resources/log4j.properties | 27 -
.../src/test/resources/log4j.properties | 24 -
.../spark/core/src/test/resources/log4j.properties | 36 -
.../flume-sink/src/test/resources/log4j.properties | 27 -
.../flume/src/test/resources/log4j.properties | 27 -
.../src/test/resources/log4j.properties | 27 -
.../kafka-0-10/src/test/resources/log4j.properties | 27 -
.../kafka-0-8/src/test/resources/log4j.properties | 27 -
.../src/main/resources/log4j.properties | 37 -
.../src/test/resources/log4j.properties | 27 -
.../graphx/src/test/resources/log4j.properties | 27 -
.../launcher/src/test/resources/log4j.properties | 33 -
.../mllib/src/test/resources/log4j.properties | 27 -
.../spark/repl/src/test/resources/log4j.properties | 27 -
.../catalyst/src/test/resources/log4j.properties | 27 -
.../sql/core/src/test/resources/log4j.properties | 57 -
.../sql/hive/src/test/resources/log4j.properties | 61 -
.../streaming/src/test/resources/log4j.properties | 27 -
.../spark/yarn/src/test/resources/log4j.properties | 31 -
.../log4j/core/tools/GenerateCustomLoggerTest.java | 181 -
.../core/tools/GenerateExtendedLoggerTest.java | 200 -
.../core/tools/BasicCommandLineArguments.java | 36 -
.../log4j/core/tools/CustomLoggerGenerator.java | 30 -
.../log4j/core/tools/ExtendedLoggerGenerator.java | 30 -
.../apache/logging/log4j/core/tools/Generate.java | 1165 ----
.../logging/log4j/core/tools/package-info.java | 25 -
.../log4j/core/tools/picocli/CommandLine.java | 5619 --------------------
src/changelog/.3.x.x/remove_cli_tools.xml | 10 +
67 files changed, 10 insertions(+), 10297 deletions(-)
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/InputStreamWrapper.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/InputStreamWrapper.java
deleted file mode 100644
index 49eaea9187..0000000000
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/InputStreamWrapper.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.log4j.config;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-class InputStreamWrapper extends InputStream {
-
- private final String description;
- private final InputStream input;
-
- public InputStreamWrapper(final InputStream input, final String description) {
- this.input = input;
- this.description = description;
- }
-
- @Override
- public int available() throws IOException {
- return input.available();
- }
-
- @Override
- public void close() throws IOException {
- input.close();
- }
-
- @Override
- public boolean equals(final Object obj) {
- return input.equals(obj);
- }
-
- @Override
- public int hashCode() {
- return input.hashCode();
- }
-
- @Override
- public synchronized void mark(final int readlimit) {
- input.mark(readlimit);
- }
-
- @Override
- public boolean markSupported() {
- return input.markSupported();
- }
-
- @Override
- public int read() throws IOException {
- return input.read();
- }
-
- @Override
- public int read(final byte[] b) throws IOException {
- return input.read(b);
- }
-
- @Override
- public int read(final byte[] b, final int off, final int len) throws IOException {
- return input.read(b, off, len);
- }
-
- @Override
- public synchronized void reset() throws IOException {
- input.reset();
- }
-
- @Override
- public long skip(final long n) throws IOException {
- return input.skip(n);
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + " [description=" + description + ", input=" + input + "]";
- }
-}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationConverter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationConverter.java
deleted file mode 100644
index c0f8e73fcb..0000000000
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationConverter.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.log4j.config;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.concurrent.atomic.AtomicInteger;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.stream.StreamResult;
-import javax.xml.transform.stream.StreamSource;
-import org.apache.logging.log4j.core.config.ConfigurationException;
-import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
-import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
-import org.apache.logging.log4j.core.config.builder.impl.DefaultConfigurationBuilder;
-import org.apache.logging.log4j.core.tools.BasicCommandLineArguments;
-import org.apache.logging.log4j.core.tools.picocli.CommandLine;
-import org.apache.logging.log4j.core.tools.picocli.CommandLine.Command;
-import org.apache.logging.log4j.core.tools.picocli.CommandLine.Option;
-
-/**
- * Tool for converting a Log4j 1.x properties configuration file to Log4j 2.x XML configuration file.
- *
- * <p>
- * Run with "--help" on the command line.
- * </p>
- *
- * <p>
- * Example:
- * </p>
- *
- * <pre>
- * java org.apache.log4j.config.Log4j1ConfigurationConverter --recurse
- * E:\vcs\git\apache\logging\logging-log4j2\log4j-1.2-api\src\test\resources\config-1.2\hadoop --in log4j.properties --verbose
- * </pre>
- */
-public final class Log4j1ConfigurationConverter {
-
- @Command(name = "Log4j1ConfigurationConverter")
- public static class CommandLineArguments extends BasicCommandLineArguments implements Runnable {
-
- @Option(
- names = {"--failfast", "-f"},
- description = "Fails on the first failure in recurse mode.")
- private boolean failFast;
-
- @Option(
- names = {"--in", "-i"},
- description = "Specifies the input file.")
- private Path pathIn;
-
- @Option(
- names = {"--out", "-o"},
- description = "Specifies the output file.")
- private Path pathOut;
-
- @Option(
- names = {"--recurse", "-r"},
- description = "Recurses into this folder looking for the input file")
- private Path recurseIntoPath;
-
- @Option(
- names = {"--verbose", "-v"},
- description = "Be verbose.")
- private boolean verbose;
-
- public Path getPathIn() {
- return pathIn;
- }
-
- public Path getPathOut() {
- return pathOut;
- }
-
- public Path getRecurseIntoPath() {
- return recurseIntoPath;
- }
-
- public boolean isFailFast() {
- return failFast;
- }
-
- public boolean isVerbose() {
- return verbose;
- }
-
- public void setFailFast(final boolean failFast) {
- this.failFast = failFast;
- }
-
- public void setPathIn(final Path pathIn) {
- this.pathIn = pathIn;
- }
-
- public void setPathOut(final Path pathOut) {
- this.pathOut = pathOut;
- }
-
- public void setRecurseIntoPath(final Path recurseIntoPath) {
- this.recurseIntoPath = recurseIntoPath;
- }
-
- public void setVerbose(final boolean verbose) {
- this.verbose = verbose;
- }
-
- @Override
- public void run() {
- if (isHelp()) {
- CommandLine.usage(this, System.err);
- return;
- }
- new Log4j1ConfigurationConverter(this).run();
- }
-
- @Override
- public String toString() {
- return "CommandLineArguments [recurseIntoPath=" + recurseIntoPath + ", verbose=" + verbose + ", pathIn="
- + pathIn + ", pathOut=" + pathOut + "]";
- }
- }
-
- private static final String FILE_EXT_XML = ".xml";
-
- public static void main(final String[] args) {
- CommandLine.run(new CommandLineArguments(), System.err, args);
- }
-
- public static Log4j1ConfigurationConverter run(final CommandLineArguments cla) {
- final Log4j1ConfigurationConverter log4j1ConfigurationConverter = new Log4j1ConfigurationConverter(cla);
- log4j1ConfigurationConverter.run();
- return log4j1ConfigurationConverter;
- }
-
- private final CommandLineArguments cla;
-
- private Log4j1ConfigurationConverter(final CommandLineArguments cla) {
- this.cla = cla;
- }
-
- protected void convert(final InputStream input, final OutputStream output) throws IOException {
- final ConfigurationBuilder<BuiltConfiguration> builder =
- new Log4j1ConfigurationParser().buildConfigurationBuilder(input);
- builder.writeXmlConfiguration(output);
- }
-
- InputStream getInputStream() throws IOException {
- final Path pathIn = cla.getPathIn();
- return pathIn == null ? System.in : new InputStreamWrapper(Files.newInputStream(pathIn), pathIn.toString());
- }
-
- OutputStream getOutputStream() throws IOException {
- final Path pathOut = cla.getPathOut();
- return pathOut == null ? System.out : Files.newOutputStream(pathOut);
- }
-
- private void run() {
- if (cla.getRecurseIntoPath() != null) {
- final AtomicInteger countOKs = new AtomicInteger();
- final AtomicInteger countFails = new AtomicInteger();
- try {
- Files.walkFileTree(cla.getRecurseIntoPath(), new SimpleFileVisitor<Path>() {
- @Override
- public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs)
- throws IOException {
- if (cla.getPathIn() == null || file.getFileName().equals(cla.getPathIn())) {
- verbose("Reading %s", file);
- String newFile = file.getFileName().toString();
- final int lastIndex = newFile.lastIndexOf(".");
- newFile = lastIndex < 0
- ? newFile + FILE_EXT_XML
- : newFile.substring(0, lastIndex) + FILE_EXT_XML;
- final Path resolvedPath = file.resolveSibling(newFile);
- try (final InputStream input =
- new InputStreamWrapper(Files.newInputStream(file), file.toString());
- final OutputStream output = Files.newOutputStream(resolvedPath)) {
- try {
- final ByteArrayOutputStream tmpOutput = new ByteArrayOutputStream();
- convert(input, tmpOutput);
- tmpOutput.close();
- DefaultConfigurationBuilder.formatXml(
- new StreamSource(new ByteArrayInputStream(tmpOutput.toByteArray())),
- new StreamResult(output));
- countOKs.incrementAndGet();
- } catch (ConfigurationException | IOException e) {
- countFails.incrementAndGet();
- if (cla.isFailFast()) {
- throw e;
- }
- e.printStackTrace();
- } catch (TransformerException e) {
- countFails.incrementAndGet();
- if (cla.isFailFast()) {
- throw new IOException(e);
- }
- e.printStackTrace();
- }
- verbose("Wrote %s", resolvedPath);
- }
- }
- return FileVisitResult.CONTINUE;
- }
- });
- } catch (final IOException e) {
- throw new ConfigurationException(e);
- } finally {
- verbose(
- "OK = %,d, Failures = %,d, Total = %,d",
- countOKs.get(), countFails.get(), countOKs.get() + countFails.get());
- }
- } else {
- verbose("Reading %s", cla.getPathIn());
- try (final InputStream input = getInputStream();
- final OutputStream output = getOutputStream()) {
- convert(input, output);
- } catch (final IOException e) {
- throw new ConfigurationException(e);
- }
- verbose("Wrote %s", cla.getPathOut());
- }
- }
-
- private void verbose(final String template, final Object... args) {
- if (cla.isVerbose()) {
- System.err.println(String.format(template, args));
- }
- }
-}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationFactory.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationFactory.java
deleted file mode 100644
index 5454ad7c0b..0000000000
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationFactory.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.log4j.config;
-
-import java.io.IOException;
-import java.io.InputStream;
-import org.apache.logging.log4j.core.LoggerContext;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.ConfigurationException;
-import org.apache.logging.log4j.core.config.ConfigurationFactory;
-import org.apache.logging.log4j.core.config.ConfigurationSource;
-import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
-import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
-
-/**
- * Experimental ConfigurationFactory for Log4j 1.2 properties configuration files.
- */
-// TODO
-// @Category(ConfigurationFactory.CATEGORY)
-// @Plugin("Log4j1ConfigurationFactory")
-//
-// Best Value?
-// @Order(50)
-public class Log4j1ConfigurationFactory extends ConfigurationFactory {
-
- private static final String[] SUFFIXES = {".properties"};
-
- @Override
- public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) {
- final ConfigurationBuilder<BuiltConfiguration> builder;
- try (final InputStream configStream = source.getInputStream()) {
- builder = new Log4j1ConfigurationParser().buildConfigurationBuilder(configStream);
- } catch (final IOException e) {
- throw new ConfigurationException("Unable to load " + source, e);
- }
- return builder.build();
- }
-
- @Override
- protected String[] getSupportedTypes() {
- return SUFFIXES;
- }
-}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationParser.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationParser.java
deleted file mode 100644
index 4e8ea68650..0000000000
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationParser.java
+++ /dev/null
@@ -1,474 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.log4j.config;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Properties;
-import java.util.TreeMap;
-import org.apache.log4j.helpers.OptionConverter;
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.core.Filter.Result;
-import org.apache.logging.log4j.core.appender.ConsoleAppender;
-import org.apache.logging.log4j.core.appender.FileAppender;
-import org.apache.logging.log4j.core.appender.NullAppender;
-import org.apache.logging.log4j.core.appender.RollingFileAppender;
-import org.apache.logging.log4j.core.config.ConfigurationException;
-import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
-import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder;
-import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
-import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
-import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder;
-import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder;
-import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder;
-import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.Strings;
-
-/**
- * Experimental parser for Log4j 1.2 properties configuration files.
- *
- * This class is not thread-safe.
- *
- * <p>
- * From the Log4j 1.2 Javadocs:
- * </p>
- * <p>
- * All option values admit variable substitution. The syntax of variable substitution is similar to that of Unix shells. The string between
- * an opening "${" and closing "}" is interpreted as a key. The value of the substituted variable can be defined as a system property or in
- * the configuration file itself. The value of the key is first searched in the system properties, and if not found there, it is then
- * searched in the configuration file being parsed. The corresponding value replaces the ${variableName} sequence. For example, if java.home
- * system property is set to /home/xyz, then every occurrence of the sequence ${java.home} will be interpreted as /home/xyz.
- * </p>
- */
-public class Log4j1ConfigurationParser {
-
- private static final String COMMA_DELIMITED_RE = "\\s*,\\s*";
- private static final String ROOTLOGGER = "rootLogger";
- private static final String ROOTCATEGORY = "rootCategory";
- private static final String TRUE = "true";
- private static final String FALSE = "false";
- private static final String RELATIVE = "RELATIVE";
- private static final String NULL = "NULL";
-
- private final Properties properties = new Properties();
-
- private final ConfigurationBuilder<BuiltConfiguration> builder =
- ConfigurationBuilderFactory.newConfigurationBuilder();
-
- /**
- * Parses a Log4j 1.2 properties configuration file in ISO 8859-1 encoding into a ConfigurationBuilder.
- *
- * @param input
- * InputStream to read from is assumed to be ISO 8859-1, and will not be closed.
- * @return the populated ConfigurationBuilder, never {@code null}
- * @throws IOException
- * if unable to read the input
- * @throws ConfigurationException
- * if the input does not contain a valid configuration
- */
- public ConfigurationBuilder<BuiltConfiguration> buildConfigurationBuilder(final InputStream input)
- throws IOException {
- try {
- properties.load(input);
- final String rootCategoryValue = getLog4jValue(ROOTCATEGORY);
- final String rootLoggerValue = getLog4jValue(ROOTLOGGER);
- if (rootCategoryValue == null && rootLoggerValue == null) {
- // This is not a Log4j 1 properties configuration file.
- warn("Missing " + ROOTCATEGORY + " or " + ROOTLOGGER + " in " + input);
- // throw new ConfigurationException(
- // "Missing " + ROOTCATEGORY + " or " + ROOTLOGGER + " in " + input);
- }
- builder.setConfigurationName("Log4j1");
- // DEBUG
- final String debugValue = getLog4jValue("debug");
- if (Boolean.parseBoolean(debugValue)) {
- builder.setStatusLevel(Level.DEBUG);
- }
- // global threshold
- final String threshold = OptionConverter.findAndSubst(PropertiesConfiguration.THRESHOLD_KEY, properties);
- if (threshold != null) {
- final Level level = OptionConverter.convertLevel(threshold.trim(), Level.ALL);
- builder.add(builder.newFilter("ThresholdFilter", Result.NEUTRAL, Result.DENY)
- .addAttribute("level", level));
- }
- // Root
- buildRootLogger(getLog4jValue(ROOTCATEGORY));
- buildRootLogger(getLog4jValue(ROOTLOGGER));
- // Appenders
- final Map<String, String> appenderNameToClassName = buildClassToPropertyPrefixMap();
- for (final Map.Entry<String, String> entry : appenderNameToClassName.entrySet()) {
- final String appenderName = entry.getKey();
- final String appenderClass = entry.getValue();
- buildAppender(appenderName, appenderClass);
- }
- // Loggers
- buildLoggers("log4j.category.");
- buildLoggers("log4j.logger.");
- buildProperties();
- return builder;
- } catch (final IllegalArgumentException e) {
- throw new ConfigurationException(e);
- }
- }
-
- private void buildProperties() {
- for (final Map.Entry<Object, Object> entry : new TreeMap<>(properties).entrySet()) {
- final String key = entry.getKey().toString();
- if (!key.startsWith("log4j.") && !key.equals(ROOTCATEGORY) && !key.equals(ROOTLOGGER)) {
- builder.addProperty(key, Objects.toString(entry.getValue(), Strings.EMPTY));
- }
- }
- }
-
- private void warn(final String string) {
- System.err.println(string);
- }
-
- private Map<String, String> buildClassToPropertyPrefixMap() {
- final String prefix = "log4j.appender.";
- final int preLength = prefix.length();
- final Map<String, String> map = new HashMap<>();
- for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
- final Object keyObj = entry.getKey();
- if (keyObj != null) {
- final String key = keyObj.toString().trim();
- if (key.startsWith(prefix)) {
- if (key.indexOf('.', preLength) < 0) {
- final String name = key.substring(preLength);
- final Object value = entry.getValue();
- if (value != null) {
- map.put(name, value.toString().trim());
- }
- }
- }
- }
- }
- return map;
- }
-
- private void buildAppender(final String appenderName, final String appenderClass) {
- switch (appenderClass) {
- case "org.apache.log4j.ConsoleAppender":
- buildConsoleAppender(appenderName);
- break;
- case "org.apache.log4j.FileAppender":
- buildFileAppender(appenderName);
- break;
- case "org.apache.log4j.DailyRollingFileAppender":
- buildDailyRollingFileAppender(appenderName);
- break;
- case "org.apache.log4j.RollingFileAppender":
- buildRollingFileAppender(appenderName);
- break;
- case "org.apache.log4j.varia.NullAppender":
- buildNullAppender(appenderName);
- break;
- default:
- reportWarning("Unknown appender class: " + appenderClass + "; ignoring appender: " + appenderName);
- }
- }
-
- private void buildConsoleAppender(final String appenderName) {
- final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, ConsoleAppender.PLUGIN_NAME);
- final String targetValue = getLog4jAppenderValue(appenderName, "Target", "System.out");
- if (targetValue != null) {
- final ConsoleAppender.Target target;
- switch (targetValue) {
- case "System.out":
- target = ConsoleAppender.Target.SYSTEM_OUT;
- break;
- case "System.err":
- target = ConsoleAppender.Target.SYSTEM_ERR;
- break;
- default:
- reportWarning("Unknown value for console Target: " + targetValue);
- target = null;
- }
- if (target != null) {
- appenderBuilder.addAttribute("target", target);
- }
- }
- buildAttribute(appenderName, appenderBuilder, "Follow", "follow");
- if (FALSE.equalsIgnoreCase(getLog4jAppenderValue(appenderName, "ImmediateFlush"))) {
- reportWarning("ImmediateFlush=false is not supported on Console appender");
- }
- buildAppenderLayout(appenderName, appenderBuilder);
- builder.add(appenderBuilder);
- }
-
- private void buildFileAppender(final String appenderName) {
- final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, FileAppender.PLUGIN_NAME);
- buildFileAppender(appenderName, appenderBuilder);
- builder.add(appenderBuilder);
- }
-
- private void buildFileAppender(final String appenderName, final AppenderComponentBuilder appenderBuilder) {
- buildMandatoryAttribute(appenderName, appenderBuilder, "File", "fileName");
- buildAttribute(appenderName, appenderBuilder, "Append", "append");
- buildAttribute(appenderName, appenderBuilder, "BufferedIO", "bufferedIo");
- buildAttribute(appenderName, appenderBuilder, "BufferSize", "bufferSize");
- buildAttribute(appenderName, appenderBuilder, "ImmediateFlush", "immediateFlush");
- buildAppenderLayout(appenderName, appenderBuilder);
- }
-
- private void buildDailyRollingFileAppender(final String appenderName) {
- final AppenderComponentBuilder appenderBuilder =
- builder.newAppender(appenderName, RollingFileAppender.PLUGIN_NAME);
- buildFileAppender(appenderName, appenderBuilder);
- final String fileName = getLog4jAppenderValue(appenderName, "File");
- final String datePattern = getLog4jAppenderValue(appenderName, "DatePattern", ".yyyy-MM-dd");
- appenderBuilder.addAttribute("filePattern", fileName + "%d{" + datePattern + "}");
- final ComponentBuilder<?> triggeringPolicy = builder.newComponent("Policies")
- .addComponent(builder.newComponent("TimeBasedTriggeringPolicy").addAttribute("modulate", true));
- appenderBuilder.addComponent(triggeringPolicy);
- appenderBuilder.addComponent(builder.newComponent("DefaultRolloverStrategy")
- .addAttribute("max", Integer.MAX_VALUE)
- .addAttribute("fileIndex", "min"));
- builder.add(appenderBuilder);
- }
-
- private void buildRollingFileAppender(final String appenderName) {
- final AppenderComponentBuilder appenderBuilder =
- builder.newAppender(appenderName, RollingFileAppender.PLUGIN_NAME);
- buildFileAppender(appenderName, appenderBuilder);
- final String fileName = getLog4jAppenderValue(appenderName, "File");
- appenderBuilder.addAttribute("filePattern", fileName + ".%i");
- final String maxFileSizeString = getLog4jAppenderValue(appenderName, "MaxFileSize", "10485760");
- final String maxBackupIndexString = getLog4jAppenderValue(appenderName, "MaxBackupIndex", "1");
- final ComponentBuilder<?> triggeringPolicy = builder.newComponent("Policies")
- .addComponent(
- builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", maxFileSizeString));
- appenderBuilder.addComponent(triggeringPolicy);
- appenderBuilder.addComponent(builder.newComponent("DefaultRolloverStrategy")
- .addAttribute("max", maxBackupIndexString)
- .addAttribute("fileIndex", "min"));
- builder.add(appenderBuilder);
- }
-
- private void buildAttribute(
- final String componentName,
- final ComponentBuilder<?> componentBuilder,
- final String sourceAttributeName,
- final String targetAttributeName) {
- final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName);
- if (attributeValue != null) {
- componentBuilder.addAttribute(targetAttributeName, attributeValue);
- }
- }
-
- private void buildMandatoryAttribute(
- final String componentName,
- final ComponentBuilder<?> componentBuilder,
- final String sourceAttributeName,
- final String targetAttributeName) {
- final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName);
- if (attributeValue != null) {
- componentBuilder.addAttribute(targetAttributeName, attributeValue);
- } else {
- reportWarning("Missing " + sourceAttributeName + " for " + componentName);
- }
- }
-
- private void buildNullAppender(final String appenderName) {
- final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, NullAppender.PLUGIN_NAME);
- builder.add(appenderBuilder);
- }
-
- private void buildAppenderLayout(final String name, final AppenderComponentBuilder appenderBuilder) {
- final String layoutClass = getLog4jAppenderValue(name, "layout", null);
- if (layoutClass != null) {
- switch (layoutClass) {
- case "org.apache.log4j.PatternLayout":
- case "org.apache.log4j.EnhancedPatternLayout": {
- String pattern = getLog4jAppenderValue(name, "layout.ConversionPattern", null);
- if (pattern != null) {
- pattern = pattern
- // Log4j 2 and Log4j 1 level names differ for custom levels
- .replaceAll("%([-\\.\\d]*)p(?!\\w)", "%$1v1Level")
- // Log4j 2's %x (NDC) is not compatible with Log4j 1's
- // %x
- // Log4j 1: "foo bar baz"
- // Log4j 2: "[foo, bar, baz]"
- // Use %ndc to get the Log4j 1 format
- .replaceAll("%([-\\.\\d]*)x(?!\\w)", "%$1ndc")
-
- // Log4j 2's %X (MDC) is not compatible with Log4j 1's
- // %X
- // Log4j 1: "{{foo,bar}{hoo,boo}}"
- // Log4j 2: "{foo=bar,hoo=boo}"
- // Use %properties to get the Log4j 1 format
- .replaceAll("%([-\\.\\d]*)X(?!\\w)", "%$1properties");
- } else {
- pattern = "%m%n";
- }
- appenderBuilder.add(newPatternLayout(pattern));
- break;
- }
- case "org.apache.log4j.SimpleLayout": {
- appenderBuilder.add(newPatternLayout("%v1Level - %m%n"));
- break;
- }
- case "org.apache.log4j.TTCCLayout": {
- String pattern = "";
- final String dateFormat = getLog4jAppenderValue(name, "layout.DateFormat", RELATIVE);
- final String timezone = getLog4jAppenderValue(name, "layout.TimeZone", null);
- if (dateFormat != null) {
- if (RELATIVE.equalsIgnoreCase(dateFormat)) {
- pattern += "%r ";
- } else if (!NULL.equalsIgnoreCase(dateFormat)) {
- pattern += "%d{" + dateFormat + "}";
- if (timezone != null) {
- pattern += "{" + timezone + "}";
- }
- pattern += " ";
- }
- }
- if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.ThreadPrinting", TRUE))) {
- pattern += "[%t] ";
- }
- pattern += "%p ";
- if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.CategoryPrefixing", TRUE))) {
- pattern += "%c ";
- }
- if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.ContextPrinting", TRUE))) {
- pattern += "%notEmpty{%ndc }";
- }
- pattern += "- %m%n";
- appenderBuilder.add(newPatternLayout(pattern));
- break;
- }
- case "org.apache.log4j.HTMLLayout": {
- final LayoutComponentBuilder htmlLayout = builder.newLayout("HtmlLayout");
- htmlLayout.addAttribute("title", getLog4jAppenderValue(name, "layout.Title", "Log4J Log Messages"));
- htmlLayout.addAttribute(
- "locationInfo",
- Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.LocationInfo", FALSE)));
- appenderBuilder.add(htmlLayout);
- break;
- }
- case "org.apache.log4j.xml.XMLLayout": {
- final LayoutComponentBuilder xmlLayout = builder.newLayout("Log4j1XmlLayout");
- xmlLayout.addAttribute(
- "locationInfo",
- Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.LocationInfo", FALSE)));
- xmlLayout.addAttribute(
- "properties",
- Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.Properties", FALSE)));
- appenderBuilder.add(xmlLayout);
- break;
- }
- default:
- reportWarning("Unknown layout class: " + layoutClass);
- }
- }
- }
-
- private LayoutComponentBuilder newPatternLayout(final String pattern) {
- final LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout");
- if (pattern != null) {
- layoutBuilder.addAttribute("pattern", pattern);
- }
- return layoutBuilder;
- }
-
- private void buildRootLogger(final String rootLoggerValue) {
- if (rootLoggerValue == null) {
- return;
- }
- final String[] rootLoggerParts = rootLoggerValue.split(COMMA_DELIMITED_RE);
- final String rootLoggerLevel = getLevelString(rootLoggerParts, Level.ERROR.name());
- final RootLoggerComponentBuilder loggerBuilder = builder.newRootLogger(rootLoggerLevel);
- //
- final String[] sortedAppenderNames = Arrays.copyOfRange(rootLoggerParts, 1, rootLoggerParts.length);
- Arrays.sort(sortedAppenderNames);
- for (final String appender : sortedAppenderNames) {
- loggerBuilder.add(builder.newAppenderRef(appender));
- }
- builder.add(loggerBuilder);
- }
-
- private String getLevelString(final String[] loggerParts, final String defaultLevel) {
- return loggerParts.length > 0 ? loggerParts[0] : defaultLevel;
- }
-
- private void buildLoggers(final String prefix) {
- final int preLength = prefix.length();
- for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
- final Object keyObj = entry.getKey();
- if (keyObj != null) {
- final String key = keyObj.toString().trim();
- if (key.startsWith(prefix)) {
- final String name = key.substring(preLength);
- final Object value = entry.getValue();
- if (value != null) {
- // a Level may be followed by a list of Appender refs.
- final String valueStr = value.toString().trim();
- final String[] split = valueStr.split(COMMA_DELIMITED_RE);
- final String level = getLevelString(split, null);
- if (level == null) {
- warn("Level is missing for entry " + entry);
- } else {
- final LoggerComponentBuilder newLogger = builder.newLogger(name, level);
- if (split.length > 1) {
- // Add Appenders to this logger
- final String[] sortedAppenderNames = Arrays.copyOfRange(split, 1, split.length);
- Arrays.sort(sortedAppenderNames);
- for (final String appenderName : sortedAppenderNames) {
- newLogger.add(builder.newAppenderRef(appenderName));
- }
- }
- builder.add(newLogger);
- }
- }
- }
- }
- }
- }
-
- private String getLog4jAppenderValue(final String appenderName, final String attributeName) {
- return getProperty("log4j.appender." + appenderName + "." + attributeName);
- }
-
- private String getProperty(final String key) {
- final String value = properties.getProperty(key);
- final String substVars = OptionConverter.substVars(value, properties);
- return substVars == null ? null : substVars.trim();
- }
-
- private String getProperty(final String key, final String defaultValue) {
- final String value = getProperty(key);
- return value == null ? defaultValue : value;
- }
-
- private String getLog4jAppenderValue(
- final String appenderName, final String attributeName, final String defaultValue) {
- return getProperty("log4j.appender." + appenderName + "." + attributeName, defaultValue);
- }
-
- private String getLog4jValue(final String key) {
- return getProperty("log4j." + key);
- }
-
- private void reportWarning(final String msg) {
- StatusLogger.getLogger().warn("Log4j 1 configuration parser: " + msg);
- }
-}
diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationConverterTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationConverterTest.java
deleted file mode 100644
index 55ac05fbc9..0000000000
--- a/log4j-1.2-api/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationConverterTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.log4j.config;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-
-import java.io.IOException;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.ArrayList;
-import java.util.List;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.xml.sax.SAXException;
-
-@RunWith(Parameterized.class)
-public abstract class AbstractLog4j1ConfigurationConverterTest {
-
- protected static List<Path> getPaths(final String root) throws IOException {
- final List<Path> paths = new ArrayList<>();
- Files.walkFileTree(Paths.get(root), new SimpleFileVisitor<Path>() {
- @Override
- public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
- paths.add(file.toAbsolutePath());
- return FileVisitResult.CONTINUE;
- }
- });
- return paths;
- }
-
- private final Path pathIn;
-
- public AbstractLog4j1ConfigurationConverterTest(final Path path) {
- this.pathIn = path;
- }
-
- @Test
- public void test() throws Exception {
- final Path tempFile = Files.createTempFile("log4j2", ".xml");
- try {
- final Log4j1ConfigurationConverter.CommandLineArguments cla =
- new Log4j1ConfigurationConverter.CommandLineArguments();
- cla.setPathIn(pathIn);
- cla.setPathOut(tempFile);
- Log4j1ConfigurationConverter.run(cla);
- checkWellFormedXml(tempFile);
- checkUnnecessaryEscaping(tempFile);
- } finally {
- Files.deleteIfExists(tempFile);
- }
- }
-
- private void checkUnnecessaryEscaping(final Path tempFile) throws IOException {
- for (String line : Files.readAllLines(tempFile)) {
- assertFalse(line.endsWith("
"));
- }
- }
-
- private void checkWellFormedXml(final Path xmlFilePath)
- throws SAXException, IOException, ParserConfigurationException {
- DocumentBuilderFactory.newInstance()
- .newDocumentBuilder()
- .parse(xmlFilePath.toUri().toString());
- }
-}
diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterHadoopTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterHadoopTest.java
deleted file mode 100644
index 13ef2b68f7..0000000000
--- a/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterHadoopTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.log4j.config;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.List;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class Log4j1ConfigurationConverterHadoopTest extends AbstractLog4j1ConfigurationConverterTest {
-
- @Parameterized.Parameters(name = "{0}")
- public static List<Path> data() throws IOException {
- return getPaths("src/test/resources/config-1.2/hadoop");
- }
-
- public Log4j1ConfigurationConverterHadoopTest(final Path path) {
- super(path);
- }
-}
diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterSparkTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterSparkTest.java
deleted file mode 100644
index 3b2384a782..0000000000
--- a/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterSparkTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.log4j.config;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.List;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class Log4j1ConfigurationConverterSparkTest extends AbstractLog4j1ConfigurationConverterTest {
-
- @Parameterized.Parameters(name = "{0}")
- public static List<Path> data() throws IOException {
- return getPaths("src/test/resources/config-1.2/spark");
- }
-
- public Log4j1ConfigurationConverterSparkTest(final Path path) {
- super(path);
- }
-}
diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationFactoryTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationFactoryTest.java
deleted file mode 100644
index 0e16e26b62..0000000000
--- a/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationFactoryTest.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.log4j.config;
-
-import static org.junit.Assert.fail;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.net.URISyntaxException;
-import java.net.URL;
-import org.apache.log4j.layout.Log4j1XmlLayout;
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.core.Appender;
-import org.apache.logging.log4j.core.Filter;
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LoggerContext;
-import org.apache.logging.log4j.core.appender.ConsoleAppender;
-import org.apache.logging.log4j.core.appender.ConsoleAppender.Target;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.LoggerConfig;
-import org.apache.logging.log4j.core.filter.ThresholdFilter;
-import org.apache.logging.log4j.core.layout.PatternLayout;
-import org.junit.Test;
-
-public class Log4j1ConfigurationFactoryTest extends AbstractLog4j1ConfigurationTest {
-
- private static final String SUFFIX = ".properties";
-
- @Override
- protected Configuration getConfiguration(final String configResource) throws URISyntaxException {
- final URL configLocation = ClassLoader.getSystemResource(configResource + SUFFIX);
- assertNotNull(configLocation, configResource);
- final Configuration configuration =
- new Log4j1ConfigurationFactory().getConfiguration(null, "test", configLocation.toURI());
- assertNotNull(configuration);
- return configuration;
- }
-
- private Layout testConsole(final String configResource) throws Exception {
- final Configuration configuration = getConfiguration(configResource);
- final String name = "Console";
- final ConsoleAppender appender = configuration.getAppender(name);
- assertNotNull(
- appender, "Missing appender '" + name + "' in configuration " + configResource + " → " + configuration);
- assertEquals(Target.SYSTEM_ERR, appender.getTarget());
- //
- final LoggerConfig loggerConfig = configuration.getLoggerConfig("com.example.foo");
- assertNotNull(loggerConfig);
- assertEquals(Level.DEBUG, loggerConfig.getLevel());
- configuration.start();
- configuration.stop();
- return appender.getLayout();
- }
-
- @Override
- @Test
- public void testConsoleEnhancedPatternLayout() throws Exception {
- super.testConsoleEnhancedPatternLayout();
- }
-
- @Override
- @Test
- public void testConsoleHtmlLayout() throws Exception {
- super.testConsoleHtmlLayout();
- }
-
- @Test
- public void testConsolePatternLayout() throws Exception {
- super.testConsolePatternLayout();
- }
-
- @Override
- @Test
- public void testConsoleSimpleLayout() throws Exception {
- super.testConsoleSimpleLayout();
- }
-
- @Override
- @Test
- public void testConsoleTtccLayout() throws Exception {
- super.testConsoleTtccLayout();
- }
-
- @Test
- public void testConsoleXmlLayout() throws Exception {
- final Log4j1XmlLayout layout = (Log4j1XmlLayout) testConsole("config-1.2/log4j-console-XmlLayout");
- assertTrue(layout.isLocationInfo());
- assertFalse(layout.isProperties());
- }
-
- @Override
- @Test
- public void testFileSimpleLayout() throws Exception {
- super.testFileSimpleLayout();
- }
-
- @Override
- @Test
- public void testNullAppender() throws Exception {
- super.testNullAppender();
- }
-
- @Override
- @Test
- public void testRollingFileAppender() throws Exception {
- super.testRollingFileAppender();
- }
-
- @Override
- @Test
- public void testDailyRollingFileAppender() throws Exception {
- super.testDailyRollingFileAppender();
- }
-
- @Test
- public void testRollingFileAppenderWithProperties() throws Exception {
- super.testRollingFileAppenderWithProperties();
- }
-
- @Override
- @Test
- public void testSystemProperties1() throws Exception {
- super.testSystemProperties1();
- }
-
- @Override
- @Test
- public void testSystemProperties2() throws Exception {
- super.testSystemProperties2();
- }
-
- @Override
- @Test
- public void testConsoleCapitalization() throws Exception {
- super.testConsoleCapitalization();
- }
-
- @Override
- @Test
- public void testDefaultValues() throws Exception {
- super.testDefaultValues();
- }
-
- @Test
- public void testUntrimmedValues() throws Exception {
- try {
- final Configuration config = getConfiguration("config-1.2/log4j-untrimmed");
- final LoggerConfig rootLogger = config.getRootLogger();
- assertEquals(Level.DEBUG, rootLogger.getLevel());
- final Appender appender = config.getAppender("Console");
- assertTrue(appender instanceof ConsoleAppender);
- final Layout layout = appender.getLayout();
- assertTrue(layout instanceof PatternLayout);
- assertEquals("%v1Level - %m%n", ((PatternLayout) layout).getConversionPattern());
- // No filter support
- config.start();
- config.stop();
- } catch (NoClassDefFoundError e) {
- fail(e.getMessage());
- }
- }
-
- @Test
- public void testGlobalThreshold() throws Exception {
- try (final LoggerContext ctx = configure("config-1.2/log4j-global-threshold")) {
- final Configuration config = ctx.getConfiguration();
- final Filter filter = config.getFilter();
- assertTrue(filter instanceof ThresholdFilter);
- final ThresholdFilter thresholdFilter = (ThresholdFilter) filter;
- assertEquals(Level.INFO, thresholdFilter.getLevel());
- assertEquals(Filter.Result.NEUTRAL, thresholdFilter.getOnMatch());
- assertEquals(Filter.Result.DENY, thresholdFilter.getOnMismatch());
- }
- }
-}
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-auth-examples/src/main/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-auth-examples/src/main/resources/log4j.properties
deleted file mode 100644
index 1fbe7b51a3..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-auth-examples/src/main/resources/log4j.properties
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-log4j.appender.test=org.apache.log4j.ConsoleAppender
-log4j.appender.test.Target=System.out
-log4j.appender.test.layout=org.apache.log4j.PatternLayout
-log4j.appender.test.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
-
-log4j.logger.org.apache.hadoop.security.authentication=DEBUG, test
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/main/conf/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/main/conf/log4j.properties
deleted file mode 100644
index fc525df71b..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/main/conf/log4j.properties
+++ /dev/null
@@ -1,324 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Define some default values that can be overridden by system properties
-hadoop.root.logger=INFO,console
-hadoop.log.dir=target
-hadoop.log.file=hadoop.log
-
-# Define the root logger to the system property "hadoop.root.logger".
-log4j.rootLogger=${hadoop.root.logger}, EventCounter
-
-# Logging Threshold
-log4j.threshold=ALL
-
-# Null Appender
-log4j.appender.NullAppender=org.apache.log4j.varia.NullAppender
-
-#
-# Rolling File Appender - cap space usage at 5gb.
-#
-hadoop.log.maxfilesize=256MB
-hadoop.log.maxbackupindex=20
-log4j.appender.RFA=org.apache.log4j.RollingFileAppender
-log4j.appender.RFA.File=${hadoop.log.dir}/${hadoop.log.file}
-
-log4j.appender.RFA.MaxFileSize=${hadoop.log.maxfilesize}
-log4j.appender.RFA.MaxBackupIndex=${hadoop.log.maxbackupindex}
-
-log4j.appender.RFA.layout=org.apache.log4j.PatternLayout
-
-# Pattern format: Date LogLevel LoggerName LogMessage
-log4j.appender.RFA.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n
-# Debugging Pattern format
-#log4j.appender.RFA.layout.ConversionPattern=%d{ISO8601} %-5p %c{2} (%F:%M(%L)) - %m%n
-
-
-#
-# Daily Rolling File Appender
-#
-
-log4j.appender.DRFA=org.apache.log4j.DailyRollingFileAppender
-log4j.appender.DRFA.File=${hadoop.log.dir}/${hadoop.log.file}
-
-# Rollover at midnight
-log4j.appender.DRFA.DatePattern=.yyyy-MM-dd
-
-log4j.appender.DRFA.layout=org.apache.log4j.PatternLayout
-
-# Pattern format: Date LogLevel LoggerName LogMessage
-log4j.appender.DRFA.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n
-# Debugging Pattern format
-#log4j.appender.DRFA.layout.ConversionPattern=%d{ISO8601} %-5p %c{2} (%F:%M(%L)) - %m%n
-
-
-#
-# console
-# Add "console" to rootlogger above if you want to use this
-#
-
-log4j.appender.console=org.apache.log4j.ConsoleAppender
-log4j.appender.console.target=System.err
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
-log4j.appender.console.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n
-
-#
-# TaskLog Appender
-#
-
-#Default values
-hadoop.tasklog.taskid=null
-hadoop.tasklog.iscleanup=false
-hadoop.tasklog.noKeepSplits=4
-hadoop.tasklog.totalLogFileSize=100
-hadoop.tasklog.purgeLogSplits=true
-hadoop.tasklog.logsRetainHours=12
-
-log4j.appender.TLA=org.apache.hadoop.mapred.TaskLogAppender
-log4j.appender.TLA.taskId=${hadoop.tasklog.taskid}
-log4j.appender.TLA.isCleanup=${hadoop.tasklog.iscleanup}
-log4j.appender.TLA.totalLogFileSize=${hadoop.tasklog.totalLogFileSize}
-
-log4j.appender.TLA.layout=org.apache.log4j.PatternLayout
-log4j.appender.TLA.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n
-
-#
-# HDFS block state change log from block manager
-#
-# Uncomment the following to log normal block state change
-# messages from BlockManager in NameNode.
-#log4j.logger.BlockStateChange=DEBUG
-
-#
-#Security appender
-#
-hadoop.security.logger=INFO,NullAppender
-hadoop.security.log.maxfilesize=256MB
-hadoop.security.log.maxbackupindex=20
-log4j.category.SecurityLogger=${hadoop.security.logger}
-hadoop.security.log.file=SecurityAuth-${user.name}.audit
-log4j.appender.RFAS=org.apache.log4j.RollingFileAppender
-log4j.appender.RFAS.File=${hadoop.log.dir}/${hadoop.security.log.file}
-log4j.appender.RFAS.layout=org.apache.log4j.PatternLayout
-log4j.appender.RFAS.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n
-log4j.appender.RFAS.MaxFileSize=${hadoop.security.log.maxfilesize}
-log4j.appender.RFAS.MaxBackupIndex=${hadoop.security.log.maxbackupindex}
-
-#
-# Daily Rolling Security appender
-#
-log4j.appender.DRFAS=org.apache.log4j.DailyRollingFileAppender
-log4j.appender.DRFAS.File=${hadoop.log.dir}/${hadoop.security.log.file}
-log4j.appender.DRFAS.layout=org.apache.log4j.PatternLayout
-log4j.appender.DRFAS.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n
-log4j.appender.DRFAS.DatePattern=.yyyy-MM-dd
-
-#
-# hadoop configuration logging
-#
-
-# Uncomment the following line to turn off configuration deprecation warnings.
-# log4j.logger.org.apache.hadoop.conf.Configuration.deprecation=WARN
-
-#
-# hdfs audit logging
-#
-hdfs.audit.logger=INFO,NullAppender
-hdfs.audit.log.maxfilesize=256MB
-hdfs.audit.log.maxbackupindex=20
-log4j.logger.org.apache.hadoop.hdfs.server.namenode.FSNamesystem.audit=${hdfs.audit.logger}
-log4j.additivity.org.apache.hadoop.hdfs.server.namenode.FSNamesystem.audit=false
-log4j.appender.RFAAUDIT=org.apache.log4j.RollingFileAppender
-log4j.appender.RFAAUDIT.File=${hadoop.log.dir}/hdfs-audit.log
-log4j.appender.RFAAUDIT.layout=org.apache.log4j.PatternLayout
-log4j.appender.RFAAUDIT.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n
-log4j.appender.RFAAUDIT.MaxFileSize=${hdfs.audit.log.maxfilesize}
-log4j.appender.RFAAUDIT.MaxBackupIndex=${hdfs.audit.log.maxbackupindex}
-
-#
-# NameNode metrics logging.
-# The default is to retain two namenode-metrics.log files up to 64MB each.
-#
-namenode.metrics.logger=INFO,NullAppender
-log4j.logger.NameNodeMetricsLog=${namenode.metrics.logger}
-log4j.additivity.NameNodeMetricsLog=false
-log4j.appender.NNMETRICSRFA=org.apache.log4j.RollingFileAppender
-log4j.appender.NNMETRICSRFA.File=${hadoop.log.dir}/namenode-metrics.log
-log4j.appender.NNMETRICSRFA.layout=org.apache.log4j.PatternLayout
-log4j.appender.NNMETRICSRFA.layout.ConversionPattern=%d{ISO8601} %m%n
-log4j.appender.NNMETRICSRFA.MaxBackupIndex=1
-log4j.appender.NNMETRICSRFA.MaxFileSize=64MB
-
-#
-# DataNode metrics logging.
-# The default is to retain two datanode-metrics.log files up to 64MB each.
-#
-datanode.metrics.logger=INFO,NullAppender
-log4j.logger.DataNodeMetricsLog=${datanode.metrics.logger}
-log4j.additivity.DataNodeMetricsLog=false
-log4j.appender.DNMETRICSRFA=org.apache.log4j.RollingFileAppender
-log4j.appender.DNMETRICSRFA.File=${hadoop.log.dir}/datanode-metrics.log
-log4j.appender.DNMETRICSRFA.layout=org.apache.log4j.PatternLayout
-log4j.appender.DNMETRICSRFA.layout.ConversionPattern=%d{ISO8601} %m%n
-log4j.appender.DNMETRICSRFA.MaxBackupIndex=1
-log4j.appender.DNMETRICSRFA.MaxFileSize=64MB
-
-#
-# mapred audit logging
-#
-mapred.audit.logger=INFO,NullAppender
-mapred.audit.log.maxfilesize=256MB
-mapred.audit.log.maxbackupindex=20
-log4j.logger.org.apache.hadoop.mapred.AuditLogger=${mapred.audit.logger}
-log4j.additivity.org.apache.hadoop.mapred.AuditLogger=false
-log4j.appender.MRAUDIT=org.apache.log4j.RollingFileAppender
-log4j.appender.MRAUDIT.File=${hadoop.log.dir}/mapred-audit.log
-log4j.appender.MRAUDIT.layout=org.apache.log4j.PatternLayout
-log4j.appender.MRAUDIT.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n
-log4j.appender.MRAUDIT.MaxFileSize=${mapred.audit.log.maxfilesize}
-log4j.appender.MRAUDIT.MaxBackupIndex=${mapred.audit.log.maxbackupindex}
-
-# Custom Logging levels
-
-#log4j.logger.org.apache.hadoop.mapred.JobTracker=DEBUG
-#log4j.logger.org.apache.hadoop.mapred.TaskTracker=DEBUG
-#log4j.logger.org.apache.hadoop.hdfs.server.namenode.FSNamesystem.audit=DEBUG
-
-# Jets3t library
-log4j.logger.org.jets3t.service.impl.rest.httpclient.RestS3Service=ERROR
-
-# AWS SDK & S3A FileSystem
-log4j.logger.com.amazonaws=ERROR
-log4j.logger.com.amazonaws.http.AmazonHttpClient=ERROR
-log4j.logger.org.apache.hadoop.fs.s3a.S3AFileSystem=WARN
-
-#
-# Event Counter Appender
-# Sends counts of logging messages at different severity levels to Hadoop Metrics.
-#
-log4j.appender.EventCounter=org.apache.hadoop.log.metrics.EventCounter
-
-#
-# Job Summary Appender
-#
-# Use following logger to send summary to separate file defined by
-# hadoop.mapreduce.jobsummary.log.file :
-# hadoop.mapreduce.jobsummary.logger=INFO,JSA
-#
-hadoop.mapreduce.jobsummary.logger=${hadoop.root.logger}
-hadoop.mapreduce.jobsummary.log.file=hadoop-mapreduce.jobsummary.log
-hadoop.mapreduce.jobsummary.log.maxfilesize=256MB
-hadoop.mapreduce.jobsummary.log.maxbackupindex=20
-log4j.appender.JSA=org.apache.log4j.RollingFileAppender
-log4j.appender.JSA.File=${hadoop.log.dir}/${hadoop.mapreduce.jobsummary.log.file}
-log4j.appender.JSA.MaxFileSize=${hadoop.mapreduce.jobsummary.log.maxfilesize}
-log4j.appender.JSA.MaxBackupIndex=${hadoop.mapreduce.jobsummary.log.maxbackupindex}
-log4j.appender.JSA.layout=org.apache.log4j.PatternLayout
-log4j.appender.JSA.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n
-log4j.logger.org.apache.hadoop.mapred.JobInProgress$JobSummary=${hadoop.mapreduce.jobsummary.logger}
-log4j.additivity.org.apache.hadoop.mapred.JobInProgress$JobSummary=false
-
-#
-# shuffle connection log from shuffleHandler
-# Uncomment the following line to enable logging of shuffle connections
-# log4j.logger.org.apache.hadoop.mapred.ShuffleHandler.audit=DEBUG
-
-#
-# Yarn ResourceManager Application Summary Log
-#
-# Set the ResourceManager summary log filename
-yarn.server.resourcemanager.appsummary.log.file=rm-appsummary.log
-# Set the ResourceManager summary log level and appender
-yarn.server.resourcemanager.appsummary.logger=${hadoop.root.logger}
-#yarn.server.resourcemanager.appsummary.logger=INFO,RMSUMMARY
-
-# To enable AppSummaryLogging for the RM,
-# set yarn.server.resourcemanager.appsummary.logger to
-# <LEVEL>,RMSUMMARY in hadoop-env.sh
-
-# Appender for ResourceManager Application Summary Log
-# Requires the following properties to be set
-# - hadoop.log.dir (Hadoop Log directory)
-# - yarn.server.resourcemanager.appsummary.log.file (resource manager app summary log filename)
-# - yarn.server.resourcemanager.appsummary.logger (resource manager app summary log level and appender)
-
-log4j.logger.org.apache.hadoop.yarn.server.resourcemanager.RMAppManager$ApplicationSummary=${yarn.server.resourcemanager.appsummary.logger}
-log4j.additivity.org.apache.hadoop.yarn.server.resourcemanager.RMAppManager$ApplicationSummary=false
-log4j.appender.RMSUMMARY=org.apache.log4j.RollingFileAppender
-log4j.appender.RMSUMMARY.File=${hadoop.log.dir}/${yarn.server.resourcemanager.appsummary.log.file}
-log4j.appender.RMSUMMARY.MaxFileSize=256MB
-log4j.appender.RMSUMMARY.MaxBackupIndex=20
-log4j.appender.RMSUMMARY.layout=org.apache.log4j.PatternLayout
-log4j.appender.RMSUMMARY.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n
-
-# HS audit log configs
-#mapreduce.hs.audit.logger=INFO,HSAUDIT
-#log4j.logger.org.apache.hadoop.mapreduce.v2.hs.HSAuditLogger=${mapreduce.hs.audit.logger}
-#log4j.additivity.org.apache.hadoop.mapreduce.v2.hs.HSAuditLogger=false
-#log4j.appender.HSAUDIT=org.apache.log4j.DailyRollingFileAppender
-#log4j.appender.HSAUDIT.File=${hadoop.log.dir}/hs-audit.log
-#log4j.appender.HSAUDIT.layout=org.apache.log4j.PatternLayout
-#log4j.appender.HSAUDIT.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n
-#log4j.appender.HSAUDIT.DatePattern=.yyyy-MM-dd
-
-# Http Server Request Logs
-#log4j.logger.http.requests.namenode=INFO,namenoderequestlog
-#log4j.appender.namenoderequestlog=org.apache.hadoop.http.HttpRequestLogAppender
-#log4j.appender.namenoderequestlog.Filename=${hadoop.log.dir}/jetty-namenode-yyyy_mm_dd.log
-#log4j.appender.namenoderequestlog.RetainDays=3
-
-#log4j.logger.http.requests.datanode=INFO,datanoderequestlog
-#log4j.appender.datanoderequestlog=org.apache.hadoop.http.HttpRequestLogAppender
-#log4j.appender.datanoderequestlog.Filename=${hadoop.log.dir}/jetty-datanode-yyyy_mm_dd.log
-#log4j.appender.datanoderequestlog.RetainDays=3
-
-#log4j.logger.http.requests.resourcemanager=INFO,resourcemanagerrequestlog
-#log4j.appender.resourcemanagerrequestlog=org.apache.hadoop.http.HttpRequestLogAppender
-#log4j.appender.resourcemanagerrequestlog.Filename=${hadoop.log.dir}/jetty-resourcemanager-yyyy_mm_dd.log
-#log4j.appender.resourcemanagerrequestlog.RetainDays=3
-
-#log4j.logger.http.requests.jobhistory=INFO,jobhistoryrequestlog
-#log4j.appender.jobhistoryrequestlog=org.apache.hadoop.http.HttpRequestLogAppender
-#log4j.appender.jobhistoryrequestlog.Filename=${hadoop.log.dir}/jetty-jobhistory-yyyy_mm_dd.log
-#log4j.appender.jobhistoryrequestlog.RetainDays=3
-
-#log4j.logger.http.requests.nodemanager=INFO,nodemanagerrequestlog
-#log4j.appender.nodemanagerrequestlog=org.apache.hadoop.http.HttpRequestLogAppender
-#log4j.appender.nodemanagerrequestlog.Filename=${hadoop.log.dir}/jetty-nodemanager-yyyy_mm_dd.log
-#log4j.appender.nodemanagerrequestlog.RetainDays=3
-
-
-# WebHdfs request log on datanodes
-# Specify -Ddatanode.webhdfs.logger=INFO,HTTPDRFA on datanode startup to
-# direct the log to a separate file.
-#datanode.webhdfs.logger=INFO,console
-#log4j.logger.datanode.webhdfs=${datanode.webhdfs.logger}
-#log4j.appender.HTTPDRFA=org.apache.log4j.DailyRollingFileAppender
-#log4j.appender.HTTPDRFA.File=${hadoop.log.dir}/hadoop-datanode-webhdfs.log
-#log4j.appender.HTTPDRFA.layout=org.apache.log4j.PatternLayout
-#log4j.appender.HTTPDRFA.layout.ConversionPattern=%d{ISO8601} %m%n
-#log4j.appender.HTTPDRFA.DatePattern=.yyyy-MM-dd
-
-
-# Appender for viewing information for errors and warnings
-yarn.ewma.cleanupInterval=300
-yarn.ewma.messageAgeLimitSeconds=86400
-yarn.ewma.maxUniqueMessages=250
-log4j.appender.EWMA=org.apache.hadoop.yarn.util.Log4jWarningErrorMetricsAppender
-log4j.appender.EWMA.cleanupInterval=${yarn.ewma.cleanupInterval}
-log4j.appender.EWMA.messageAgeLimitSeconds=${yarn.ewma.messageAgeLimitSeconds}
-log4j.appender.EWMA.maxUniqueMessages=${yarn.ewma.maxUniqueMessages}
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/test/resources/log4j.properties
deleted file mode 100644
index a40de161bd..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-kms/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-kms/src/test/resources/log4j.properties
deleted file mode 100644
index d57e5846ee..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-kms/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,30 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# STDOUT Appender
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.Target=System.out
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p %c{1} - %m%n
-
-log4j.rootLogger=INFO, stdout
-log4j.logger.org.apache.hadoop.conf=ERROR
-log4j.logger.org.apache.hadoop.crytpo.key.kms.server=ALL
-log4j.logger.com.sun.jersey.server.wadl.generators.WadlGeneratorJAXBGrammarGenerator=OFF
-log4j.logger.org.apache.hadoop.security=OFF
-log4j.logger.org.apache.directory.server.core=OFF
-log4j.logger.org.apache.hadoop.util.NativeCodeLoader=OFF
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-minikdc/src/main/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-minikdc/src/main/resources/log4j.properties
deleted file mode 100644
index 29f00dda73..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-minikdc/src/main/resources/log4j.properties
+++ /dev/null
@@ -1,30 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# STDOUT Appender
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.Target=System.err
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p %c{1} - %m%n
-
-log4j.rootLogger=INFO, stdout
-
-# Switching off most of Apache DS logqing which is QUITE verbose
-log4j.logger.org.apache.directory=OFF
-log4j.logger.org.apache.directory.server.kerberos=INFO, stdout
-log4j.additivity.org.apache.directory=false
-log4j.logger.net.sf.ehcache=INFO, stdout
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-nfs/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-nfs/src/test/resources/log4j.properties
deleted file mode 100644
index a40de161bd..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-nfs/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs-client/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs-client/src/test/resources/log4j.properties
deleted file mode 100644
index 6c2fa4cb33..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs-client/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,50 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c{2} (%F:%M(%L)) - %m%n
-
-#
-# NameNode metrics logging.
-# The default is to retain two namenode-metrics.log files up to 64MB each.
-#
-log4j.logger.NameNodeMetricsLog=INFO,NNMETRICSRFA
-log4j.additivity.NameNodeMetricsLog=false
-log4j.appender.NNMETRICSRFA=org.apache.log4j.RollingFileAppender
-log4j.appender.NNMETRICSRFA.File=${hadoop.log.dir}/namenode-metrics.log
-log4j.appender.NNMETRICSRFA.layout=org.apache.log4j.PatternLayout
-log4j.appender.NNMETRICSRFA.layout.ConversionPattern=%d{ISO8601} %m%n
-log4j.appender.NNMETRICSRFA.MaxBackupIndex=1
-log4j.appender.NNMETRICSRFA.MaxFileSize=64MB
-
-#
-# DataNode metrics logging.
-# The default is to retain two datanode-metrics.log files up to 64MB each.
-#
-log4j.logger.DataNodeMetricsLog=INFO,DNMETRICSRFA
-log4j.additivity.DataNodeMetricsLog=false
-log4j.appender.DNMETRICSRFA=org.apache.log4j.RollingFileAppender
-log4j.appender.DNMETRICSRFA.File=${hadoop.log.dir}/datanode-metrics.log
-log4j.appender.DNMETRICSRFA.layout=org.apache.log4j.PatternLayout
-log4j.appender.DNMETRICSRFA.layout.ConversionPattern=%d{ISO8601} %m%n
-log4j.appender.DNMETRICSRFA.MaxBackupIndex=1
-log4j.appender.DNMETRICSRFA.MaxFileSize=64MB
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/contrib/bkjournal/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/contrib/bkjournal/src/test/resources/log4j.properties
deleted file mode 100644
index 58db1c8842..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/contrib/bkjournal/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-#
-# Bookkeeper Journal Logging Configuration
-#
-
-# Format is "<default threshold> (, <appender>)+
-
-# DEFAULT: console appender only
-log4j.rootLogger=DEBUG, CONSOLE
-
-# Example with rolling log file
-#log4j.rootLogger=DEBUG, CONSOLE, ROLLINGFILE
-
-# Example with rolling log file and tracing
-#log4j.rootLogger=TRACE, CONSOLE, ROLLINGFILE, TRACEFILE
-
-#
-# Log INFO level and above messages to the console
-#
-log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
-log4j.appender.CONSOLE.Threshold=INFO
-log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
-log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} - %-5p - [%t:%C{1}@%L] - %m%n
-
-#
-# Add ROLLINGFILE to rootLogger to get log file output
-# Log DEBUG level and above messages to a log file
-log4j.appender.ROLLINGFILE=org.apache.log4j.DailyRollingFileAppender
-log4j.appender.ROLLINGFILE.Threshold=DEBUG
-log4j.appender.ROLLINGFILE.File=hdfs-namenode.log
-log4j.appender.ROLLINGFILE.layout=org.apache.log4j.PatternLayout
-log4j.appender.ROLLINGFILE.layout.ConversionPattern=%d{ISO8601} - %-5p - [%t:%C{1}@%L] - %m%n
-
-# Max log file size of 10MB
-log4j.appender.ROLLINGFILE.MaxFileSize=10MB
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/log4j.properties
deleted file mode 100644
index 6c2fa4cb33..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,50 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c{2} (%F:%M(%L)) - %m%n
-
-#
-# NameNode metrics logging.
-# The default is to retain two namenode-metrics.log files up to 64MB each.
-#
-log4j.logger.NameNodeMetricsLog=INFO,NNMETRICSRFA
-log4j.additivity.NameNodeMetricsLog=false
-log4j.appender.NNMETRICSRFA=org.apache.log4j.RollingFileAppender
-log4j.appender.NNMETRICSRFA.File=${hadoop.log.dir}/namenode-metrics.log
-log4j.appender.NNMETRICSRFA.layout=org.apache.log4j.PatternLayout
-log4j.appender.NNMETRICSRFA.layout.ConversionPattern=%d{ISO8601} %m%n
-log4j.appender.NNMETRICSRFA.MaxBackupIndex=1
-log4j.appender.NNMETRICSRFA.MaxFileSize=64MB
-
-#
-# DataNode metrics logging.
-# The default is to retain two datanode-metrics.log files up to 64MB each.
-#
-log4j.logger.DataNodeMetricsLog=INFO,DNMETRICSRFA
-log4j.additivity.DataNodeMetricsLog=false
-log4j.appender.DNMETRICSRFA=org.apache.log4j.RollingFileAppender
-log4j.appender.DNMETRICSRFA.File=${hadoop.log.dir}/datanode-metrics.log
-log4j.appender.DNMETRICSRFA.layout=org.apache.log4j.PatternLayout
-log4j.appender.DNMETRICSRFA.layout.ConversionPattern=%d{ISO8601} %m%n
-log4j.appender.DNMETRICSRFA.MaxBackupIndex=1
-log4j.appender.DNMETRICSRFA.MaxFileSize=64MB
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/resources/log4j.properties
deleted file mode 100644
index b7ca691429..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-common/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-common/src/test/resources/log4j.properties
deleted file mode 100644
index b7ca691429..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-common/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/resources/log4j.properties
deleted file mode 100644
index b7ca691429..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/resources/log4j.properties
deleted file mode 100644
index b7ca691429..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/resources/log4j.properties
deleted file mode 100644
index b7ca691429..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-nativetask/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-nativetask/src/test/resources/log4j.properties
deleted file mode 100644
index b7ca691429..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-nativetask/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/test/resources/log4j.properties
deleted file mode 100644
index b7ca691429..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-aws/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-aws/src/test/resources/log4j.properties
deleted file mode 100644
index 396985af1e..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-aws/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c{2} (%F:%M(%L)) - %m%n
-
-log4j.logger.org.apache.hadoop.util.NativeCodeLoader=ERROR
-
-# for debugging low level S3a operations, uncomment this line
-# log4j.logger.org.apache.hadoop.fs.s3a=DEBUG
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-azure/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-azure/src/test/resources/log4j.properties
deleted file mode 100644
index 3bd94a6e2c..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-azure/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=INFO,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t]: %c{2} (%F:%M(%L)) - %m%n
-
-log4j.logger.org.apache.hadoop.fs.azure.AzureFileSystemThreadPoolExecutor=DEBUG
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-openstack/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-openstack/src/test/resources/log4j.properties
deleted file mode 100644
index 83dd3f2fd9..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-openstack/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-log4j.rootLogger=INFO,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.target=System.out
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c{2} (%F:%M(%L)) - %m%n
-#log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c %x - %m%n"
-#log4j.logger.org.apache.hadoop.fs.swift=DEBUG
-
-#crank back on warnings about -1 content length GETs
-log4j.logger.org.apache.commons.httpclient.HttpMethodBase=ERROR
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-sls/src/main/sample-conf/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-sls/src/main/sample-conf/log4j.properties
deleted file mode 100644
index 3672426f7e..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-sls/src/main/sample-conf/log4j.properties
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-log4j.appender.test=org.apache.log4j.ConsoleAppender
-log4j.appender.test.Target=System.out
-log4j.appender.test.layout=org.apache.log4j.PatternLayout
-log4j.appender.test.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
-
-log4j.logger=NONE, test
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/test/resources/log4j.properties
deleted file mode 100644
index b7ca691429..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/test/resources/log4j.properties
deleted file mode 100644
index 898a262af8..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=INFO,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/resources/log4j.properties
deleted file mode 100644
index b7ca691429..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-registry/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-registry/src/test/resources/log4j.properties
deleted file mode 100644
index cd739b65be..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-registry/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,64 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=INFO,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c{2} (%F:%M(%L)) - %m%n
-
-log4j.appender.subprocess=org.apache.log4j.ConsoleAppender
-log4j.appender.subprocess.layout=org.apache.log4j.PatternLayout
-log4j.appender.subprocess.layout.ConversionPattern=[%c{1}]: %m%n
-
-# packages under test
-log4j.logger.org.apache.hadoop.yarn.registry=DEBUG
-log4j.logger.org.apache.hadoop.service=DEBUG
-
-log4j.logger.org.apache.hadoop.security.UserGroupInformation=DEBUG
-
-
-#crank back on some noise
-log4j.logger.org.apache.hadoop.util.NativeCodeLoader=ERROR
-log4j.logger.org.apache.hadoop.hdfs.server.datanode.BlockPoolSliceScanner=WARN
-log4j.logger.org.apache.hadoop.hdfs.server.blockmanagement=WARN
-log4j.logger.org.apache.hadoop.hdfs.server.namenode.FSNamesystem.audit=WARN
-log4j.logger.org.apache.hadoop.hdfs=WARN
-
-
-log4j.logger.org.apache.hadoop.yarn.server.nodemanager.containermanager.monitor=WARN
-log4j.logger.org.apache.hadoop.yarn.server.nodemanager.NodeStatusUpdaterImpl=WARN
-log4j.logger.org.apache.zookeeper=INFO
-log4j.logger.org.apache.zookeeper.ClientCnxn=DEBUG
-
-log4j.logger.org.apache.hadoop.yarn.server.resourcemanager.security=WARN
-log4j.logger.org.apache.hadoop.metrics2=ERROR
-log4j.logger.org.apache.hadoop.util.HostsFileReader=WARN
-log4j.logger.org.apache.hadoop.yarn.event.AsyncDispatcher=WARN
-log4j.logger.org.apache.hadoop.security.token.delegation=WARN
-log4j.logger.org.apache.hadoop.yarn.util.AbstractLivelinessMonitor=WARN
-log4j.logger.org.apache.hadoop.yarn.server.nodemanager.security=WARN
-log4j.logger.org.apache.hadoop.yarn.server.resourcemanager.RMNMInfo=WARN
-
-# curator noise
-log4j.logger.org.apache.curator.framework.imps=WARN
-log4j.logger.org.apache.curator.framework.state.ConnectionStateManager=ERROR
-
-log4j.logger.org.apache.directory.api.ldap=ERROR
-log4j.logger.org.apache.directory.server=ERROR
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/test/resources/log4j.properties
deleted file mode 100644
index b7ca691429..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/resources/log4j.properties
deleted file mode 100644
index b7ca691429..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/resources/log4j.properties
deleted file mode 100644
index b7ca691429..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-tests/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-tests/src/test/resources/log4j.properties
deleted file mode 100644
index 898a262af8..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-tests/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=INFO,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase-tests/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase-tests/src/test/resources/log4j.properties
deleted file mode 100644
index b7ca691429..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase-tests/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/resources/log4j.properties
deleted file mode 100644
index b7ca691429..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# log4j configuration used during build and unit tests
-
-log4j.rootLogger=info,stdout
-log4j.threshold=ALL
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/R/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/R/log4j.properties
deleted file mode 100644
index 6365f63b8b..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/R/log4j.properties
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file target/unit-tests.log
-log4j.rootCategory=INFO, file
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=R/target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Ignore messages below warning level from Jetty, because it's a bit verbose
-log4j.logger.org.eclipse.jetty=WARN
-org.eclipse.jetty.LEVEL=WARN
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/common/network-common/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/common/network-common/src/test/resources/log4j.properties
deleted file mode 100644
index e9feeb64f0..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/common/network-common/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file target/unit-tests.log
-log4j.rootCategory=DEBUG, file
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Silence verbose logs from 3rd-party libraries.
-log4j.logger.io.netty=INFO
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/common/network-shuffle/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/common/network-shuffle/src/test/resources/log4j.properties
deleted file mode 100644
index e8abecea80..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/common/network-shuffle/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file target/unit-tests.log
-log4j.rootCategory=DEBUG, file
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/core/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/core/src/test/resources/log4j.properties
deleted file mode 100644
index d919270cf2..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/core/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file target/unit-tests.log
-test.appender=file
-log4j.rootCategory=INFO, ${test.appender}
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Tests that launch java subprocesses can set the "test.appender" system property to
-# "console" to avoid having the child process's logs overwrite the unit test's
-# log file.
-log4j.appender.console=org.apache.log4j.ConsoleAppender
-log4j.appender.console.target=System.err
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
-log4j.appender.console.layout.ConversionPattern=%t: %m%n
-
-# Ignore messages below warning level from Jetty, because it's a bit verbose
-log4j.logger.org.spark_project.jetty=WARN
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/flume-sink/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/flume-sink/src/test/resources/log4j.properties
deleted file mode 100644
index bad5156b0f..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/flume-sink/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file streaming/target/unit-tests.log
-log4j.rootCategory=INFO, file
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Ignore messages below warning level from Jetty, because it's a bit verbose
-log4j.logger.org.spark_project.jetty=WARN
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/flume/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/flume/src/test/resources/log4j.properties
deleted file mode 100644
index 001d0482ac..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/flume/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file target/unit-tests.log
-log4j.rootCategory=INFO, file
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Ignore messages below warning level from Jetty, because it's a bit verbose
-log4j.logger.org.spark_project.jetty=WARN
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/java8-tests/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/java8-tests/src/test/resources/log4j.properties
deleted file mode 100644
index 001d0482ac..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/java8-tests/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file target/unit-tests.log
-log4j.rootCategory=INFO, file
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Ignore messages below warning level from Jetty, because it's a bit verbose
-log4j.logger.org.spark_project.jetty=WARN
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kafka-0-10/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kafka-0-10/src/test/resources/log4j.properties
deleted file mode 100644
index 49618ae7b5..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kafka-0-10/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file target/unit-tests.log
-log4j.rootCategory=INFO, file
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Ignore messages below warning level from Jetty, because it's a bit verbose
-log4j.logger.org.spark-project.jetty=WARN
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kafka-0-8/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kafka-0-8/src/test/resources/log4j.properties
deleted file mode 100644
index 001d0482ac..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kafka-0-8/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file target/unit-tests.log
-log4j.rootCategory=INFO, file
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Ignore messages below warning level from Jetty, because it's a bit verbose
-log4j.logger.org.spark_project.jetty=WARN
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kinesis-asl/src/main/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kinesis-asl/src/main/resources/log4j.properties
deleted file mode 100644
index 798457fae6..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kinesis-asl/src/main/resources/log4j.properties
+++ /dev/null
@@ -1,37 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-log4j.rootCategory=WARN, console
-
-# File appender
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=false
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %p %c{1}: %m%n
-
-# Console appender
-log4j.appender.console=org.apache.log4j.ConsoleAppender
-log4j.appender.console.target=System.out
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
-log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n
-
-# Settings to quiet third party logs that are too verbose
-log4j.logger.org.spark_project.jetty=WARN
-log4j.logger.org.spark_project.jetty.util.component.AbstractLifeCycle=ERROR
-log4j.logger.org.apache.spark.repl.SparkIMain$exprTyper=INFO
-log4j.logger.org.apache.spark.repl.SparkILoop$SparkILoopInterpreter=INFO
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kinesis-asl/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kinesis-asl/src/test/resources/log4j.properties
deleted file mode 100644
index 001d0482ac..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kinesis-asl/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file target/unit-tests.log
-log4j.rootCategory=INFO, file
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Ignore messages below warning level from Jetty, because it's a bit verbose
-log4j.logger.org.spark_project.jetty=WARN
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/graphx/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/graphx/src/test/resources/log4j.properties
deleted file mode 100644
index 001d0482ac..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/graphx/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file target/unit-tests.log
-log4j.rootCategory=INFO, file
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Ignore messages below warning level from Jetty, because it's a bit verbose
-log4j.logger.org.spark_project.jetty=WARN
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/launcher/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/launcher/src/test/resources/log4j.properties
deleted file mode 100644
index 3e7f668307..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/launcher/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file core/target/unit-tests.log
-test.appender=file
-log4j.rootCategory=INFO, ${test.appender}
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=false
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-log4j.appender.childproc=org.apache.log4j.ConsoleAppender
-log4j.appender.childproc.target=System.err
-log4j.appender.childproc.layout=org.apache.log4j.PatternLayout
-log4j.appender.childproc.layout.ConversionPattern=%t: %m%n
-
-# Ignore messages below warning level from Jetty, because it's a bit verbose
-log4j.logger.org.spark_project.jetty=WARN
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/mllib/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/mllib/src/test/resources/log4j.properties
deleted file mode 100644
index 001d0482ac..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/mllib/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file target/unit-tests.log
-log4j.rootCategory=INFO, file
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Ignore messages below warning level from Jetty, because it's a bit verbose
-log4j.logger.org.spark_project.jetty=WARN
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/repl/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/repl/src/test/resources/log4j.properties
deleted file mode 100644
index 2472d7dd17..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/repl/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the target/unit-tests.log
-log4j.rootCategory=INFO, file
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Ignore messages below warning level from Jetty, because it's a bit verbose
-log4j.logger.org.spark_project.jetty=WARN
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/catalyst/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/catalyst/src/test/resources/log4j.properties
deleted file mode 100644
index 001d0482ac..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/catalyst/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file target/unit-tests.log
-log4j.rootCategory=INFO, file
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Ignore messages below warning level from Jetty, because it's a bit verbose
-log4j.logger.org.spark_project.jetty=WARN
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/core/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/core/src/test/resources/log4j.properties
deleted file mode 100644
index c216bec215..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/core/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,57 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file core/target/unit-tests.log
-log4j.rootLogger=INFO, CA, FA
-
-#Console Appender
-log4j.appender.CA=org.apache.log4j.ConsoleAppender
-log4j.appender.CA.layout=org.apache.log4j.PatternLayout
-log4j.appender.CA.layout.ConversionPattern=%d{HH:mm:ss.SSS} %p %c: %m%n
-log4j.appender.CA.Threshold = WARN
-log4j.appender.CA.follow = true
-
-
-#File Appender
-log4j.appender.FA=org.apache.log4j.FileAppender
-log4j.appender.FA.append=false
-log4j.appender.FA.file=target/unit-tests.log
-log4j.appender.FA.layout=org.apache.log4j.PatternLayout
-log4j.appender.FA.layout.ConversionPattern=%d{HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Set the logger level of File Appender to WARN
-log4j.appender.FA.Threshold = INFO
-
-# Some packages are noisy for no good reason.
-log4j.additivity.org.apache.parquet.hadoop.ParquetRecordReader=false
-log4j.logger.org.apache.parquet.hadoop.ParquetRecordReader=OFF
-
-log4j.additivity.org.apache.parquet.hadoop.ParquetOutputCommitter=false
-log4j.logger.org.apache.parquet.hadoop.ParquetOutputCommitter=OFF
-
-log4j.additivity.org.apache.hadoop.hive.serde2.lazy.LazyStruct=false
-log4j.logger.org.apache.hadoop.hive.serde2.lazy.LazyStruct=OFF
-
-log4j.additivity.org.apache.hadoop.hive.metastore.RetryingHMSHandler=false
-log4j.logger.org.apache.hadoop.hive.metastore.RetryingHMSHandler=OFF
-
-log4j.additivity.hive.ql.metadata.Hive=false
-log4j.logger.hive.ql.metadata.Hive=OFF
-
-# Parquet related logging
-log4j.logger.org.apache.parquet.hadoop=WARN
-log4j.logger.org.apache.spark.sql.parquet=INFO
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/hive/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/hive/src/test/resources/log4j.properties
deleted file mode 100644
index 4f31a6aca5..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/hive/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,61 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file core/target/unit-tests.log
-log4j.rootLogger=DEBUG, CA, FA
-
-#Console Appender
-log4j.appender.CA=org.apache.log4j.ConsoleAppender
-log4j.appender.CA.layout=org.apache.log4j.PatternLayout
-log4j.appender.CA.layout.ConversionPattern=%d{HH:mm:ss.SSS} %p %c: %m%n
-log4j.appender.CA.Threshold = WARN
-
-
-#File Appender
-log4j.appender.FA=org.apache.log4j.FileAppender
-log4j.appender.FA.append=false
-log4j.appender.FA.file=target/unit-tests.log
-log4j.appender.FA.layout=org.apache.log4j.PatternLayout
-log4j.appender.FA.layout.ConversionPattern=%d{HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Set the logger level of File Appender to WARN
-log4j.appender.FA.Threshold = DEBUG
-
-# Some packages are noisy for no good reason.
-log4j.additivity.org.apache.hadoop.hive.serde2.lazy.LazyStruct=false
-log4j.logger.org.apache.hadoop.hive.serde2.lazy.LazyStruct=OFF
-
-log4j.additivity.org.apache.hadoop.hive.metastore.RetryingHMSHandler=false
-log4j.logger.org.apache.hadoop.hive.metastore.RetryingHMSHandler=OFF
-
-log4j.additivity.hive.log=false
-log4j.logger.hive.log=OFF
-
-log4j.additivity.parquet.hadoop.ParquetRecordReader=false
-log4j.logger.parquet.hadoop.ParquetRecordReader=OFF
-
-log4j.additivity.org.apache.parquet.hadoop.ParquetRecordReader=false
-log4j.logger.org.apache.parquet.hadoop.ParquetRecordReader=OFF
-
-log4j.additivity.org.apache.parquet.hadoop.ParquetOutputCommitter=false
-log4j.logger.org.apache.parquet.hadoop.ParquetOutputCommitter=OFF
-
-log4j.additivity.hive.ql.metadata.Hive=false
-log4j.logger.hive.ql.metadata.Hive=OFF
-
-log4j.additivity.org.apache.hadoop.hive.ql.io.RCFile=false
-log4j.logger.org.apache.hadoop.hive.ql.io.RCFile=ERROR
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/streaming/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/streaming/src/test/resources/log4j.properties
deleted file mode 100644
index 001d0482ac..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/streaming/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file target/unit-tests.log
-log4j.rootCategory=INFO, file
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Ignore messages below warning level from Jetty, because it's a bit verbose
-log4j.logger.org.spark_project.jetty=WARN
diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/yarn/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/yarn/src/test/resources/log4j.properties
deleted file mode 100644
index 29ec90539b..0000000000
--- a/log4j-1.2-api/src/test/resources/config-1.2/spark/yarn/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to you under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Set everything to be logged to the file target/unit-tests.log
-log4j.rootCategory=DEBUG, file
-log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.append=true
-log4j.appender.file.file=target/unit-tests.log
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
-
-# Ignore messages below warning level from a few verbose libraries.
-log4j.logger.com.sun.jersey=WARN
-log4j.logger.org.apache.hadoop=WARN
-log4j.logger.org.eclipse.jetty=WARN
-log4j.logger.org.mortbay=WARN
-log4j.logger.org.spark_project.jetty=WARN
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/tools/GenerateCustomLoggerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/tools/GenerateCustomLoggerTest.java
deleted file mode 100644
index 5932d113fa..0000000000
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/tools/GenerateCustomLoggerTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.core.tools;
-
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import javax.tools.Diagnostic;
-import javax.tools.DiagnosticCollector;
-import javax.tools.JavaCompiler;
-import javax.tools.JavaFileObject;
-import javax.tools.StandardJavaFileManager;
-import javax.tools.ToolProvider;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Marker;
-import org.apache.logging.log4j.core.test.TestConstants;
-import org.apache.logging.log4j.message.Message;
-import org.apache.logging.log4j.message.MessageFactory;
-import org.apache.logging.log4j.test.TestLogger;
-import org.apache.logging.log4j.util.MessageSupplier;
-import org.apache.logging.log4j.util.Strings;
-import org.apache.logging.log4j.util.Supplier;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.Tag;
-import org.junit.jupiter.api.Test;
-import org.junitpioneer.jupiter.SetSystemProperty;
-
-@Tag("functional")
-@SetSystemProperty(
- key = TestConstants.LOGGER_CONTEXT_FACTORY,
- value = "org.apache.logging.log4j.test.TestLoggerContextFactory")
-public class GenerateCustomLoggerTest {
-
- private static final String TEST_SOURCE = "target/test-classes/org/apache/logging/log4j/core/MyCustomLogger.java";
-
- @AfterAll
- public static void afterClass() {
- File file = new File(TEST_SOURCE);
- final File parent = file.getParentFile();
- if (file.exists()) {
- file.delete();
- }
- file = new File(parent, "MyCustomLogger.class");
- if (file.exists()) {
- file.delete();
- }
- }
-
- @Test
- @SuppressWarnings("ReturnValueIgnored")
- public void testGenerateSource() throws Exception {
- final String CLASSNAME = "org.apache.logging.log4j.core.MyCustomLogger";
-
- // generate custom logger source
- final List<String> values = Arrays.asList("DEFCON1=350 DEFCON2=450 DEFCON3=550".split(" "));
- final List<Generate.LevelInfo> levels = Generate.LevelInfo.parse(values, Generate.CustomLogger.class);
- final String src = Generate.generateSource(CLASSNAME, levels, Generate.Type.CUSTOM);
- final File f = new File(TEST_SOURCE);
- f.getParentFile().mkdirs();
- try (final FileOutputStream out = new FileOutputStream(f)) {
- out.write(src.getBytes(Charset.defaultCharset()));
- }
-
- // set up compiler
- final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
- final List<String> errors = new ArrayList<>();
- try (final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) {
- final Iterable<? extends JavaFileObject> compilationUnits =
- fileManager.getJavaFileObjectsFromFiles(Collections.singletonList(f));
- final String classPath = System.getProperty("jdk.module.path");
- final List<String> optionList = new ArrayList<>();
- if (Strings.isNotBlank(classPath)) {
- optionList.add("-classpath");
- optionList.add(classPath);
- }
- // compile generated source
- compiler.getTask(null, fileManager, diagnostics, optionList, null, compilationUnits)
- .call();
-
- // check we don't have any compilation errors
- for (final Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
- if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
- errors.add(String.format("Compile error: %s%n", diagnostic.getMessage(Locale.getDefault())));
- }
- }
- }
- assertTrue(errors.isEmpty(), errors.toString());
-
- // load the compiled class
- final Class<?> cls = Class.forName(CLASSNAME);
-
- // check that all factory methods exist and are static
- assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create").getModifiers()));
- assertTrue(
- Modifier.isStatic(cls.getDeclaredMethod("create", Class.class).getModifiers()));
- assertTrue(
- Modifier.isStatic(cls.getDeclaredMethod("create", Object.class).getModifiers()));
- assertTrue(
- Modifier.isStatic(cls.getDeclaredMethod("create", String.class).getModifiers()));
- assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", Class.class, MessageFactory.class)
- .getModifiers()));
- assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", Object.class, MessageFactory.class)
- .getModifiers()));
- assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", String.class, MessageFactory.class)
- .getModifiers()));
-
- // check that all log methods exist
- final String[] logMethods = {"defcon1", "defcon2", "defcon3"};
- for (final String name : logMethods) {
- assertDoesNotThrow(() -> {
- cls.getDeclaredMethod(name, Marker.class, Message.class, Throwable.class);
- cls.getDeclaredMethod(name, Marker.class, Object.class, Throwable.class);
- cls.getDeclaredMethod(name, Marker.class, String.class, Throwable.class);
- cls.getDeclaredMethod(name, Marker.class, Message.class);
- cls.getDeclaredMethod(name, Marker.class, Object.class);
- cls.getDeclaredMethod(name, Marker.class, String.class);
- cls.getDeclaredMethod(name, Message.class);
- cls.getDeclaredMethod(name, Object.class);
- cls.getDeclaredMethod(name, String.class);
- cls.getDeclaredMethod(name, Message.class, Throwable.class);
- cls.getDeclaredMethod(name, Object.class, Throwable.class);
- cls.getDeclaredMethod(name, String.class, Throwable.class);
- cls.getDeclaredMethod(name, String.class, Object[].class);
- cls.getDeclaredMethod(name, Marker.class, String.class, Object[].class);
-
- // 2.4 lambda support
- cls.getDeclaredMethod(name, Marker.class, MessageSupplier.class);
- cls.getDeclaredMethod(name, Marker.class, MessageSupplier.class, Throwable.class);
- cls.getDeclaredMethod(name, Marker.class, String.class, Supplier[].class);
- cls.getDeclaredMethod(name, Marker.class, Supplier.class);
- cls.getDeclaredMethod(name, Marker.class, Supplier.class, Throwable.class);
- cls.getDeclaredMethod(name, MessageSupplier.class);
- cls.getDeclaredMethod(name, MessageSupplier.class, Throwable.class);
- cls.getDeclaredMethod(name, String.class, Supplier[].class);
- cls.getDeclaredMethod(name, Supplier.class);
- cls.getDeclaredMethod(name, Supplier.class, Throwable.class);
- });
- }
-
- // now see if it actually works...
- final Method create = cls.getDeclaredMethod("create", String.class);
- final Object customLogger = create.invoke(null, "X.Y.Z");
- int n = 0;
- for (final String name : logMethods) {
- final Method method = cls.getDeclaredMethod(name, String.class);
- method.invoke(customLogger, "This is message " + n++);
- }
-
- final TestLogger underlying = (TestLogger) LogManager.getLogger("X.Y.Z");
- final List<String> lines = underlying.getEntries();
- for (int i = 0; i < lines.size(); i++) {
- assertEquals(" " + levels.get(i).name + " This is message " + i, lines.get(i));
- }
- }
-}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/tools/GenerateExtendedLoggerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/tools/GenerateExtendedLoggerTest.java
deleted file mode 100644
index cb8f49afbf..0000000000
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/tools/GenerateExtendedLoggerTest.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.core.tools;
-
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import javax.tools.Diagnostic;
-import javax.tools.DiagnosticCollector;
-import javax.tools.JavaCompiler;
-import javax.tools.JavaFileObject;
-import javax.tools.StandardJavaFileManager;
-import javax.tools.ToolProvider;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Marker;
-import org.apache.logging.log4j.core.test.TestConstants;
-import org.apache.logging.log4j.message.Message;
-import org.apache.logging.log4j.message.MessageFactory;
-import org.apache.logging.log4j.spi.ExtendedLogger;
-import org.apache.logging.log4j.test.TestLogger;
-import org.apache.logging.log4j.util.MessageSupplier;
-import org.apache.logging.log4j.util.Strings;
-import org.apache.logging.log4j.util.Supplier;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.Tag;
-import org.junit.jupiter.api.Test;
-import org.junitpioneer.jupiter.SetSystemProperty;
-
-@Tag("functional")
-@SetSystemProperty(
- key = TestConstants.LOGGER_CONTEXT_FACTORY,
- value = "org.apache.logging.log4j.test.TestLoggerContextFactory")
-public class GenerateExtendedLoggerTest {
-
- private static final String TEST_SOURCE = "target/test-classes/org/apache/logging/log4j/core/MyExtendedLogger.java";
-
- @AfterAll
- public static void afterClass() {
- File file = new File(TEST_SOURCE);
- final File parent = file.getParentFile();
- if (file.exists()) {
- file.delete();
- }
- file = new File(parent, "MyExtendedLogger.class");
- if (file.exists()) {
- file.delete();
- }
- }
-
- @Test
- @SuppressWarnings("ReturnValueIgnored")
- public void testGenerateSource() throws Exception {
- final String CLASSNAME = "org.apache.logging.log4j.core.MyExtendedLogger";
-
- // generate custom logger source
- final List<String> values = Arrays.asList("DIAG=350 NOTICE=450 VERBOSE=550".split(" "));
- final List<Generate.LevelInfo> levels = Generate.LevelInfo.parse(values, Generate.ExtendedLogger.class);
- final String src = Generate.generateSource(CLASSNAME, levels, Generate.Type.EXTEND);
- final File f = new File(TEST_SOURCE);
- f.getParentFile().mkdirs();
- try (final FileOutputStream out = new FileOutputStream(f)) {
- out.write(src.getBytes(Charset.defaultCharset()));
- }
-
- final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
- final List<String> errors = new ArrayList<>();
- try (final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) {
- final Iterable<? extends JavaFileObject> compilationUnits =
- fileManager.getJavaFileObjectsFromFiles(Collections.singletonList(f));
- final String classPath = System.getProperty("jdk.module.path");
- final List<String> optionList = new ArrayList<>();
- if (Strings.isNotBlank(classPath)) {
- optionList.add("-classpath");
- optionList.add(classPath);
- }
- // compile generated source
- compiler.getTask(null, fileManager, diagnostics, optionList, null, compilationUnits)
- .call();
-
- // check we don't have any compilation errors
- for (final Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
- if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
- errors.add(String.format("Compile error: %s%n", diagnostic.getMessage(Locale.getDefault())));
- }
- }
- }
- assertTrue(errors.isEmpty(), errors.toString());
-
- // load the compiled class
- final Class<?> cls = Class.forName(CLASSNAME);
-
- // check that all factory methods exist and are static
- assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create").getModifiers()));
- assertTrue(
- Modifier.isStatic(cls.getDeclaredMethod("create", Class.class).getModifiers()));
- assertTrue(
- Modifier.isStatic(cls.getDeclaredMethod("create", Object.class).getModifiers()));
- assertTrue(
- Modifier.isStatic(cls.getDeclaredMethod("create", String.class).getModifiers()));
- assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", Class.class, MessageFactory.class)
- .getModifiers()));
- assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", Object.class, MessageFactory.class)
- .getModifiers()));
- assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", String.class, MessageFactory.class)
- .getModifiers()));
-
- // check that the extended log methods exist
- final String[] extendedMethods = {"diag", "notice", "verbose"};
- for (final String name : extendedMethods) {
- assertDoesNotThrow(() -> {
- cls.getDeclaredMethod(name, Marker.class, Message.class, Throwable.class);
- cls.getDeclaredMethod(name, Marker.class, Object.class, Throwable.class);
- cls.getDeclaredMethod(name, Marker.class, String.class, Throwable.class);
- cls.getDeclaredMethod(name, Marker.class, Message.class);
- cls.getDeclaredMethod(name, Marker.class, Object.class);
- cls.getDeclaredMethod(name, Marker.class, String.class);
- cls.getDeclaredMethod(name, Message.class);
- cls.getDeclaredMethod(name, Object.class);
- cls.getDeclaredMethod(name, String.class);
- cls.getDeclaredMethod(name, Message.class, Throwable.class);
- cls.getDeclaredMethod(name, Object.class, Throwable.class);
- cls.getDeclaredMethod(name, String.class, Throwable.class);
- cls.getDeclaredMethod(name, String.class, Object[].class);
- cls.getDeclaredMethod(name, Marker.class, String.class, Object[].class);
-
- // 2.4 lambda support
- cls.getDeclaredMethod(name, Marker.class, MessageSupplier.class);
- cls.getDeclaredMethod(name, Marker.class, MessageSupplier.class, Throwable.class);
- cls.getDeclaredMethod(name, Marker.class, String.class, Supplier[].class);
- cls.getDeclaredMethod(name, Marker.class, Supplier.class);
- cls.getDeclaredMethod(name, Marker.class, Supplier.class, Throwable.class);
- cls.getDeclaredMethod(name, MessageSupplier.class);
- cls.getDeclaredMethod(name, MessageSupplier.class, Throwable.class);
- cls.getDeclaredMethod(name, String.class, Supplier[].class);
- cls.getDeclaredMethod(name, Supplier.class);
- cls.getDeclaredMethod(name, Supplier.class, Throwable.class);
- });
- }
-
- // now see if it actually works...
- final Method create = cls.getDeclaredMethod("create", String.class);
- final Object extendedLogger = create.invoke(null, "X.Y.Z");
- int n = 0;
- for (final String name : extendedMethods) {
- final Method method = cls.getDeclaredMethod(name, String.class);
- method.invoke(extendedLogger, "This is message " + n++);
- }
-
- // This logger extends o.a.l.log4j.spi.ExtendedLogger,
- // so all the standard logging methods can be used as well
- final ExtendedLogger logger = (ExtendedLogger) extendedLogger;
- logger.trace("trace message");
- logger.debug("debug message");
- logger.info("info message");
- logger.warn("warn message");
- logger.error("error message");
- logger.fatal("fatal message");
-
- final TestLogger underlying = (TestLogger) LogManager.getLogger("X.Y.Z");
- final List<String> lines = underlying.getEntries();
- for (int i = 0; i < lines.size() - 6; i++) {
- assertEquals(" " + levels.get(i).name + " This is message " + i, lines.get(i));
- }
-
- // test that the standard logging methods still work
- int i = lines.size() - 6;
- assertEquals(" TRACE trace message", lines.get(i++));
- assertEquals(" DEBUG debug message", lines.get(i++));
- assertEquals(" INFO info message", lines.get(i++));
- assertEquals(" WARN warn message", lines.get(i++));
- assertEquals(" ERROR error message", lines.get(i++));
- assertEquals(" FATAL fatal message", lines.get(i++));
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/BasicCommandLineArguments.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/BasicCommandLineArguments.java
deleted file mode 100644
index bacc03f61c..0000000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/BasicCommandLineArguments.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.core.tools;
-
-import org.apache.logging.log4j.core.tools.picocli.CommandLine.Option;
-
-public class BasicCommandLineArguments {
-
- @Option(
- names = {"--help", "-?", "-h"},
- usageHelp = true,
- description = "Prints this help and exits.")
- private boolean help;
-
- public boolean isHelp() {
- return help;
- }
-
- public void setHelp(final boolean help) {
- this.help = help;
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/CustomLoggerGenerator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/CustomLoggerGenerator.java
deleted file mode 100644
index 8f61116750..0000000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/CustomLoggerGenerator.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.core.tools;
-
-/**
- * Wrapper around {@link Generate.CustomLogger}.
- */
-public class CustomLoggerGenerator {
- /**
- * Delegates to {@link Generate.CustomLogger#main(String[])}
- * @param args the command line arguments to pass on
- */
- public static void main(final String[] args) {
- Generate.CustomLogger.main(args);
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/ExtendedLoggerGenerator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/ExtendedLoggerGenerator.java
deleted file mode 100644
index 339f9cc192..0000000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/ExtendedLoggerGenerator.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.core.tools;
-
-/**
- * Wrapper around {@link Generate.ExtendedLogger}.
- */
-public class ExtendedLoggerGenerator {
- /**
- * Delegates to {@link Generate.ExtendedLogger#main(String[])}
- * @param args the command line arguments to pass on
- */
- public static void main(final String[] args) {
- Generate.ExtendedLogger.main(args);
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/Generate.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/Generate.java
deleted file mode 100644
index 5c3d34e85f..0000000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/Generate.java
+++ /dev/null
@@ -1,1165 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.core.tools;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Generates source code for custom or extended logger wrappers.
- * <p>
- * Usage:
- * <p>
- * To generate source code for an extended logger that adds custom log levels to the existing ones: <br>
- * {@code java org.apache.logging.log4j.core.tools.Generate$ExtendedLogger <logger.class.name> <CUSTOMLEVEL>=<WEIGHT>
- * [CUSTOMLEVEL2=WEIGHT2 [CUSTOMLEVEL3=WEIGHT3] ...]}
- * <p>
- * Example of creating an extended logger:<br>
- * {@code java org.apache.logging.log4j.core.tools.Generate$ExtendedLogger com.mycomp.ExtLogger DIAG=350 NOTICE=450
- * VERBOSE=550}
- * <p>
- * To generate source code for a custom logger that replaces the existing log levels with custom ones: <br>
- * {@code java org.apache.logging.log4j.core.tools.Generate$CustomLogger <logger.class.name> <CUSTOMLEVEL>=<WEIGHT>
- * [CUSTOMLEVEL2=WEIGHT2 [CUSTOMLEVEL3=WEIGHT3] ...]}
- * <p>
- * Example of creating a custom logger:<br>
- * {@code java org.apache.logging.log4j.core.tools.Generate$CustomLogger com.mycomp.MyLogger DEFCON1=350 DEFCON2=450
- * DEFCON3=550}
- */
-public final class Generate {
- // Implementation note:
- // The generated code is in the user's namespace which has its own versioning scheme, so
- // any @since tags in the generated code deliberately mention "Log4j-2.x" rather than just the log4j version number.
-
- static final String PACKAGE_DECLARATION = "package %s;%n%n";
-
- enum Type {
- CUSTOM {
- @Override
- String imports() {
- // @formatter:off
- return ""
- + "import org.apache.logging.log4j.Level;%n"
- + "import org.apache.logging.log4j.LogManager;%n"
- + "import org.apache.logging.log4j.Logger;%n"
- + "import org.apache.logging.log4j.Marker;%n"
- + "import org.apache.logging.log4j.message.Message;%n"
- + "import org.apache.logging.log4j.message.MessageFactory;%n"
- + "import org.apache.logging.log4j.spi.ExtendedLogger;%n"
- + "import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;%n"
- + "import org.apache.logging.log4j.util.MessageSupplier;%n"
- + "import org.apache.logging.log4j.util.Supplier;%n"
- + "%n";
- // @formatter:on
- }
-
- @Override
- String declaration() {
- // @formatter:off
- return ""
- + "/**%n"
- + " * Custom Logger interface with convenience methods for%n"
- + " * %s%n"
- + " * <p>Compatible with Log4j 2.6 or higher.</p>%n"
- + " */%n"
- + "public final class %s {%n"
- + " private final ExtendedLoggerWrapper logger;%n"
- + "%n";
- // @formatter:on
- }
-
- @Override
- String constructor() {
- // @formatter:off
- return ""
- + "%n"
- + " private %s(final Logger logger) {%n"
- + " this.logger = new ExtendedLoggerWrapper((ExtendedLogger) logger, logger.getName(), "
- + "logger.getMessageFactory());%n"
- + " }%n";
- // @formatter:on
- }
-
- @Override
- Class<?> generator() {
- return CustomLogger.class;
- }
- },
- EXTEND {
- @Override
- String imports() {
- // @formatter:off
- return ""
- + "import org.apache.logging.log4j.Level;%n"
- + "import org.apache.logging.log4j.LogManager;%n"
- + "import org.apache.logging.log4j.Logger;%n"
- + "import org.apache.logging.log4j.Marker;%n"
- + "import org.apache.logging.log4j.message.Message;%n"
- + "import org.apache.logging.log4j.message.MessageFactory;%n"
- + "import org.apache.logging.log4j.spi.ExtendedLogger;%n"
- + "import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;%n"
- + "import org.apache.logging.log4j.util.MessageSupplier;%n"
- + "import org.apache.logging.log4j.util.Supplier;%n"
- + "%n";
- // @formatter:on
- }
-
- @Override
- String declaration() {
- // @formatter:off
- return ""
- + "/**%n"
- + " * Extended Logger interface with convenience methods for%n"
- + " * %s%n"
- + " * <p>Compatible with Log4j 2.6 or higher.</p>%n"
- + " */%n"
- + "public final class %s extends ExtendedLoggerWrapper {%n"
- + " private static final long serialVersionUID = " + System.nanoTime() + "L;%n"
- + " private final ExtendedLoggerWrapper logger;%n"
- + "%n";
- // @formatter:on
- }
-
- @Override
- String constructor() {
- // @formatter:off
- return ""
- + "%n"
- + " private %s(final Logger logger) {%n"
- + " super((ExtendedLogger) logger, logger.getName(), logger.getMessageFactory());%n"
- + " this.logger = this;%n"
- + " }%n";
- // @formatter:on
- }
-
- @Override
- Class<?> generator() {
- return ExtendedLogger.class;
- }
- };
-
- abstract String imports();
-
- abstract String declaration();
-
- abstract String constructor();
-
- abstract Class<?> generator();
- }
-
- static final String FQCN_FIELD = "" + " private static final String FQCN = %s.class.getName();%n";
-
- static final String LEVEL_FIELD = "" + " private static final Level %s = Level.forName(\"%s\", %d);%n";
-
- static final String FACTORY_METHODS = ""
- // @formatter:off
- + "%n"
- + " /**%n"
- + " * Returns a custom Logger with the name of the calling class.%n"
- + " * %n"
- + " * @return The custom Logger for the calling class.%n"
- + " */%n"
- + " public static CLASSNAME create() {%n"
- + " final Logger wrapped = LogManager.getLogger();%n"
- + " return new CLASSNAME(wrapped);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Returns a custom Logger using the fully qualified name of the Class as%n"
- + " * the Logger name.%n"
- + " * %n"
- + " * @param loggerName The Class whose name should be used as the Logger name.%n"
- + " * If null it will default to the calling class.%n"
- + " * @return The custom Logger.%n"
- + " */%n"
- + " public static CLASSNAME create(final Class<?> loggerName) {%n"
- + " final Logger wrapped = LogManager.getLogger(loggerName);%n"
- + " return new CLASSNAME(wrapped);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Returns a custom Logger using the fully qualified name of the Class as%n"
- + " * the Logger name.%n"
- + " * %n"
- + " * @param loggerName The Class whose name should be used as the Logger name.%n"
- + " * If null it will default to the calling class.%n"
- + " * @param messageFactory The message factory is used only when creating a%n"
- + " * logger, subsequent use does not change the logger but will log%n"
- + " * a warning if mismatched.%n"
- + " * @return The custom Logger.%n"
- + " */%n"
- + " public static CLASSNAME create(final Class<?> loggerName, final MessageFactory"
- + " messageFactory) {%n"
- + " final Logger wrapped = LogManager.getLogger(loggerName, messageFactory);%n"
- + " return new CLASSNAME(wrapped);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Returns a custom Logger using the fully qualified class name of the value%n"
- + " * as the Logger name.%n"
- + " * %n"
- + " * @param value The value whose class name should be used as the Logger%n"
- + " * name. If null the name of the calling class will be used as%n"
- + " * the logger name.%n"
- + " * @return The custom Logger.%n"
- + " */%n"
- + " public static CLASSNAME create(final Object value) {%n"
- + " final Logger wrapped = LogManager.getLogger(value);%n"
- + " return new CLASSNAME(wrapped);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Returns a custom Logger using the fully qualified class name of the value%n"
- + " * as the Logger name.%n"
- + " * %n"
- + " * @param value The value whose class name should be used as the Logger%n"
- + " * name. If null the name of the calling class will be used as%n"
- + " * the logger name.%n"
- + " * @param messageFactory The message factory is used only when creating a%n"
- + " * logger, subsequent use does not change the logger but will log%n"
- + " * a warning if mismatched.%n"
- + " * @return The custom Logger.%n"
- + " */%n"
- + " public static CLASSNAME create(final Object value, final MessageFactory messageFactory) {%n"
- + " final Logger wrapped = LogManager.getLogger(value, messageFactory);%n"
- + " return new CLASSNAME(wrapped);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Returns a custom Logger with the specified name.%n"
- + " * %n"
- + " * @param name The logger name. If null the name of the calling class will%n"
- + " * be used.%n"
- + " * @return The custom Logger.%n"
- + " */%n"
- + " public static CLASSNAME create(final String name) {%n"
- + " final Logger wrapped = LogManager.getLogger(name);%n"
- + " return new CLASSNAME(wrapped);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Returns a custom Logger with the specified name.%n"
- + " * %n"
- + " * @param name The logger name. If null the name of the calling class will%n"
- + " * be used.%n"
- + " * @param messageFactory The message factory is used only when creating a%n"
- + " * logger, subsequent use does not change the logger but will log%n"
- + " * a warning if mismatched.%n"
- + " * @return The custom Logger.%n"
- + " */%n"
- + " public static CLASSNAME create(final String name, final MessageFactory messageFactory) {%n"
- + " final Logger wrapped = LogManager.getLogger(name, messageFactory);%n"
- + " return new CLASSNAME(wrapped);%n"
- + " }%n";
- // @formatter:on
-
- static final String METHODS = ""
- // @formatter:off
- + "%n"
- + " /**%n"
- + " * Logs a message with the specific Marker at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param msg the message string to be logged%n"
- + " */%n"
- + " public void methodName(final Marker marker, final Message msg) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msg, (Throwable) null);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with the specific Marker at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param msg the message string to be logged%n"
- + " * @param t A Throwable or null.%n"
- + " */%n"
- + " public void methodName(final Marker marker, final Message msg, final Throwable t) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msg, t);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message object to log.%n"
- + " */%n"
- + " public void methodName(final Marker marker, final Object message) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, (Throwable) null);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message CharSequence with the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message CharSequence to log.%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final Marker marker, final CharSequence message) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, (Throwable) null);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n"
- + " * the {@link Throwable} {@code t} passed as parameter.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message to log.%n"
- + " * @param t the exception to log, including its stack trace.%n"
- + " */%n"
- + " public void methodName(final Marker marker, final Object message, final Throwable t) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, t);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n"
- + " * the {@link Throwable} {@code t} passed as parameter.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the CharSequence to log.%n"
- + " * @param t the exception to log, including its stack trace.%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final Marker marker, final CharSequence message, final Throwable t) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, t);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message object to log.%n"
- + " */%n"
- + " public void methodName(final Marker marker, final String message) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, (Throwable) null);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param params parameters to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " */%n"
- + " public void methodName(final Marker marker, final String message, final Object... params) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, params);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final Marker marker, final String message, final Object p0) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final Marker marker, final String message, final Object p0, "
- + "final Object p1) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final Marker marker, final String message, final Object p0, "
- + "final Object p1, final Object p2) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @param p3 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final Marker marker, final String message, final Object p0, "
- + "final Object p1, final Object p2,%n"
- + " final Object p3) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @param p3 parameter to the message.%n"
- + " * @param p4 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final Marker marker, final String message, final Object p0, "
- + "final Object p1, final Object p2,%n"
- + " final Object p3, final Object p4) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @param p3 parameter to the message.%n"
- + " * @param p4 parameter to the message.%n"
- + " * @param p5 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final Marker marker, final String message, final Object p0, "
- + "final Object p1, final Object p2,%n"
- + " final Object p3, final Object p4, final Object p5) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4, p5);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @param p3 parameter to the message.%n"
- + " * @param p4 parameter to the message.%n"
- + " * @param p5 parameter to the message.%n"
- + " * @param p6 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final Marker marker, final String message, final Object p0, "
- + "final Object p1, final Object p2,%n"
- + " final Object p3, final Object p4, final Object p5, final Object p6) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4, p5, p6);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @param p3 parameter to the message.%n"
- + " * @param p4 parameter to the message.%n"
- + " * @param p5 parameter to the message.%n"
- + " * @param p6 parameter to the message.%n"
- + " * @param p7 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final Marker marker, final String message, final Object p0, "
- + "final Object p1, final Object p2,%n"
- + " final Object p3, final Object p4, final Object p5, final Object p6,%n"
- + " final Object p7) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4, p5, p6, p7);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @param p3 parameter to the message.%n"
- + " * @param p4 parameter to the message.%n"
- + " * @param p5 parameter to the message.%n"
- + " * @param p6 parameter to the message.%n"
- + " * @param p7 parameter to the message.%n"
- + " * @param p8 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final Marker marker, final String message, final Object p0, "
- + "final Object p1, final Object p2,%n"
- + " final Object p3, final Object p4, final Object p5, final Object p6,%n"
- + " final Object p7, final Object p8) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, "
- + "p8);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @param p3 parameter to the message.%n"
- + " * @param p4 parameter to the message.%n"
- + " * @param p5 parameter to the message.%n"
- + " * @param p6 parameter to the message.%n"
- + " * @param p7 parameter to the message.%n"
- + " * @param p8 parameter to the message.%n"
- + " * @param p9 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final Marker marker, final String message, final Object p0, "
- + "final Object p1, final Object p2,%n"
- + " final Object p3, final Object p4, final Object p5, final Object p6,%n"
- + " final Object p7, final Object p8, final Object p9) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, "
- + "p8, p9);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n"
- + " * the {@link Throwable} {@code t} passed as parameter.%n"
- + " * %n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message to log.%n"
- + " * @param t the exception to log, including its stack trace.%n"
- + " */%n"
- + " public void methodName(final Marker marker, final String message, final Throwable t) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, t);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs the specified Message at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param msg the message string to be logged%n"
- + " */%n"
- + " public void methodName(final Message msg) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msg, (Throwable) null);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs the specified Message at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param msg the message string to be logged%n"
- + " * @param t A Throwable or null.%n"
- + " */%n"
- + " public void methodName(final Message msg, final Throwable t) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msg, t);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param message the message object to log.%n"
- + " */%n"
- + " public void methodName(final Object message) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, (Throwable) null);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n"
- + " * the {@link Throwable} {@code t} passed as parameter.%n"
- + " * %n"
- + " * @param message the message to log.%n"
- + " * @param t the exception to log, including its stack trace.%n"
- + " */%n"
- + " public void methodName(final Object message, final Throwable t) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, t);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message CharSequence with the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param message the message CharSequence to log.%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final CharSequence message) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, (Throwable) null);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a CharSequence at the {@code CUSTOM_LEVEL} level including the stack trace of%n"
- + " * the {@link Throwable} {@code t} passed as parameter.%n"
- + " * %n"
- + " * @param message the CharSequence to log.%n"
- + " * @param t the exception to log, including its stack trace.%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final CharSequence message, final Throwable t) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, t);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param message the message object to log.%n"
- + " */%n"
- + " public void methodName(final String message) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, (Throwable) null);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param params parameters to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " */%n"
- + " public void methodName(final String message, final Object... params) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, params);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final String message, final Object p0) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final String message, final Object p0, "
- + "final Object p1) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final String message, final Object p0, "
- + "final Object p1, final Object p2) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @param p3 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final String message, final Object p0, "
- + "final Object p1, final Object p2,%n"
- + " final Object p3) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @param p3 parameter to the message.%n"
- + " * @param p4 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final String message, final Object p0, "
- + "final Object p1, final Object p2,%n"
- + " final Object p3, final Object p4) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @param p3 parameter to the message.%n"
- + " * @param p4 parameter to the message.%n"
- + " * @param p5 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final String message, final Object p0, "
- + "final Object p1, final Object p2,%n"
- + " final Object p3, final Object p4, final Object p5) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4, p5);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @param p3 parameter to the message.%n"
- + " * @param p4 parameter to the message.%n"
- + " * @param p5 parameter to the message.%n"
- + " * @param p6 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final String message, final Object p0, "
- + "final Object p1, final Object p2,%n"
- + " final Object p3, final Object p4, final Object p5, final Object p6) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4, p5, p6);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @param p3 parameter to the message.%n"
- + " * @param p4 parameter to the message.%n"
- + " * @param p5 parameter to the message.%n"
- + " * @param p6 parameter to the message.%n"
- + " * @param p7 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final String message, final Object p0, "
- + "final Object p1, final Object p2,%n"
- + " final Object p3, final Object p4, final Object p5, final Object p6,%n"
- + " final Object p7) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4, p5, p6, p7);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @param p3 parameter to the message.%n"
- + " * @param p4 parameter to the message.%n"
- + " * @param p5 parameter to the message.%n"
- + " * @param p6 parameter to the message.%n"
- + " * @param p7 parameter to the message.%n"
- + " * @param p8 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final String message, final Object p0, "
- + "final Object p1, final Object p2,%n"
- + " final Object p3, final Object p4, final Object p5, final Object p6,%n"
- + " final Object p7, final Object p8) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4, p5, p6, p7, "
- + "p8);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n"
- + " * %n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param p0 parameter to the message.%n"
- + " * @param p1 parameter to the message.%n"
- + " * @param p2 parameter to the message.%n"
- + " * @param p3 parameter to the message.%n"
- + " * @param p4 parameter to the message.%n"
- + " * @param p5 parameter to the message.%n"
- + " * @param p6 parameter to the message.%n"
- + " * @param p7 parameter to the message.%n"
- + " * @param p8 parameter to the message.%n"
- + " * @param p9 parameter to the message.%n"
- + " * @see #getMessageFactory()%n"
- + " * @since Log4j-2.6%n"
- + " */%n"
- + " public void methodName(final String message, final Object p0, "
- + "final Object p1, final Object p2,%n"
- + " final Object p3, final Object p4, final Object p5, final Object p6,%n"
- + " final Object p7, final Object p8, final Object p9) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4, p5, p6, p7, "
- + "p8, p9);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n"
- + " * the {@link Throwable} {@code t} passed as parameter.%n"
- + " * %n"
- + " * @param message the message to log.%n"
- + " * @param t the exception to log, including its stack trace.%n"
- + " */%n"
- + " public void methodName(final String message, final Throwable t) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, t);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message which is only to be constructed if the logging level is the {@code CUSTOM_LEVEL}"
- + "level.%n"
- + " *%n"
- + " * @param msgSupplier A function, which when called, produces the desired log message;%n"
- + " * the format depends on the message factory.%n"
- + " * @since Log4j-2.4%n"
- + " */%n"
- + " public void methodName(final Supplier<?> msgSupplier) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msgSupplier, (Throwable) null);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message (only to be constructed if the logging level is the {@code CUSTOM_LEVEL}%n"
- + " * level) including the stack trace of the {@link Throwable} <code>t</code> passed as parameter.%n"
- + " *%n"
- + " * @param msgSupplier A function, which when called, produces the desired log message;%n"
- + " * the format depends on the message factory.%n"
- + " * @param t the exception to log, including its stack trace.%n"
- + " * @since Log4j-2.4%n"
- + " */%n"
- + " public void methodName(final Supplier<?> msgSupplier, final Throwable t) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msgSupplier, t);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message which is only to be constructed if the logging level is the%n"
- + " * {@code CUSTOM_LEVEL} level with the specified Marker.%n"
- + " *%n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param msgSupplier A function, which when called, produces the desired log message;%n"
- + " * the format depends on the message factory.%n"
- + " * @since Log4j-2.4%n"
- + " */%n"
- + " public void methodName(final Marker marker, final Supplier<?> msgSupplier) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msgSupplier, (Throwable) null);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters which are only to be constructed if the logging level is the%n"
- + " * {@code CUSTOM_LEVEL} level.%n"
- + " *%n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param paramSuppliers An array of functions, which when called, produce the desired log"
- + " message parameters.%n"
- + " * @since Log4j-2.4%n"
- + " */%n"
- + " public void methodName(final Marker marker, final String message, final Supplier<?>..."
- + " paramSuppliers) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, paramSuppliers);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message (only to be constructed if the logging level is the {@code CUSTOM_LEVEL}%n"
- + " * level) with the specified Marker and including the stack trace of the {@link Throwable}%n"
- + " * <code>t</code> passed as parameter.%n"
- + " *%n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param msgSupplier A function, which when called, produces the desired log message;%n"
- + " * the format depends on the message factory.%n"
- + " * @param t A Throwable or null.%n"
- + " * @since Log4j-2.4%n"
- + " */%n"
- + " public void methodName(final Marker marker, final Supplier<?> msgSupplier, final Throwable t) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msgSupplier, t);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message with parameters which are only to be constructed if the logging level is%n"
- + " * the {@code CUSTOM_LEVEL} level.%n"
- + " *%n"
- + " * @param message the message to log; the format depends on the message factory.%n"
- + " * @param paramSuppliers An array of functions, which when called, produce the desired log"
- + " message parameters.%n"
- + " * @since Log4j-2.4%n"
- + " */%n"
- + " public void methodName(final String message, final Supplier<?>... paramSuppliers) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, paramSuppliers);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message which is only to be constructed if the logging level is the%n"
- + " * {@code CUSTOM_LEVEL} level with the specified Marker. The {@code MessageSupplier} may or may%n"
- + " * not use the {@link MessageFactory} to construct the {@code Message}.%n"
- + " *%n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param msgSupplier A function, which when called, produces the desired log message.%n"
- + " * @since Log4j-2.4%n"
- + " */%n"
- + " public void methodName(final Marker marker, final MessageSupplier msgSupplier) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msgSupplier, (Throwable) null);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message (only to be constructed if the logging level is the {@code CUSTOM_LEVEL}%n"
- + " * level) with the specified Marker and including the stack trace of the {@link Throwable}%n"
- + " * <code>t</code> passed as parameter. The {@code MessageSupplier} may or may not use the%n"
- + " * {@link MessageFactory} to construct the {@code Message}.%n"
- + " *%n"
- + " * @param marker the marker data specific to this log statement%n"
- + " * @param msgSupplier A function, which when called, produces the desired log message.%n"
- + " * @param t A Throwable or null.%n"
- + " * @since Log4j-2.4%n"
- + " */%n"
- + " public void methodName(final Marker marker, final MessageSupplier msgSupplier, final "
- + "Throwable t) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msgSupplier, t);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message which is only to be constructed if the logging level is the%n"
- + " * {@code CUSTOM_LEVEL} level. The {@code MessageSupplier} may or may not use the%n"
- + " * {@link MessageFactory} to construct the {@code Message}.%n"
- + " *%n"
- + " * @param msgSupplier A function, which when called, produces the desired log message.%n"
- + " * @since Log4j-2.4%n"
- + " */%n"
- + " public void methodName(final MessageSupplier msgSupplier) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msgSupplier, (Throwable) null);%n"
- + " }%n"
- + "%n"
- + " /**%n"
- + " * Logs a message (only to be constructed if the logging level is the {@code CUSTOM_LEVEL}%n"
- + " * level) including the stack trace of the {@link Throwable} <code>t</code> passed as parameter.%n"
- + " * The {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the%n"
- + " * {@code Message}.%n"
- + " *%n"
- + " * @param msgSupplier A function, which when called, produces the desired log message.%n"
- + " * @param t the exception to log, including its stack trace.%n"
- + " * @since Log4j-2.4%n"
- + " */%n"
- + " public void methodName(final MessageSupplier msgSupplier, final Throwable t) {%n"
- + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msgSupplier, t);%n"
- + " }%n";
- // @formatter:on
-
- private Generate() {}
-
- /**
- * Generates source code for custom logger wrappers that only provide convenience methods for the specified custom
- * levels, not for the standard built-in levels.
- */
- public static final class CustomLogger {
- /**
- * Generates source code for custom logger wrappers that only provide convenience methods for the specified
- * custom levels, not for the standard built-in levels.
- *
- * @param args className of the custom logger to generate, followed by a NAME=intLevel pair for each custom log
- * level to generate convenience methods for
- */
- public static void main(final String[] args) {
- generate(args, Type.CUSTOM);
- }
-
- private CustomLogger() {}
- }
-
- /**
- * Generates source code for extended logger wrappers that provide convenience methods for the specified custom
- * levels, and by extending {@code org.apache.logging.log4j.spi.ExtendedLoggerWrapper}, inherit the convenience
- * methods for the built-in levels provided by the {@code Logger} interface.
- */
- public static final class ExtendedLogger {
- /**
- * Generates source code for extended logger wrappers that provide convenience methods for the specified custom
- * levels.
- *
- * @param args className of the custom logger to generate, followed by a NAME=intLevel pair for each custom log
- * level to generate convenience methods for
- */
- public static void main(final String[] args) {
- generate(args, Type.EXTEND);
- }
-
- private ExtendedLogger() {}
- }
-
- static class LevelInfo {
- final String name;
- final int intLevel;
-
- LevelInfo(final String description) {
- final String[] parts = description.split("=");
- name = parts[0];
- intLevel = Integer.parseInt(parts[1]);
- }
-
- public static List<LevelInfo> parse(final List<String> values, final Class<?> generator) {
- final List<LevelInfo> result = new ArrayList<>(values.size());
- for (int i = 0; i < values.size(); i++) {
- try {
- result.add(new LevelInfo(values.get(i)));
- } catch (final Exception ex) {
- System.err.println("Cannot parse custom level '" + values.get(i) + "': " + ex.toString());
- usage(System.err, generator);
- System.exit(-1);
- }
- }
- return result;
- }
- }
-
- private static void generate(final String[] args, final Type type) {
- generate(args, type, System.out);
- }
-
- /**
- * Generates source code for extended logger wrappers that provide convenience methods for the specified custom
- * levels.
- *
- * @param args className of the custom logger to generate, followed by a NAME=intLevel pair for each custom log
- * level to generate convenience methods for
- * @param printStream the stream to write the generated source code to
- */
- public static void generateExtend(final String[] args, final PrintStream printStream) {
- generate(args, Type.EXTEND, printStream);
- }
-
- /**
- * Generates source code for custom logger wrappers that only provide convenience methods for the specified
- * custom levels, not for the standard built-in levels.
- *
- * @param args className of the custom logger to generate, followed by a NAME=intLevel pair for each custom log
- * level to generate convenience methods for
- * @param printStream the stream to write the generated source code to
- */
- public static void generateCustom(final String[] args, final PrintStream printStream) {
- generate(args, Type.CUSTOM, printStream);
- }
-
- static void generate(final String[] args, final Type type, final PrintStream printStream) {
- if (!validate(args)) {
- usage(printStream, type.generator());
- System.exit(-1);
- }
- final List<String> values = new ArrayList<>(Arrays.asList(args));
- final String classFQN = values.remove(0);
- final List<LevelInfo> levels = LevelInfo.parse(values, type.generator());
- printStream.println(generateSource(classFQN, levels, type));
- }
-
- static boolean validate(final String[] args) {
- return args.length >= 2;
- }
-
- private static void usage(final PrintStream out, final Class<?> generator) {
- out.println("Usage: java " + generator.getName() + " className LEVEL1=intLevel1 [LEVEL2=intLevel2...]");
- out.println(" Where className is the fully qualified class name of the custom/extended logger");
- out.println(" to generate, followed by a space-separated list of custom log levels.");
- out.println(" For each custom log level, specify NAME=intLevel (without spaces).");
- }
-
- @SuppressFBWarnings(
- value = "FORMAT_STRING_MANIPULATION",
- justification = "The format strings come from constants. The replacement is done for readability.")
- static String generateSource(final String classNameFQN, final List<LevelInfo> levels, final Type type) {
- final StringBuilder sb = new StringBuilder(10000 * levels.size());
- final int lastDot = classNameFQN.lastIndexOf('.');
- final String pkg = classNameFQN.substring(0, Math.max(lastDot, 0));
- if (!pkg.isEmpty()) {
- sb.append(String.format(PACKAGE_DECLARATION, pkg));
- }
- sb.append(String.format(type.imports(), ""));
- final String className = classNameFQN.substring(classNameFQN.lastIndexOf('.') + 1);
- final String javadocDescr = javadocDescription(levels);
- sb.append(String.format(type.declaration(), javadocDescr, className));
- sb.append(String.format(FQCN_FIELD, className));
- for (final LevelInfo level : levels) {
- sb.append(String.format(LEVEL_FIELD, level.name, level.name, level.intLevel));
- }
- sb.append(String.format(type.constructor(), className));
- sb.append(String.format(FACTORY_METHODS.replaceAll("CLASSNAME", className), ""));
- for (final LevelInfo level : levels) {
- final String methodName = camelCase(level.name);
- final String phase1 = METHODS.replaceAll("CUSTOM_LEVEL", level.name);
- final String phase2 = phase1.replaceAll("methodName", methodName);
- sb.append(String.format(phase2, ""));
- }
-
- sb.append('}');
- sb.append(System.getProperty("line.separator"));
- return sb.toString();
- }
-
- static String javadocDescription(final List<LevelInfo> levels) {
- if (levels.size() == 1) {
- return "the " + levels.get(0).name + " custom log level.";
- }
- final StringBuilder sb = new StringBuilder(512);
- sb.append("the ");
- String sep = "";
- for (int i = 0; i < levels.size(); i++) {
- sb.append(sep);
- sb.append(levels.get(i).name);
- sep = (i == levels.size() - 2) ? " and " : ", ";
- }
- sb.append(" custom log levels.");
- return sb.toString();
- }
-
- static String camelCase(final String customLevel) {
- final StringBuilder sb = new StringBuilder(customLevel.length());
- boolean lower = true;
- for (final char ch : customLevel.toCharArray()) {
- if (ch == '_') {
- lower = false;
- continue;
- }
- sb.append(lower ? Character.toLowerCase(ch) : Character.toUpperCase(ch));
- lower = true;
- }
- return sb.toString();
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/package-info.java
deleted file mode 100644
index 8cbd2eb92f..0000000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/package-info.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-/**
- * Log4j 2 command line tools.
- */
-@Export
-@Version("2.20.1")
-package org.apache.logging.log4j.core.tools;
-
-import org.osgi.annotation.bundle.Export;
-import org.osgi.annotation.versioning.Version;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java
deleted file mode 100644
index 37b5b46bf2..0000000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java
+++ /dev/null
@@ -1,5619 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.core.tools.picocli;
-
-import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Column.Overflow.SPAN;
-import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Column.Overflow.TRUNCATE;
-import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Column.Overflow.WRAP;
-import static org.apache.logging.log4j.util.Strings.toRootLowerCase;
-import static org.apache.logging.log4j.util.Strings.toRootUpperCase;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import java.io.File;
-import java.io.PrintStream;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.reflect.Array;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.lang.reflect.WildcardType;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.net.InetAddress;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.charset.Charset;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.sql.Time;
-import java.text.BreakIterator;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.Stack;
-import java.util.TreeSet;
-import java.util.UUID;
-import java.util.concurrent.Callable;
-import java.util.regex.Pattern;
-import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.IStyle;
-import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.Style;
-import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.Text;
-
-/**
- * <p>
- * CommandLine interpreter that uses reflection to initialize an annotated domain object with values obtained from the
- * command line arguments.
- * </p><h2>Example</h2>
- * <pre>import static picocli.CommandLine.*;
- *
- * @Command(header = "Encrypt FILE(s), or standard input, to standard output or to the output file.",
- * version = "v1.2.3")
- * public class Encrypt {
- *
- * @Parameters(type = File.class, description = "Any number of input files")
- * private List<File> files = new ArrayList<File>();
- *
- * @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
- * private File outputFile;
- *
- * @Option(names = { "-v", "--verbose"}, description = "Verbosely list files processed")
- * private boolean verbose;
- *
- * @Option(names = { "-h", "--help", "-?", "-help"}, usageHelp = true, description = "Display this help and exit")
- * private boolean help;
- *
- * @Option(names = { "-V", "--version"}, versionHelp = true, description = "Display version info and exit")
- * private boolean versionHelp;
- * }
- * </pre>
- * <p>
- * Use {@code CommandLine} to initialize a domain object as follows:
- * </p><pre>
- * public static void main(String... args) {
- * Encrypt encrypt = new Encrypt();
- * try {
- * List<CommandLine> parsedCommands = new CommandLine(encrypt).parse(args);
- * if (!CommandLine.printHelpIfRequested(parsedCommands, System.err, Help.Ansi.AUTO)) {
- * runProgram(encrypt);
- * }
- * } catch (ParameterException ex) { // command line arguments could not be parsed
- * System.err.println(ex.getMessage());
- * ex.getCommandLine().usage(System.err);
- * }
- * }
- * </pre><p>
- * Invoke the above program with some command line arguments. The below are all equivalent:
- * </p>
- * <pre>
- * --verbose --out=outfile in1 in2
- * --verbose --out outfile in1 in2
- * -v --out=outfile in1 in2
- * -v -o outfile in1 in2
- * -v -o=outfile in1 in2
- * -vo outfile in1 in2
- * -vo=outfile in1 in2
- * -v -ooutfile in1 in2
- * -vooutfile in1 in2
- * </pre>
- */
-public class CommandLine {
- /** This is picocli version {@value}. */
- public static final String VERSION = "2.0.3";
-
- private final Tracer tracer = new Tracer();
- private final Interpreter interpreter;
- private String commandName = Help.DEFAULT_COMMAND_NAME;
- private boolean overwrittenOptionsAllowed = false;
- private boolean unmatchedArgumentsAllowed = false;
- private final List<String> unmatchedArguments = new ArrayList<String>();
- private CommandLine parent;
- private boolean usageHelpRequested;
- private boolean versionHelpRequested;
- private final List<String> versionLines = new ArrayList<String>();
-
- /**
- * Constructs a new {@code CommandLine} interpreter with the specified annotated object.
- * When the {@link #parse(String...)} method is called, fields of the specified object that are annotated
- * with {@code @Option} or {@code @Parameters} will be initialized based on command line arguments.
- * @param command the object to initialize from the command line arguments
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- */
- public CommandLine(final Object command) {
- interpreter = new Interpreter(command);
- }
-
- /** Registers a subcommand with the specified name. For example:
- * <pre>
- * CommandLine commandLine = new CommandLine(new Git())
- * .addSubcommand("status", new GitStatus())
- * .addSubcommand("commit", new GitCommit();
- * .addSubcommand("add", new GitAdd())
- * .addSubcommand("branch", new GitBranch())
- * .addSubcommand("checkout", new GitCheckout())
- * //...
- * ;
- * </pre>
- *
- * <p>The specified object can be an annotated object or a
- * {@code CommandLine} instance with its own nested subcommands. For example:</p>
- * <pre>
- * CommandLine commandLine = new CommandLine(new MainCommand())
- * .addSubcommand("cmd1", new ChildCommand1()) // subcommand
- * .addSubcommand("cmd2", new ChildCommand2())
- * .addSubcommand("cmd3", new CommandLine(new ChildCommand3()) // subcommand with nested sub-subcommands
- * .addSubcommand("cmd3sub1", new GrandChild3Command1())
- * .addSubcommand("cmd3sub2", new GrandChild3Command2())
- * .addSubcommand("cmd3sub3", new CommandLine(new GrandChild3Command3()) // deeper nesting
- * .addSubcommand("cmd3sub3sub1", new GreatGrandChild3Command3_1())
- * .addSubcommand("cmd3sub3sub2", new GreatGrandChild3Command3_2())
- * )
- * );
- * </pre>
- * <p>The default type converters are available on all subcommands and nested sub-subcommands, but custom type
- * converters are registered only with the subcommand hierarchy as it existed when the custom type was registered.
- * To ensure a custom type converter is available to all subcommands, register the type converter last, after
- * adding subcommands.</p>
- * <p>See also the {@link Command#subcommands()} annotation to register subcommands declaratively.</p>
- *
- * @param name the string to recognize on the command line as a subcommand
- * @param command the object to initialize with command line arguments following the subcommand name.
- * This may be a {@code CommandLine} instance with its own (nested) subcommands
- * @return this CommandLine object, to allow method chaining
- * @see #registerConverter(Class, ITypeConverter)
- * @since 0.9.7
- * @see Command#subcommands()
- */
- public CommandLine addSubcommand(final String name, final Object command) {
- final CommandLine commandLine = toCommandLine(command);
- commandLine.parent = this;
- interpreter.commands.put(name, commandLine);
- return this;
- }
- /** Returns a map with the subcommands {@linkplain #addSubcommand(String, Object) registered} on this instance.
- * @return a map with the registered subcommands
- * @since 0.9.7
- */
- public Map<String, CommandLine> getSubcommands() {
- return new LinkedHashMap<String, CommandLine>(interpreter.commands);
- }
- /**
- * Returns the command that this is a subcommand of, or {@code null} if this is a top-level command.
- * @return the command that this is a subcommand of, or {@code null} if this is a top-level command
- * @see #addSubcommand(String, Object)
- * @see Command#subcommands()
- * @since 0.9.8
- */
- public CommandLine getParent() {
- return parent;
- }
-
- /** Returns the annotated object that this {@code CommandLine} instance was constructed with.
- * @param <T> the type of the variable that the return value is being assigned to
- * @return the annotated object that this {@code CommandLine} instance was constructed with
- * @since 0.9.7
- */
- public <T> T getCommand() {
- return (T) interpreter.command;
- }
-
- /** Returns {@code true} if an option annotated with {@link Option#usageHelp()} was specified on the command line.
- * @return whether the parser encountered an option annotated with {@link Option#usageHelp()}.
- * @since 0.9.8 */
- public boolean isUsageHelpRequested() {
- return usageHelpRequested;
- }
-
- /** Returns {@code true} if an option annotated with {@link Option#versionHelp()} was specified on the command line.
- * @return whether the parser encountered an option annotated with {@link Option#versionHelp()}.
- * @since 0.9.8 */
- public boolean isVersionHelpRequested() {
- return versionHelpRequested;
- }
-
- /** Returns whether options for single-value fields can be specified multiple times on the command line.
- * The default is {@code false} and a {@link OverwrittenOptionException} is thrown if this happens.
- * When {@code true}, the last specified value is retained.
- * @return {@code true} if options for single-value fields can be specified multiple times on the command line, {@code false} otherwise
- * @since 0.9.7
- */
- public boolean isOverwrittenOptionsAllowed() {
- return overwrittenOptionsAllowed;
- }
-
- /** Sets whether options for single-value fields can be specified multiple times on the command line without a {@link OverwrittenOptionException} being thrown.
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * @param newValue the new setting
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 0.9.7
- */
- public CommandLine setOverwrittenOptionsAllowed(final boolean newValue) {
- this.overwrittenOptionsAllowed = newValue;
- for (final CommandLine command : interpreter.commands.values()) {
- command.setOverwrittenOptionsAllowed(newValue);
- }
- return this;
- }
-
- /** Returns whether the end user may specify arguments on the command line that are not matched to any option or parameter fields.
- * The default is {@code false} and a {@link UnmatchedArgumentException} is thrown if this happens.
- * When {@code true}, the last unmatched arguments are available via the {@link #getUnmatchedArguments()} method.
- * @return {@code true} if the end use may specify unmatched arguments on the command line, {@code false} otherwise
- * @see #getUnmatchedArguments()
- * @since 0.9.7
- */
- public boolean isUnmatchedArgumentsAllowed() {
- return unmatchedArgumentsAllowed;
- }
-
- /** Sets whether the end user may specify unmatched arguments on the command line without a {@link UnmatchedArgumentException} being thrown.
- * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
- * later will have the default setting. To ensure a setting is applied to all
- * subcommands, call the setter last, after adding subcommands.</p>
- * @param newValue the new setting. When {@code true}, the last unmatched arguments are available via the {@link #getUnmatchedArguments()} method.
- * @return this {@code CommandLine} object, to allow method chaining
- * @since 0.9.7
- * @see #getUnmatchedArguments()
- */
- public CommandLine setUnmatchedArgumentsAllowed(final boolean newValue) {
- this.unmatchedArgumentsAllowed = newValue;
- for (final CommandLine command : interpreter.commands.values()) {
- command.setUnmatchedArgumentsAllowed(newValue);
- }
- return this;
- }
-
- /** Returns the list of unmatched command line arguments, if any.
- * @return the list of unmatched command line arguments or an empty list
- * @see #isUnmatchedArgumentsAllowed()
- * @since 0.9.7
- */
- public List<String> getUnmatchedArguments() {
- return unmatchedArguments;
- }
-
- /**
- * <p>
- * Convenience method that initializes the specified annotated object from the specified command line arguments.
- * </p><p>
- * This is equivalent to
- * </p><pre>
- * CommandLine cli = new CommandLine(command);
- * cli.parse(args);
- * return command;
- * </pre>
- *
- * @param command the object to initialize. This object contains fields annotated with
- * {@code @Option} or {@code @Parameters}.
- * @param args the command line arguments to parse
- * @param <T> the type of the annotated object
- * @return the specified annotated object
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ParameterException if the specified command line arguments are invalid
- * @since 0.9.7
- */
- public static <T> T populateCommand(final T command, final String... args) {
- final CommandLine cli = toCommandLine(command);
- cli.parse(args);
- return command;
- }
-
- /** Parses the specified command line arguments and returns a list of {@code CommandLine} objects representing the
- * top-level command and any subcommands (if any) that were recognized and initialized during the parsing process.
- * <p>
- * If parsing succeeds, the first element in the returned list is always {@code this CommandLine} object. The
- * returned list may contain more elements if subcommands were {@linkplain #addSubcommand(String, Object) registered}
- * and these subcommands were initialized by matching command line arguments. If parsing fails, a
- * {@link ParameterException} is thrown.
- * </p>
- *
- * @param args the command line arguments to parse
- * @return a list with the top-level command and any subcommands initialized by this method
- * @throws ParameterException if the specified command line arguments are invalid; use
- * {@link ParameterException#getCommandLine()} to get the command or subcommand whose user input was invalid
- */
- public List<CommandLine> parse(final String... args) {
- return interpreter.parse(args);
- }
- /**
- * Represents a function that can process a List of {@code CommandLine} objects resulting from successfully
- * {@linkplain #parse(String...) parsing} the command line arguments. This is a
- * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">functional interface</a>
- * whose functional method is {@link #handleParseResult(List, PrintStream, CommandLine.Help.Ansi)}.
- * <p>
- * Implementations of this functions can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandler}
- * methods to take some next step after the command line was successfully parsed.
- * </p>
- * @see RunFirst
- * @see RunLast
- * @see RunAll
- * @since 2.0 */
- public interface IParseResultHandler {
- /** Processes a List of {@code CommandLine} objects resulting from successfully
- * {@linkplain #parse(String...) parsing} the command line arguments and optionally returns a list of results.
- * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
- * @param out the {@code PrintStream} to print help to if requested
- * @param ansi for printing help messages using ANSI styles and colors
- * @return a list of results, or an empty list if there are no results
- * @throws ExecutionException if a problem occurred while processing the parse results; use
- * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- */
- List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi)
- throws ExecutionException;
- }
- /**
- * Represents a function that can handle a {@code ParameterException} that occurred while
- * {@linkplain #parse(String...) parsing} the command line arguments. This is a
- * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">functional interface</a>
- * whose functional method is {@link #handleException(CommandLine.ParameterException, PrintStream, CommandLine.Help.Ansi, String...)}.
- * <p>
- * Implementations of this functions can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandler}
- * methods to handle situations when the command line could not be parsed.
- * </p>
- * @see DefaultExceptionHandler
- * @since 2.0 */
- public interface IExceptionHandler {
- /** Handles a {@code ParameterException} that occurred while {@linkplain #parse(String...) parsing} the command
- * line arguments and optionally returns a list of results.
- * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments,
- * and the CommandLine representing the command or subcommand whose input was invalid
- * @param out the {@code PrintStream} to print help to if requested
- * @param ansi for printing help messages using ANSI styles and colors
- * @param args the command line arguments that could not be parsed
- * @return a list of results, or an empty list if there are no results
- */
- List<Object> handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args);
- }
- /**
- * Default exception handler that prints the exception message to the specified {@code PrintStream}, followed by the
- * usage message for the command or subcommand whose input was invalid.
- * <p>Implementation roughly looks like this:</p>
- * <pre>
- * System.err.println(paramException.getMessage());
- * paramException.getCommandLine().usage(System.err);
- * </pre>
- * @since 2.0 */
- public static class DefaultExceptionHandler implements IExceptionHandler {
- @Override
- public List<Object> handleException(
- final ParameterException ex, final PrintStream out, final Help.Ansi ansi, final String... args) {
- out.println(ex.getMessage());
- ex.getCommandLine().usage(out, ansi);
- return Collections.emptyList();
- }
- }
- /**
- * Helper method that may be useful when processing the list of {@code CommandLine} objects that result from successfully
- * {@linkplain #parse(String...) parsing} command line arguments. This method prints out
- * {@linkplain #usage(PrintStream, Help.Ansi) usage help} if {@linkplain #isUsageHelpRequested() requested}
- * or {@linkplain #printVersionHelp(PrintStream, Help.Ansi) version help} if {@linkplain #isVersionHelpRequested() requested}
- * and returns {@code true}. Otherwise, if none of the specified {@code CommandLine} objects have help requested,
- * this method returns {@code false}.
- * <p>
- * Note that this method <em>only</em> looks at the {@link Option#usageHelp() usageHelp} and
- * {@link Option#versionHelp() versionHelp} attributes. The {@link Option#help() help} attribute is ignored.
- * </p>
- * @param parsedCommands the list of {@code CommandLine} objects to check if help was requested
- * @param out the {@code PrintStream} to print help to if requested
- * @param ansi for printing help messages using ANSI styles and colors
- * @return {@code true} if help was printed, {@code false} otherwise
- * @since 2.0 */
- public static boolean printHelpIfRequested(
- final List<CommandLine> parsedCommands, final PrintStream out, final Help.Ansi ansi) {
- for (final CommandLine parsed : parsedCommands) {
- if (parsed.isUsageHelpRequested()) {
- parsed.usage(out, ansi);
- return true;
- } else if (parsed.isVersionHelpRequested()) {
- parsed.printVersionHelp(out, ansi);
- return true;
- }
- }
- return false;
- }
-
- private static Object execute(final CommandLine parsed) {
- final Object command = parsed.getCommand();
- if (command instanceof Runnable) {
- try {
- ((Runnable) command).run();
- return null;
- } catch (final Exception ex) {
- throw new ExecutionException(parsed, "Error while running command (" + command + ")", ex);
- }
- } else if (command instanceof Callable) {
- try {
- return ((Callable<Object>) command).call();
- } catch (final Exception ex) {
- throw new ExecutionException(parsed, "Error while calling command (" + command + ")", ex);
- }
- }
- throw new ExecutionException(parsed, "Parsed command (" + command + ") is not Runnable or Callable");
- }
- /**
- * Command line parse result handler that prints help if requested, and otherwise executes the top-level
- * {@code Runnable} or {@code Callable} command.
- * For use in the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) parseWithHandler} methods.
- * <p>
- * From picocli v2.0, {@code RunFirst} is used to implement the {@link #run(Runnable, PrintStream, Help.Ansi, String...) run}
- * and {@link #call(Callable, PrintStream, Help.Ansi, String...) call} convenience methods.
- * </p>
- * @since 2.0 */
- public static class RunFirst implements IParseResultHandler {
- /** Prints help if requested, and otherwise executes the top-level {@code Runnable} or {@code Callable} command.
- * If the top-level command does not implement either {@code Runnable} or {@code Callable}, a {@code ExecutionException}
- * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
- *
- * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
- * @param out the {@code PrintStream} to print help to if requested
- * @param ansi for printing help messages using ANSI styles and colors
- * @return an empty list if help was requested, or a list containing a single element: the result of calling the
- * {@code Callable}, or a {@code null} element if the top-level command was a {@code Runnable}
- * @throws ExecutionException if a problem occurred while processing the parse results; use
- * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- */
- @Override
- public List<Object> handleParseResult(
- final List<CommandLine> parsedCommands, final PrintStream out, final Help.Ansi ansi) {
- if (printHelpIfRequested(parsedCommands, out, ansi)) {
- return Collections.emptyList();
- }
- return Arrays.asList(execute(parsedCommands.get(0)));
- }
- }
- /**
- * Command line parse result handler that prints help if requested, and otherwise executes the most specific
- * {@code Runnable} or {@code Callable} subcommand.
- * For use in the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) parseWithHandler} methods.
- * <p>
- * Something like this:</p>
- * <pre>
- * // RunLast implementation: print help if requested, otherwise execute the most specific subcommand
- * if (CommandLine.printHelpIfRequested(parsedCommands, System.err, Help.Ansi.AUTO)) {
- * return emptyList();
- * }
- * CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
- * Object command = last.getCommand();
- * if (command instanceof Runnable) {
- * try {
- * ((Runnable) command).run();
- * } catch (Exception ex) {
- * throw new ExecutionException(last, "Error in runnable " + command, ex);
- * }
- * } else if (command instanceof Callable) {
- * Object result;
- * try {
- * result = ((Callable) command).call();
- * } catch (Exception ex) {
- * throw new ExecutionException(last, "Error in callable " + command, ex);
- * }
- * // ...do something with result
- * } else {
- * throw new ExecutionException(last, "Parsed command (" + command + ") is not Runnable or Callable");
- * }
- * </pre>
- * @since 2.0 */
- public static class RunLast implements IParseResultHandler {
- /** Prints help if requested, and otherwise executes the most specific {@code Runnable} or {@code Callable} subcommand.
- * If the last (sub)command does not implement either {@code Runnable} or {@code Callable}, a {@code ExecutionException}
- * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
- *
- * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
- * @param out the {@code PrintStream} to print help to if requested
- * @param ansi for printing help messages using ANSI styles and colors
- * @return an empty list if help was requested, or a list containing a single element: the result of calling the
- * {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable}
- * @throws ExecutionException if a problem occurred while processing the parse results; use
- * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- */
- @Override
- public List<Object> handleParseResult(
- final List<CommandLine> parsedCommands, final PrintStream out, final Help.Ansi ansi) {
- if (printHelpIfRequested(parsedCommands, out, ansi)) {
- return Collections.emptyList();
- }
- final CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
- return Arrays.asList(execute(last));
- }
- }
- /**
- * Command line parse result handler that prints help if requested, and otherwise executes the top-level command and
- * all subcommands as {@code Runnable} or {@code Callable}.
- * For use in the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) parseWithHandler} methods.
- * @since 2.0 */
- public static class RunAll implements IParseResultHandler {
- /** Prints help if requested, and otherwise executes the top-level command and all subcommands as {@code Runnable}
- * or {@code Callable}. If any of the {@code CommandLine} commands does not implement either
- * {@code Runnable} or {@code Callable}, a {@code ExecutionException}
- * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
- *
- * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
- * @param out the {@code PrintStream} to print help to if requested
- * @param ansi for printing help messages using ANSI styles and colors
- * @return an empty list if help was requested, or a list containing the result of executing all commands:
- * the return values from calling the {@code Callable} commands, {@code null} elements for commands that implement {@code Runnable}
- * @throws ExecutionException if a problem occurred while processing the parse results; use
- * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- */
- @Override
- public List<Object> handleParseResult(
- final List<CommandLine> parsedCommands, final PrintStream out, final Help.Ansi ansi) {
- if (printHelpIfRequested(parsedCommands, out, ansi)) {
- return null;
- }
- final List<Object> result = new ArrayList<Object>();
- for (final CommandLine parsed : parsedCommands) {
- result.add(execute(parsed));
- }
- return result;
- }
- }
- /**
- * Returns the result of calling {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)}
- * with {@code Help.Ansi.AUTO} and a new {@link DefaultExceptionHandler} in addition to the specified parse result handler,
- * {@code PrintStream}, and the specified command line arguments.
- * <p>
- * This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, Help.Ansi, String...) run}
- * and {@link #call(Callable, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better
- * support for nested subcommands.
- * </p>
- * <p>Calling this method roughly expands to:</p>
- * <pre>
- * try {
- * List<CommandLine> parsedCommands = parse(args);
- * return parseResultsHandler.handleParseResult(parsedCommands, out, Help.Ansi.AUTO);
- * } catch (ParameterException ex) {
- * return new DefaultExceptionHandler().handleException(ex, out, ansi, args);
- * }
- * </pre>
- * <p>
- * Picocli provides some default handlers that allow you to accomplish some common tasks with very little code.
- * The following handlers are available:</p>
- * <ul>
- * <li>{@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand
- * and tries to execute it as a {@code Runnable} or {@code Callable}.</li>
- * <li>{@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.</li>
- * <li>{@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.</li>
- * <li>{@link DefaultExceptionHandler} prints the error message followed by usage help</li>
- * </ul>
- * @param handler the function that will process the result of successfully parsing the command line arguments
- * @param out the {@code PrintStream} to print help to if requested
- * @param args the command line arguments
- * @return a list of results, or an empty list if there are no results
- * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the
- * parse results; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- * @see RunLast
- * @see RunAll
- * @since 2.0 */
- public List<Object> parseWithHandler(
- final IParseResultHandler handler, final PrintStream out, final String... args) {
- return parseWithHandlers(handler, out, Help.Ansi.AUTO, new DefaultExceptionHandler(), args);
- }
- /**
- * Tries to {@linkplain #parse(String...) parse} the specified command line arguments, and if successful, delegates
- * the processing of the resulting list of {@code CommandLine} objects to the specified {@linkplain IParseResultHandler handler}.
- * If the command line arguments were invalid, the {@code ParameterException} thrown from the {@code parse} method
- * is caught and passed to the specified {@link IExceptionHandler}.
- * <p>
- * This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, Help.Ansi, String...) run}
- * and {@link #call(Callable, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better
- * support for nested subcommands.
- * </p>
- * <p>Calling this method roughly expands to:</p>
- * <pre>
- * try {
- * List<CommandLine> parsedCommands = parse(args);
- * return parseResultsHandler.handleParseResult(parsedCommands, out, ansi);
- * } catch (ParameterException ex) {
- * return new exceptionHandler.handleException(ex, out, ansi, args);
- * }
- * </pre>
- * <p>
- * Picocli provides some default handlers that allow you to accomplish some common tasks with very little code.
- * The following handlers are available:</p>
- * <ul>
- * <li>{@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand
- * and tries to execute it as a {@code Runnable} or {@code Callable}.</li>
- * <li>{@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.</li>
- * <li>{@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.</li>
- * <li>{@link DefaultExceptionHandler} prints the error message followed by usage help</li>
- * </ul>
- *
- * @param handler the function that will process the result of successfully parsing the command line arguments
- * @param out the {@code PrintStream} to print help to if requested
- * @param ansi for printing help messages using ANSI styles and colors
- * @param exceptionHandler the function that can handle the {@code ParameterException} thrown when the command line arguments are invalid
- * @param args the command line arguments
- * @return a list of results produced by the {@code IParseResultHandler} or the {@code IExceptionHandler}, or an empty list if there are no results
- * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the parse
- * result {@code CommandLine} objects; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
- * @see RunLast
- * @see RunAll
- * @see DefaultExceptionHandler
- * @since 2.0 */
- public List<Object> parseWithHandlers(
- final IParseResultHandler handler,
- final PrintStream out,
- final Help.Ansi ansi,
- final IExceptionHandler exceptionHandler,
- final String... args) {
- try {
- final List<CommandLine> result = parse(args);
- return handler.handleParseResult(result, out, ansi);
- } catch (final ParameterException ex) {
- return exceptionHandler.handleException(ex, out, ansi, args);
- }
- }
- /**
- * Equivalent to {@code new CommandLine(command).usage(out)}. See {@link #usage(PrintStream)} for details.
- * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
- * @param out the print stream to print the help message to
- * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- */
- public static void usage(final Object command, final PrintStream out) {
- toCommandLine(command).usage(out);
- }
-
- /**
- * Equivalent to {@code new CommandLine(command).usage(out, ansi)}.
- * See {@link #usage(PrintStream, Help.Ansi)} for details.
- * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
- * @param out the print stream to print the help message to
- * @param ansi whether the usage message should contain ANSI escape codes or not
- * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- */
- public static void usage(final Object command, final PrintStream out, final Help.Ansi ansi) {
- toCommandLine(command).usage(out, ansi);
- }
-
- /**
- * Equivalent to {@code new CommandLine(command).usage(out, colorScheme)}.
- * See {@link #usage(PrintStream, Help.ColorScheme)} for details.
- * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
- * @param out the print stream to print the help message to
- * @param colorScheme the {@code ColorScheme} defining the styles for options, parameters and commands when ANSI is enabled
- * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- */
- public static void usage(final Object command, final PrintStream out, final Help.ColorScheme colorScheme) {
- toCommandLine(command).usage(out, colorScheme);
- }
-
- /**
- * Delegates to {@link #usage(PrintStream, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
- * @param out the printStream to print to
- * @see #usage(PrintStream, Help.ColorScheme)
- */
- public void usage(final PrintStream out) {
- usage(out, Help.Ansi.AUTO);
- }
-
- /**
- * Delegates to {@link #usage(PrintStream, Help.ColorScheme)} with the {@linkplain Help#defaultColorScheme(CommandLine.Help.Ansi) default color scheme}.
- * @param out the printStream to print to
- * @param ansi whether the usage message should include ANSI escape codes or not
- * @see #usage(PrintStream, Help.ColorScheme)
- */
- public void usage(final PrintStream out, final Help.Ansi ansi) {
- usage(out, Help.defaultColorScheme(ansi));
- }
- /**
- * Prints a usage help message for the annotated command class to the specified {@code PrintStream}.
- * Delegates construction of the usage help message to the {@link Help} inner class and is equivalent to:
- * <pre>
- * Help help = new Help(command).addAllSubcommands(getSubcommands());
- * StringBuilder sb = new StringBuilder()
- * .append(help.headerHeading())
- * .append(help.header())
- * .append(help.synopsisHeading()) //e.g. Usage:
- * .append(help.synopsis()) //e.g. <main class> [OPTIONS] <command> [COMMAND-OPTIONS] [ARGUMENTS]
- * .append(help.descriptionHeading()) //e.g. %nDescription:%n%n
- * .append(help.description()) //e.g. {"Converts foos to bars.", "Use options to control conversion mode."}
- * .append(help.parameterListHeading()) //e.g. %nPositional parameters:%n%n
- * .append(help.parameterList()) //e.g. [FILE...] the files to convert
- * .append(help.optionListHeading()) //e.g. %nOptions:%n%n
- * .append(help.optionList()) //e.g. -h, --help displays this help and exits
- * .append(help.commandListHeading()) //e.g. %nCommands:%n%n
- * .append(help.commandList()) //e.g. add adds the frup to the frooble
- * .append(help.footerHeading())
- * .append(help.footer());
- * out.print(sb);
- * </pre>
- * <p>Annotate your class with {@link Command} to control many aspects of the usage help message, including
- * the program name, text of section headings and section contents, and some aspects of the auto-generated sections
- * of the usage help message.
- * <p>To customize the auto-generated sections of the usage help message, like how option details are displayed,
- * instantiate a {@link Help} object and use a {@link Help.TextTable} with more of fewer columns, a custom
- * {@linkplain Help.Layout layout}, and/or a custom option {@linkplain Help.IOptionRenderer renderer}
- * for ultimate control over which aspects of an Option or Field are displayed where.</p>
- * @param out the {@code PrintStream} to print the usage help message to
- * @param colorScheme the {@code ColorScheme} defining the styles for options, parameters and commands when ANSI is enabled
- */
- public void usage(final PrintStream out, final Help.ColorScheme colorScheme) {
- final Help help = new Help(interpreter.command, colorScheme).addAllSubcommands(getSubcommands());
- if (!Help.DEFAULT_SEPARATOR.equals(getSeparator())) {
- help.separator = getSeparator();
- help.parameterLabelRenderer = help.createDefaultParamLabelRenderer(); // update for new separator
- }
- if (!Help.DEFAULT_COMMAND_NAME.equals(getCommandName())) {
- help.commandName = getCommandName();
- }
- final StringBuilder sb = new StringBuilder()
- .append(help.headerHeading())
- .append(help.header())
- .append(help.synopsisHeading()) // e.g. Usage:
- .append(help.synopsis(
- help.synopsisHeadingLength())) // e.g. <main class> [OPTIONS] <command>
- // [COMMAND-OPTIONS] [ARGUMENTS]
- .append(help.descriptionHeading()) // e.g. %nDescription:%n%n
- .append(help.description()) // e.g. {"Converts foos to bars.", "Use options to control conversion
- // mode."}
- .append(help.parameterListHeading()) // e.g. %nPositional parameters:%n%n
- .append(help.parameterList()) // e.g. [FILE...] the files to convert
- .append(help.optionListHeading()) // e.g. %nOptions:%n%n
- .append(help.optionList()) // e.g. -h, --help displays this help and exits
- .append(help.commandListHeading()) // e.g. %nCommands:%n%n
- .append(help.commandList()) // e.g. add adds the frup to the frooble
- .append(help.footerHeading())
- .append(help.footer());
- out.print(sb);
- }
-
- /**
- * Delegates to {@link #printVersionHelp(PrintStream, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
- * @param out the printStream to print to
- * @see #printVersionHelp(PrintStream, Help.Ansi)
- * @since 0.9.8
- */
- public void printVersionHelp(final PrintStream out) {
- printVersionHelp(out, Help.Ansi.AUTO);
- }
-
- /**
- * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintStream}.
- * Each element of the array of version strings is printed on a separate line. Version strings may contain
- * <a href="http://picocli.info/#_usage_help_with_styles_and_colors">markup for colors and style</a>.
- * @param out the printStream to print to
- * @param ansi whether the usage message should include ANSI escape codes or not
- * @see Command#version()
- * @see Option#versionHelp()
- * @see #isVersionHelpRequested()
- * @since 0.9.8
- */
- public void printVersionHelp(final PrintStream out, final Help.Ansi ansi) {
- for (final String versionInfo : versionLines) {
- out.println(ansi.new Text(versionInfo));
- }
- }
- /**
- * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintStream}.
- * Each element of the array of version strings is {@linkplain String#format(String, Object...) formatted} with the
- * specified parameters, and printed on a separate line. Both version strings and parameters may contain
- * <a href="http://picocli.info/#_usage_help_with_styles_and_colors">markup for colors and style</a>.
- * @param out the printStream to print to
- * @param ansi whether the usage message should include ANSI escape codes or not
- * @param params Arguments referenced by the format specifiers in the version strings
- * @see Command#version()
- * @see Option#versionHelp()
- * @see #isVersionHelpRequested()
- * @since 1.0.0
- */
- public void printVersionHelp(final PrintStream out, final Help.Ansi ansi, final Object... params) {
- for (final String versionInfo : versionLines) {
- out.println(ansi.new Text(String.format(versionInfo, params)));
- }
- }
-
- /**
- * Delegates to {@link #call(Callable, PrintStream, Help.Ansi, String...)} with {@link Help.Ansi#AUTO}.
- * <p>
- * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested},
- * and any exceptions thrown by the {@code Callable} are caught and rethrown wrapped in an {@code ExecutionException}.
- * </p>
- * @param callable the command to call when {@linkplain #parse(String...) parsing} succeeds.
- * @param out the printStream to print to
- * @param args the command line arguments to parse
- * @param <C> the annotated object must implement Callable
- * @param <T> the return type of the most specific command (must implement {@code Callable})
- * @see #call(Callable, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Callable throws an exception
- * @return {@code null} if an error occurred while parsing the command line options, otherwise returns the result of calling the Callable
- * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)
- * @see RunFirst
- */
- public static <C extends Callable<T>, T> T call(final C callable, final PrintStream out, final String... args) {
- return call(callable, out, Help.Ansi.AUTO, args);
- }
- /**
- * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
- * The annotated object needs to implement {@link Callable}. Calling this method is equivalent to:
- * <pre>
- * CommandLine cmd = new CommandLine(callable);
- * List<CommandLine> parsedCommands;
- * try {
- * parsedCommands = cmd.parse(args);
- * } catch (ParameterException ex) {
- * out.println(ex.getMessage());
- * cmd.usage(out, ansi);
- * return null;
- * }
- * if (CommandLine.printHelpIfRequested(parsedCommands, out, ansi)) {
- * return null;
- * }
- * CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
- * try {
- * Callable<Object> subcommand = last.getCommand();
- * return subcommand.call();
- * } catch (Exception ex) {
- * throw new ExecutionException(last, "Error calling " + last.getCommand(), ex);
- * }
- * </pre>
- * <p>
- * If the specified Callable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
- * command line is executed.
- * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler, PrintStream, String...) parseWithHandler}
- * method with a {@link RunAll} handler or a custom handler.
- * </p><p>
- * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested},
- * and any exceptions thrown by the {@code Callable} are caught and rethrown wrapped in an {@code ExecutionException}.
- * </p>
- * @param callable the command to call when {@linkplain #parse(String...) parsing} succeeds.
- * @param out the printStream to print to
- * @param ansi whether the usage message should include ANSI escape codes or not
- * @param args the command line arguments to parse
- * @param <C> the annotated object must implement Callable
- * @param <T> the return type of the specified {@code Callable}
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Callable throws an exception
- * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
- * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)
- * @see RunLast
- */
- public static <C extends Callable<T>, T> T call(
- final C callable, final PrintStream out, final Help.Ansi ansi, final String... args) {
- final CommandLine cmd = new CommandLine(callable); // validate command outside of try-catch
- final List<Object> results =
- cmd.parseWithHandlers(new RunLast(), out, ansi, new DefaultExceptionHandler(), args);
- return results == null || results.isEmpty() ? null : (T) results.get(0);
- }
-
- /**
- * Delegates to {@link #run(Runnable, PrintStream, Help.Ansi, String...)} with {@link Help.Ansi#AUTO}.
- * <p>
- * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested},
- * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}.
- * </p>
- * @param runnable the command to run when {@linkplain #parse(String...) parsing} succeeds.
- * @param out the printStream to print to
- * @param args the command line arguments to parse
- * @param <R> the annotated object must implement Runnable
- * @see #run(Runnable, PrintStream, Help.Ansi, String...)
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Runnable throws an exception
- * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)
- * @see RunFirst
- */
- public static <R extends Runnable> void run(final R runnable, final PrintStream out, final String... args) {
- run(runnable, out, Help.Ansi.AUTO, args);
- }
- /**
- * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
- * The annotated object needs to implement {@link Runnable}. Calling this method is equivalent to:
- * <pre>
- * CommandLine cmd = new CommandLine(runnable);
- * List<CommandLine> parsedCommands;
- * try {
- * parsedCommands = cmd.parse(args);
- * } catch (ParameterException ex) {
- * out.println(ex.getMessage());
- * cmd.usage(out, ansi);
- * return null;
- * }
- * if (CommandLine.printHelpIfRequested(parsedCommands, out, ansi)) {
- * return null;
- * }
- * CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
- * try {
- * Runnable subcommand = last.getCommand();
- * subcommand.run();
- * } catch (Exception ex) {
- * throw new ExecutionException(last, "Error running " + last.getCommand(), ex);
- * }
- * </pre>
- * <p>
- * If the specified Runnable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
- * command line is executed.
- * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler, PrintStream, String...) parseWithHandler}
- * method with a {@link RunAll} handler or a custom handler.
- * </p><p>
- * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested},
- * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}.
- * </p>
- * @param runnable the command to run when {@linkplain #parse(String...) parsing} succeeds.
- * @param out the printStream to print to
- * @param ansi whether the usage message should include ANSI escape codes or not
- * @param args the command line arguments to parse
- * @param <R> the annotated object must implement Runnable
- * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
- * @throws ExecutionException if the Runnable throws an exception
- * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)
- * @see RunLast
- */
- public static <R extends Runnable> void run(
- final R runnable, final PrintStream out, final Help.Ansi ansi, final String... args) {
- final CommandLine cmd = new CommandLine(runnable); // validate command outside of try-catch
- cmd.parseWithHandlers(new RunLast(), out, ansi, new DefaultExceptionHandler(), args);
- }
-
- /**
- * Registers the specified type converter for the specified class. When initializing fields annotated with
- * {@link Option}, the field's type is used as a lookup key to find the associated type converter, and this
- * type converter converts the original command line argument string value to the correct type.
- * <p>
- * Java 8 lambdas make it easy to register custom type converters:
- * </p>
- * <pre>
- * commandLine.registerConverter(java.nio.file.Path.class, s -> java.nio.file.Paths.get(s));
- * commandLine.registerConverter(java.time.Duration.class, s -> java.time.Duration.parse(s));</pre>
- * <p>
- * Built-in type converters are pre-registered for the following java 1.5 types:
- * </p>
- * <ul>
- * <li>all primitive types</li>
- * <li>all primitive wrapper types: Boolean, Byte, Character, Double, Float, Integer, Long, Short</li>
- * <li>any enum</li>
- * <li>java.io.File</li>
- * <li>java.math.BigDecimal</li>
- * <li>java.math.BigInteger</li>
- * <li>java.net.InetAddress</li>
- * <li>java.net.URI</li>
- * <li>java.net.URL</li>
- * <li>java.nio.charset.Charset</li>
- * <li>java.sql.Time</li>
- * <li>java.util.Date</li>
- * <li>java.util.UUID</li>
- * <li>java.util.regex.Pattern</li>
- * <li>StringBuilder</li>
- * <li>CharSequence</li>
- * <li>String</li>
- * </ul>
- * <p>The specified converter will be registered with this {@code CommandLine} and the full hierarchy of its
- * subcommands and nested sub-subcommands <em>at the moment the converter is registered</em>. Subcommands added
- * later will not have this converter added automatically. To ensure a custom type converter is available to all
- * subcommands, register the type converter last, after adding subcommands.</p>
- *
- * @param cls the target class to convert parameter string values to
- * @param converter the class capable of converting string values to the specified target type
- * @param <K> the target type
- * @return this CommandLine object, to allow method chaining
- * @see #addSubcommand(String, Object)
- */
- public <K> CommandLine registerConverter(final Class<K> cls, final ITypeConverter<K> converter) {
- interpreter.converterRegistry.put(Assert.notNull(cls, "class"), Assert.notNull(converter, "converter"));
- for (final CommandLine command : interpreter.commands.values()) {
- command.registerConverter(cls, converter);
- }
- return this;
- }
-
- /** Returns the String that separates option names from option values when parsing command line options. {@value Help#DEFAULT_SEPARATOR} by default.
- * @return the String the parser uses to separate option names from option values */
- public String getSeparator() {
- return interpreter.separator;
- }
-
- /** Sets the String the parser uses to separate option names from option values to the specified value.
- * The separator may also be set declaratively with the {@link CommandLine.Command#separator()} annotation attribute.
- * @param separator the String that separates option names from option values
- * @return this {@code CommandLine} object, to allow method chaining */
- public CommandLine setSeparator(final String separator) {
- interpreter.separator = Assert.notNull(separator, "separator");
- return this;
- }
-
- /** Returns the command name (also called program name) displayed in the usage help synopsis. {@value Help#DEFAULT_COMMAND_NAME} by default.
- * @return the command name (also called program name) displayed in the usage */
- public String getCommandName() {
- return commandName;
- }
-
- /** Sets the command name (also called program name) displayed in the usage help synopsis to the specified value.
- * Note that this method only modifies the usage help message, it does not impact parsing behaviour.
- * The command name may also be set declaratively with the {@link CommandLine.Command#name()} annotation attribute.
- * @param commandName command name (also called program name) displayed in the usage help synopsis
- * @return this {@code CommandLine} object, to allow method chaining */
- public CommandLine setCommandName(final String commandName) {
- this.commandName = Assert.notNull(commandName, "commandName");
- return this;
- }
-
- private static boolean empty(final String str) {
- return str == null || str.trim().length() == 0;
- }
-
- private static boolean empty(final Object[] array) {
- return array == null || array.length == 0;
- }
-
- private static boolean empty(final Text txt) {
- return txt == null || txt.plain.toString().trim().length() == 0;
- }
-
- private static String str(final String[] arr, final int i) {
- return (arr == null || arr.length == 0) ? "" : arr[i];
- }
-
- private static boolean isBoolean(final Class<?> type) {
- return type == Boolean.class || type == Boolean.TYPE;
- }
-
- private static CommandLine toCommandLine(final Object obj) {
- return obj instanceof CommandLine ? (CommandLine) obj : new CommandLine(obj);
- }
-
- private static boolean isMultiValue(final Field field) {
- return isMultiValue(field.getType());
- }
-
- private static boolean isMultiValue(final Class<?> cls) {
- return cls.isArray() || Collection.class.isAssignableFrom(cls) || Map.class.isAssignableFrom(cls);
- }
-
- private static Class<?>[] getTypeAttribute(final Field field) {
- final Class<?>[] explicit = field.isAnnotationPresent(Parameters.class)
- ? field.getAnnotation(Parameters.class).type()
- : field.getAnnotation(Option.class).type();
- if (explicit.length > 0) {
- return explicit;
- }
- if (field.getType().isArray()) {
- return new Class<?>[] {field.getType().getComponentType()};
- }
- if (isMultiValue(field)) {
- final Type type = field.getGenericType(); // e.g. Map<Long, ? extends Number>
- if (type instanceof ParameterizedType) {
- final ParameterizedType parameterizedType = (ParameterizedType) type;
- final Type[] paramTypes = parameterizedType.getActualTypeArguments(); // e.g. ? extends Number
- final Class<?>[] result = new Class<?>[paramTypes.length];
- for (int i = 0; i < paramTypes.length; i++) {
- if (paramTypes[i] instanceof Class) {
- result[i] = (Class<?>) paramTypes[i];
- continue;
- } // e.g. Long
- if (paramTypes[i] instanceof WildcardType) { // e.g. ? extends Number
- final WildcardType wildcardType = (WildcardType) paramTypes[i];
- final Type[] lower = wildcardType.getLowerBounds(); // e.g. []
- if (lower.length > 0 && lower[0] instanceof Class) {
- result[i] = (Class<?>) lower[0];
- continue;
- }
- final Type[] upper = wildcardType.getUpperBounds(); // e.g. Number
- if (upper.length > 0 && upper[0] instanceof Class) {
- result[i] = (Class<?>) upper[0];
- continue;
- }
- }
- Arrays.fill(result, String.class);
- return result; // too convoluted generic type, giving up
- }
- return result; // we inferred all types from ParameterizedType
- }
- return new Class<?>[] {String.class, String.class}; // field is multi-value but not ParameterizedType
- }
- return new Class<?>[] {field.getType()}; // not a multi-value field
- }
- /**
- * <p>
- * Annotate fields in your class with {@code @Option} and picocli will initialize these fields when matching
- * arguments are specified on the command line.
- * </p><p>
- * For example:
- * </p>
- * <pre>import static picocli.CommandLine.*;
- *
- * public class MyClass {
- * @Parameters(type = File.class, description = "Any number of input files")
- * private List<File> files = new ArrayList<File>();
- *
- * @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
- * private File outputFile;
- *
- * @Option(names = { "-v", "--verbose"}, description = "Verbosely list files processed")
- * private boolean verbose;
- *
- * @Option(names = { "-h", "--help", "-?", "-help"}, usageHelp = true, description = "Display this help and exit")
- * private boolean help;
- *
- * @Option(names = { "-V", "--version"}, versionHelp = true, description = "Display version information and exit")
- * private boolean version;
- * }
- * </pre>
- * <p>
- * A field cannot be annotated with both {@code @Parameters} and {@code @Option} or a
- * {@code ParameterException} is thrown.
- * </p>
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.FIELD)
- public @interface Option {
- /**
- * One or more option names. At least one option name is required.
- * <p>
- * Different environments have different conventions for naming options, but usually options have a prefix
- * that sets them apart from parameters.
- * Picocli supports all of the below styles. The default separator is {@code '='}, but this can be configured.
- * </p><p>
- * <b>*nix</b>
- * </p><p>
- * In Unix and Linux, options have a short (single-character) name, a long name or both.
- * Short options
- * (<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02">POSIX
- * style</a> are single-character and are preceded by the {@code '-'} character, e.g., {@code `-v'}.
- * <a href="https://www.gnu.org/software/tar/manual/html_node/Long-Options.html">GNU-style</a> long
- * (or <em>mnemonic</em>) options start with two dashes in a row, e.g., {@code `--file'}.
- * </p><p>Picocli supports the POSIX convention that short options can be grouped, with the last option
- * optionally taking a parameter, which may be attached to the option name or separated by a space or
- * a {@code '='} character. The below examples are all equivalent:
- * </p><pre>
- * -xvfFILE
- * -xvf FILE
- * -xvf=FILE
- * -xv --file FILE
- * -xv --file=FILE
- * -x -v --file FILE
- * -x -v --file=FILE
- * </pre><p>
- * <b>DOS</b>
- * </p><p>
- * DOS options mostly have upper case single-character names and start with a single slash {@code '/'} character.
- * Option parameters are separated by a {@code ':'} character. Options cannot be grouped together but
- * must be specified separately. For example:
- * </p><pre>
- * DIR /S /A:D /T:C
- * </pre><p>
- * <b>PowerShell</b>
- * </p><p>
- * Windows PowerShell options generally are a word preceded by a single {@code '-'} character, e.g., {@code `-Help'}.
- * Option parameters are separated by a space or by a {@code ':'} character.
- * </p>
- * @return one or more option names
- */
- String[] names();
-
- /**
- * Indicates whether this option is required. By default this is false.
- * If an option is required, but a user invokes the program without specifying the required option,
- * a {@link MissingParameterException} is thrown from the {@link #parse(String...)} method.
- * @return whether this option is required
- */
- boolean required() default false;
-
- /**
- * Set {@code help=true} if this option should disable validation of the remaining arguments:
- * If the {@code help} option is specified, no error message is generated for missing required options.
- * <p>
- * This attribute is useful for special options like help ({@code -h} and {@code --help} on unix,
- * {@code -?} and {@code -Help} on Windows) or version ({@code -V} and {@code --version} on unix,
- * {@code -Version} on Windows).
- * </p>
- * <p>
- * Note that the {@link #parse(String...)} method will not print help documentation. It will only set
- * the value of the annotated field. It is the responsibility of the caller to inspect the annotated fields
- * and take the appropriate action.
- * </p>
- * @return whether this option disables validation of the other arguments
- * @deprecated Use {@link #usageHelp()} and {@link #versionHelp()} instead. See {@link #printHelpIfRequested(List, PrintStream, CommandLine.Help.Ansi)}
- */
- boolean help() default false;
-
- /**
- * Set {@code usageHelp=true} if this option allows the user to request usage help. If this option is
- * specified on the command line, picocli will not validate the remaining arguments (so no "missing required
- * option" errors) and the {@link CommandLine#isUsageHelpRequested()} method will return {@code true}.
- * <p>
- * This attribute is useful for special options like help ({@code -h} and {@code --help} on unix,
- * {@code -?} and {@code -Help} on Windows).
- * </p>
- * <p>
- * Note that the {@link #parse(String...)} method will not print usage help documentation. It will only set
- * the value of the annotated field. It is the responsibility of the caller to inspect the annotated fields
- * and take the appropriate action.
- * </p>
- * @return whether this option allows the user to request usage help
- * @since 0.9.8
- */
- boolean usageHelp() default false;
-
- /**
- * Set {@code versionHelp=true} if this option allows the user to request version information. If this option is
- * specified on the command line, picocli will not validate the remaining arguments (so no "missing required
- * option" errors) and the {@link CommandLine#isVersionHelpRequested()} method will return {@code true}.
- * <p>
- * This attribute is useful for special options like version ({@code -V} and {@code --version} on unix,
- * {@code -Version} on Windows).
- * </p>
- * <p>
- * Note that the {@link #parse(String...)} method will not print version information. It will only set
- * the value of the annotated field. It is the responsibility of the caller to inspect the annotated fields
- * and take the appropriate action.
- * </p>
- * @return whether this option allows the user to request version information
- * @since 0.9.8
- */
- boolean versionHelp() default false;
-
- /**
- * Description of this option, used when generating the usage documentation.
- * @return the description of this option
- */
- String[] description() default {};
-
- /**
- * Specifies the minimum number of required parameters and the maximum number of accepted parameters.
- * If an option declares a positive arity, and the user specifies an insufficient number of parameters on the
- * command line, a {@link MissingParameterException} is thrown by the {@link #parse(String...)} method.
- * <p>
- * In many cases picocli can deduce the number of required parameters from the field's type.
- * By default, flags (boolean options) have arity zero,
- * and single-valued type fields (String, int, Integer, double, Double, File, Date, etc) have arity one.
- * Generally, fields with types that cannot hold multiple values can omit the {@code arity} attribute.
- * </p><p>
- * Fields used to capture options with arity two or higher should have a type that can hold multiple values,
- * like arrays or Collections. See {@link #type()} for strongly-typed Collection fields.
- * </p><p>
- * For example, if an option has 2 required parameters and any number of optional parameters,
- * specify {@code @Option(names = "-example", arity = "2..*")}.
- * </p>
- * <b>A note on boolean options</b>
- * <p>
- * By default picocli does not expect boolean options (also called "flags" or "switches") to have a parameter.
- * You can make a boolean option take a required parameter by annotating your field with {@code arity="1"}.
- * For example: </p>
- * <pre>@Option(names = "-v", arity = "1") boolean verbose;</pre>
- * <p>
- * Because this boolean field is defined with arity 1, the user must specify either {@code <program> -v false}
- * or {@code <program> -v true}
- * on the command line, or a {@link MissingParameterException} is thrown by the {@link #parse(String...)}
- * method.
- * </p><p>
- * To make the boolean parameter possible but optional, define the field with {@code arity = "0..1"}.
- * For example: </p>
- * <pre>@Option(names="-v", arity="0..1") boolean verbose;</pre>
- * <p>This will accept any of the below without throwing an exception:</p>
- * <pre>
- * -v
- * -v true
- * -v false
- * </pre>
- * @return how many arguments this option requires
- */
- String arity() default "";
-
- /**
- * Specify a {@code paramLabel} for the option parameter to be used in the usage help message. If omitted,
- * picocli uses the field name in fish brackets ({@code '<'} and {@code '>'}) by default. Example:
- * <pre>class Example {
- * @Option(names = {"-o", "--output"}, paramLabel="FILE", description="path of the output file")
- * private File out;
- * @Option(names = {"-j", "--jobs"}, arity="0..1", description="Allow N jobs at once; infinite jobs with no arg.")
- * private int maxJobs = -1;
- * }</pre>
- * <p>By default, the above gives a usage help message like the following:</p><pre>
- * Usage: <main class> [OPTIONS]
- * -o, --output FILE path of the output file
- * -j, --jobs [<maxJobs>] Allow N jobs at once; infinite jobs with no arg.
- * </pre>
- * @return name of the option parameter used in the usage help message
- */
- String paramLabel() default "";
-
- /** <p>
- * Optionally specify a {@code type} to control exactly what Class the option parameter should be converted
- * to. This may be useful when the field type is an interface or an abstract class. For example, a field can
- * be declared to have type {@code java.lang.Number}, and annotating {@code @Option(type=Short.class)}
- * ensures that the option parameter value is converted to a {@code Short} before setting the field value.
- * </p><p>
- * For array fields whose <em>component</em> type is an interface or abstract class, specify the concrete <em>component</em> type.
- * For example, a field with type {@code Number[]} may be annotated with {@code @Option(type=Short.class)}
- * to ensure that option parameter values are converted to {@code Short} before adding an element to the array.
- * </p><p>
- * Picocli will use the {@link ITypeConverter} that is
- * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert
- * the raw String values before modifying the field value.
- * </p><p>
- * Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields,
- * but starting from 2.0 picocli will infer the component type from the generic type's type arguments.
- * For example, for a field of type {@code Map<TimeUnit, Long>} picocli will know the option parameter
- * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit}
- * enum value, and the value should be converted to a {@code Long}. No {@code @Option(type=...)} type attribute
- * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound
- * as the Class to convert to, unless the {@code @Option} annotation specifies an explicit {@code type} attribute.
- * </p><p>
- * If the field type is a raw collection or a raw map, and you want it to contain other values than Strings,
- * or if the generic type's type arguments are interfaces or abstract classes, you may
- * specify a {@code type} attribute to control the Class that the option parameter should be converted to.
- * @return the type(s) to convert the raw String values
- */
- Class<?>[] type() default {};
-
- /**
- * Specify a regular expression to use to split option parameter values before applying them to the field.
- * All elements resulting from the split are added to the array or Collection. Ignored for single-value fields.
- * @return a regular expression to split option parameter values or {@code ""} if the value should not be split
- * @see String#split(String)
- */
- String split() default "";
-
- /**
- * Set {@code hidden=true} if this option should not be included in the usage documentation.
- * @return whether this option should be excluded from the usage message
- */
- boolean hidden() default false;
- }
- /**
- * <p>
- * Fields annotated with {@code @Parameters} will be initialized with positional parameters. By specifying the
- * {@link #index()} attribute you can pick which (or what range) of the positional parameters to apply. If no index
- * is specified, the field will get all positional parameters (so it should be an array or a collection).
- * </p><p>
- * When parsing the command line arguments, picocli first tries to match arguments to {@link Option Options}.
- * Positional parameters are the arguments that follow the options, or the arguments that follow a "--" (double
- * dash) argument on the command line.
- * </p><p>
- * For example:
- * </p>
- * <pre>import static picocli.CommandLine.*;
- *
- * public class MyCalcParameters {
- * @Parameters(type = BigDecimal.class, description = "Any number of input numbers")
- * private List<BigDecimal> files = new ArrayList<BigDecimal>();
- *
- * @Option(names = { "-h", "--help", "-?", "-help"}, help = true, description = "Display this help and exit")
- * private boolean help;
- * }
- * </pre><p>
- * A field cannot be annotated with both {@code @Parameters} and {@code @Option} or a {@code ParameterException}
- * is thrown.</p>
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.FIELD)
- public @interface Parameters {
- /** Specify an index ("0", or "1", etc.) to pick which of the command line arguments should be assigned to this
- * field. For array or Collection fields, you can also specify an index range ("0..3", or "2..*", etc.) to assign
- * a subset of the command line arguments to this field. The default is "*", meaning all command line arguments.
- * @return an index or range specifying which of the command line arguments should be assigned to this field
- */
- String index() default "*";
-
- /** Description of the parameter(s), used when generating the usage documentation.
- * @return the description of the parameter(s)
- */
- String[] description() default {};
-
- /**
- * Specifies the minimum number of required parameters and the maximum number of accepted parameters. If a
- * positive arity is declared, and the user specifies an insufficient number of parameters on the command line,
- * {@link MissingParameterException} is thrown by the {@link #parse(String...)} method.
- * <p>The default depends on the type of the parameter: booleans require no parameters, arrays and Collections
- * accept zero to any number of parameters, and any other type accepts one parameter.</p>
- * @return the range of minimum and maximum parameters accepted by this command
- */
- String arity() default "";
-
- /**
- * Specify a {@code paramLabel} for the parameter to be used in the usage help message. If omitted,
- * picocli uses the field name in fish brackets ({@code '<'} and {@code '>'}) by default. Example:
- * <pre>class Example {
- * @Parameters(paramLabel="FILE", description="path of the input FILE(s)")
- * private File[] inputFiles;
- * }</pre>
- * <p>By default, the above gives a usage help message like the following:</p><pre>
- * Usage: <main class> [FILE...]
- * [FILE...] path of the input FILE(s)
- * </pre>
- * @return name of the positional parameter used in the usage help message
- */
- String paramLabel() default "";
-
- /**
- * <p>
- * Optionally specify a {@code type} to control exactly what Class the positional parameter should be converted
- * to. This may be useful when the field type is an interface or an abstract class. For example, a field can
- * be declared to have type {@code java.lang.Number}, and annotating {@code @Parameters(type=Short.class)}
- * ensures that the positional parameter value is converted to a {@code Short} before setting the field value.
- * </p><p>
- * For array fields whose <em>component</em> type is an interface or abstract class, specify the concrete <em>component</em> type.
- * For example, a field with type {@code Number[]} may be annotated with {@code @Parameters(type=Short.class)}
- * to ensure that positional parameter values are converted to {@code Short} before adding an element to the array.
- * </p><p>
- * Picocli will use the {@link ITypeConverter} that is
- * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert
- * the raw String values before modifying the field value.
- * </p><p>
- * Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields,
- * but starting from 2.0 picocli will infer the component type from the generic type's type arguments.
- * For example, for a field of type {@code Map<TimeUnit, Long>} picocli will know the positional parameter
- * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit}
- * enum value, and the value should be converted to a {@code Long}. No {@code @Parameters(type=...)} type attribute
- * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound
- * as the Class to convert to, unless the {@code @Parameters} annotation specifies an explicit {@code type} attribute.
- * </p><p>
- * If the field type is a raw collection or a raw map, and you want it to contain other values than Strings,
- * or if the generic type's type arguments are interfaces or abstract classes, you may
- * specify a {@code type} attribute to control the Class that the positional parameter should be converted to.
- * @return the type(s) to convert the raw String values
- */
- Class<?>[] type() default {};
-
- /**
- * Specify a regular expression to use to split positional parameter values before applying them to the field.
- * All elements resulting from the split are added to the array or Collection. Ignored for single-value fields.
- * @return a regular expression to split operand values or {@code ""} if the value should not be split
- * @see String#split(String)
- */
- String split() default "";
-
- /**
- * Set {@code hidden=true} if this parameter should not be included in the usage message.
- * @return whether this parameter should be excluded from the usage message
- */
- boolean hidden() default false;
- }
-
- /**
- * <p>Annotate your class with {@code @Command} when you want more control over the format of the generated help
- * message.
- * </p><pre>
- * @Command(name = "Encrypt",
- * description = "Encrypt FILE(s), or standard input, to standard output or to the output file.",
- * footer = "Copyright (c) 2017")
- * public class Encrypt {
- * @Parameters(paramLabel = "FILE", type = File.class, description = "Any number of input files")
- * private List<File> files = new ArrayList<File>();
- *
- * @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
- * private File outputFile;
- * }</pre>
- * <p>
- * The structure of a help message looks like this:
- * </p><ul>
- * <li>[header]</li>
- * <li>[synopsis]: {@code Usage: <commandName> [OPTIONS] [FILE...]}</li>
- * <li>[description]</li>
- * <li>[parameter list]: {@code [FILE...] Any number of input files}</li>
- * <li>[option list]: {@code -h, --help prints this help message and exits}</li>
- * <li>[footer]</li>
- * </ul> */
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.TYPE, ElementType.LOCAL_VARIABLE, ElementType.PACKAGE})
- public @interface Command {
- /** Program name to show in the synopsis. If omitted, {@code "<main class>"} is used.
- * For {@linkplain #subcommands() declaratively added} subcommands, this attribute is also used
- * by the parser to recognize subcommands in the command line arguments.
- * @return the program name to show in the synopsis
- * @see Help#commandName */
- String name() default "<main class>";
-
- /** A list of classes to instantiate and register as subcommands. When registering subcommands declaratively
- * like this, you don't need to call the {@link CommandLine#addSubcommand(String, Object)} method. For example, this:
- * <pre>
- * @Command(subcommands = {
- * GitStatus.class,
- * GitCommit.class,
- * GitBranch.class })
- * public class Git { ... }
- *
- * CommandLine commandLine = new CommandLine(new Git());
- * </pre> is equivalent to this:
- * <pre>
- * // alternative: programmatically add subcommands.
- * // NOTE: in this case there should be no `subcommands` attribute on the @Command annotation.
- * @Command public class Git { ... }
- *
- * CommandLine commandLine = new CommandLine(new Git())
- * .addSubcommand("status", new GitStatus())
- * .addSubcommand("commit", new GitCommit())
- * .addSubcommand("branch", new GitBranch());
- * </pre>
- * @return the declaratively registered subcommands of this command, or an empty array if none
- * @see CommandLine#addSubcommand(String, Object)
- * @since 0.9.8
- */
- Class<?>[] subcommands() default {};
-
- /** String that separates options from option parameters. Default is {@code "="}. Spaces are also accepted.
- * @return the string that separates options from option parameters, used both when parsing and when generating usage help
- * @see Help#separator
- * @see CommandLine#setSeparator(String) */
- String separator() default "=";
-
- /** Version information for this command, to print to the console when the user specifies an
- * {@linkplain Option#versionHelp() option} to request version help. This is not part of the usage help message.
- *
- * @return a string or an array of strings with version information about this command.
- * @since 0.9.8
- * @see CommandLine#printVersionHelp(PrintStream)
- */
- String[] version() default {};
-
- /** Set the heading preceding the header section. May contain embedded {@linkplain java.util.Formatter format specifiers}.
- * @return the heading preceding the header section
- * @see Help#headerHeading(Object...) */
- String headerHeading() default "";
-
- /** Optional summary description of the command, shown before the synopsis.
- * @return summary description of the command
- * @see Help#header
- * @see Help#header(Object...) */
- String[] header() default {};
-
- /** Set the heading preceding the synopsis text. May contain embedded
- * {@linkplain java.util.Formatter format specifiers}. The default heading is {@code "Usage: "} (without a line
- * break between the heading and the synopsis text).
- * @return the heading preceding the synopsis text
- * @see Help#synopsisHeading(Object...) */
- String synopsisHeading() default "Usage: ";
-
- /** Specify {@code true} to generate an abbreviated synopsis like {@code "<main> [OPTIONS] [PARAMETERS...]"}.
- * By default, a detailed synopsis with individual option names and parameters is generated.
- * @return whether the synopsis should be abbreviated
- * @see Help#abbreviateSynopsis
- * @see Help#abbreviatedSynopsis()
- * @see Help#detailedSynopsis(Comparator, boolean) */
- boolean abbreviateSynopsis() default false;
-
- /** Specify one or more custom synopsis lines to display instead of an auto-generated synopsis.
- * @return custom synopsis text to replace the auto-generated synopsis
- * @see Help#customSynopsis
- * @see Help#customSynopsis(Object...) */
- String[] customSynopsis() default {};
-
- /** Set the heading preceding the description section. May contain embedded {@linkplain java.util.Formatter format specifiers}.
- * @return the heading preceding the description section
- * @see Help#descriptionHeading(Object...) */
- String descriptionHeading() default "";
-
- /** Optional text to display between the synopsis line(s) and the list of options.
- * @return description of this command
- * @see Help#description
- * @see Help#description(Object...) */
- String[] description() default {};
-
- /** Set the heading preceding the parameters list. May contain embedded {@linkplain java.util.Formatter format specifiers}.
- * @return the heading preceding the parameters list
- * @see Help#parameterListHeading(Object...) */
- String parameterListHeading() default "";
-
- /** Set the heading preceding the options list. May contain embedded {@linkplain java.util.Formatter format specifiers}.
- * @return the heading preceding the options list
- * @see Help#optionListHeading(Object...) */
- String optionListHeading() default "";
-
- /** Specify {@code false} to show Options in declaration order. The default is to sort alphabetically.
- * @return whether options should be shown in alphabetic order.
- * @see Help#sortOptions */
- boolean sortOptions() default true;
-
- /** Prefix required options with this character in the options list. The default is no marker: the synopsis
- * indicates which options and parameters are required.
- * @return the character to show in the options list to mark required options
- * @see Help#requiredOptionMarker */
- char requiredOptionMarker() default ' ';
-
- /** Specify {@code true} to show default values in the description column of the options list (except for
- * boolean options). False by default.
- * @return whether the default values for options and parameters should be shown in the description column
- * @see Help#showDefaultValues */
- boolean showDefaultValues() default false;
-
- /** Set the heading preceding the subcommands list. May contain embedded {@linkplain java.util.Formatter format specifiers}.
- * The default heading is {@code "Commands:%n"} (with a line break at the end).
- * @return the heading preceding the subcommands list
- * @see Help#commandListHeading(Object...) */
- String commandListHeading() default "Commands:%n";
-
- /** Set the heading preceding the footer section. May contain embedded {@linkplain java.util.Formatter format specifiers}.
- * @return the heading preceding the footer section
- * @see Help#footerHeading(Object...) */
- String footerHeading() default "";
-
- /** Optional text to display after the list of options.
- * @return text to display after the list of options
- * @see Help#footer
- * @see Help#footer(Object...) */
- String[] footer() default {};
- }
- /**
- * <p>
- * When parsing command line arguments and initializing
- * fields annotated with {@link Option @Option} or {@link Parameters @Parameters},
- * String values can be converted to any type for which a {@code ITypeConverter} is registered.
- * </p><p>
- * This interface defines the contract for classes that know how to convert a String into some domain object.
- * Custom converters can be registered with the {@link #registerConverter(Class, ITypeConverter)} method.
- * </p><p>
- * Java 8 lambdas make it easy to register custom type converters:
- * </p>
- * <pre>
- * commandLine.registerConverter(java.nio.file.Path.class, s -> java.nio.file.Paths.get(s));
- * commandLine.registerConverter(java.time.Duration.class, s -> java.time.Duration.parse(s));</pre>
- * <p>
- * Built-in type converters are pre-registered for the following java 1.5 types:
- * </p>
- * <ul>
- * <li>all primitive types</li>
- * <li>all primitive wrapper types: Boolean, Byte, Character, Double, Float, Integer, Long, Short</li>
- * <li>any enum</li>
- * <li>java.io.File</li>
- * <li>java.math.BigDecimal</li>
- * <li>java.math.BigInteger</li>
- * <li>java.net.InetAddress</li>
- * <li>java.net.URI</li>
- * <li>java.net.URL</li>
- * <li>java.nio.charset.Charset</li>
- * <li>java.sql.Time</li>
- * <li>java.util.Date</li>
- * <li>java.util.UUID</li>
- * <li>java.util.regex.Pattern</li>
- * <li>StringBuilder</li>
- * <li>CharSequence</li>
- * <li>String</li>
- * </ul>
- * @param <K> the type of the object that is the result of the conversion
- */
- public interface ITypeConverter<K> {
- /**
- * Converts the specified command line argument value to some domain object.
- * @param value the command line argument String value
- * @return the resulting domain object
- * @throws Exception an exception detailing what went wrong during the conversion
- */
- K convert(String value) throws Exception;
- }
- /** Describes the number of parameters required and accepted by an option or a positional parameter.
- * @since 0.9.7
- */
- public static class Range implements Comparable<Range> {
- /** Required number of parameters for an option or positional parameter. */
- public final int min;
- /** Maximum accepted number of parameters for an option or positional parameter. */
- public final int max;
-
- public final boolean isVariable;
- private final boolean isUnspecified;
- private final String originalValue;
-
- /** Constructs a new Range object with the specified parameters.
- * @param min minimum number of required parameters
- * @param max maximum number of allowed parameters (or Integer.MAX_VALUE if variable)
- * @param variable {@code true} if any number or parameters is allowed, {@code false} otherwise
- * @param unspecified {@code true} if no arity was specified on the option/parameter (value is based on type)
- * @param originalValue the original value that was specified on the option or parameter
- */
- public Range(
- final int min,
- final int max,
- final boolean variable,
- final boolean unspecified,
- final String originalValue) {
- this.min = min;
- this.max = max;
- this.isVariable = variable;
- this.isUnspecified = unspecified;
- this.originalValue = originalValue;
- }
- /** Returns a new {@code Range} based on the {@link Option#arity()} annotation on the specified field,
- * or the field type's default arity if no arity was specified.
- * @param field the field whose Option annotation to inspect
- * @return a new {@code Range} based on the Option arity annotation on the specified field */
- public static Range optionArity(final Field field) {
- return field.isAnnotationPresent(Option.class)
- ? adjustForType(
- Range.valueOf(field.getAnnotation(Option.class).arity()), field)
- : new Range(0, 0, false, true, "0");
- }
- /** Returns a new {@code Range} based on the {@link Parameters#arity()} annotation on the specified field,
- * or the field type's default arity if no arity was specified.
- * @param field the field whose Parameters annotation to inspect
- * @return a new {@code Range} based on the Parameters arity annotation on the specified field */
- public static Range parameterArity(final Field field) {
- return field.isAnnotationPresent(Parameters.class)
- ? adjustForType(
- Range.valueOf(field.getAnnotation(Parameters.class).arity()), field)
- : new Range(0, 0, false, true, "0");
- }
- /** Returns a new {@code Range} based on the {@link Parameters#index()} annotation on the specified field.
- * @param field the field whose Parameters annotation to inspect
- * @return a new {@code Range} based on the Parameters index annotation on the specified field */
- public static Range parameterIndex(final Field field) {
- return field.isAnnotationPresent(Parameters.class)
- ? Range.valueOf(field.getAnnotation(Parameters.class).index())
- : new Range(0, 0, false, true, "0");
- }
-
- static Range adjustForType(final Range result, final Field field) {
- return result.isUnspecified ? defaultArity(field) : result;
- }
- /** Returns the default arity {@code Range}: for {@link Option options} this is 0 for booleans and 1 for
- * other types, for {@link Parameters parameters} booleans have arity 0, arrays or Collections have
- * arity "0..*", and other types have arity 1.
- * @param field the field whose default arity to return
- * @return a new {@code Range} indicating the default arity of the specified field
- * @since 2.0 */
- public static Range defaultArity(final Field field) {
- final Class<?> type = field.getType();
- if (field.isAnnotationPresent(Option.class)) {
- return defaultArity(type);
- }
- if (isMultiValue(type)) {
- return Range.valueOf("0..1");
- }
- return Range.valueOf("1"); // for single-valued fields (incl. boolean positional parameters)
- }
- /** Returns the default arity {@code Range} for {@link Option options}: booleans have arity 0, other types have arity 1.
- * @param type the type whose default arity to return
- * @return a new {@code Range} indicating the default arity of the specified type */
- public static Range defaultArity(final Class<?> type) {
- return isBoolean(type) ? Range.valueOf("0") : Range.valueOf("1");
- }
-
- private int size() {
- return 1 + max - min;
- }
-
- static Range parameterCapacity(final Field field) {
- final Range arity = parameterArity(field);
- if (!isMultiValue(field)) {
- return arity;
- }
- final Range index = parameterIndex(field);
- if (arity.max == 0) {
- return arity;
- }
- if (index.size() == 1) {
- return arity;
- }
- if (index.isVariable) {
- return Range.valueOf(arity.min + "..*");
- }
- if (arity.size() == 1) {
- return Range.valueOf(arity.min * index.size() + "");
- }
- if (arity.isVariable) {
- return Range.valueOf(arity.min * index.size() + "..*");
- }
- return Range.valueOf(arity.min * index.size() + ".." + arity.max * index.size());
- }
- /** Leniently parses the specified String as an {@code Range} value and return the result. A range string can
- * be a fixed integer value or a range of the form {@code MIN_VALUE + ".." + MAX_VALUE}. If the
- * {@code MIN_VALUE} string is not numeric, the minimum is zero. If the {@code MAX_VALUE} is not numeric, the
- * range is taken to be variable and the maximum is {@code Integer.MAX_VALUE}.
- * @param range the value range string to parse
- * @return a new {@code Range} value */
- public static Range valueOf(String range) {
- range = range.trim();
- final boolean unspecified = range.length() == 0 || range.startsWith(".."); // || range.endsWith("..");
- int min = -1, max = -1;
- boolean variable = false;
- int dots = -1;
- if ((dots = range.indexOf("..")) >= 0) {
- min = parseInt(range.substring(0, dots), 0);
- max = parseInt(range.substring(dots + 2), Integer.MAX_VALUE);
- variable = max == Integer.MAX_VALUE;
- } else {
- max = parseInt(range, Integer.MAX_VALUE);
- variable = max == Integer.MAX_VALUE;
- min = variable ? 0 : max;
- }
- return new Range(min, max, variable, unspecified, range);
- }
-
- private static int parseInt(final String str, final int defaultValue) {
- try {
- return Integer.parseInt(str);
- } catch (final Exception ex) {
- return defaultValue;
- }
- }
- /** Returns a new Range object with the {@code min} value replaced by the specified value.
- * The {@code max} of the returned Range is guaranteed not to be less than the new {@code min} value.
- * @param newMin the {@code min} value of the returned Range object
- * @return a new Range object with the specified {@code min} value */
- public Range min(final int newMin) {
- return new Range(newMin, Math.max(newMin, max), isVariable, isUnspecified, originalValue);
- }
-
- /** Returns a new Range object with the {@code max} value replaced by the specified value.
- * The {@code min} of the returned Range is guaranteed not to be greater than the new {@code max} value.
- * @param newMax the {@code max} value of the returned Range object
- * @return a new Range object with the specified {@code max} value */
- public Range max(final int newMax) {
- return new Range(Math.min(min, newMax), newMax, isVariable, isUnspecified, originalValue);
- }
-
- /**
- * Returns {@code true} if this Range includes the specified value, {@code false} otherwise.
- * @param value the value to check
- * @return {@code true} if the specified value is not less than the minimum and not greater than the maximum of this Range
- */
- public boolean contains(final int value) {
- return min <= value && max >= value;
- }
-
- @Override
- public boolean equals(final Object object) {
- if (!(object instanceof Range)) {
- return false;
- }
- final Range other = (Range) object;
- return other.max == this.max && other.min == this.min && other.isVariable == this.isVariable;
- }
-
- @Override
- public int hashCode() {
- return ((17 * 37 + max) * 37 + min) * 37 + (isVariable ? 1 : 0);
- }
-
- @Override
- public String toString() {
- return min == max ? String.valueOf(min) : min + ".." + (isVariable ? "*" : max);
- }
-
- @Override
- public int compareTo(final Range other) {
- final int result = min - other.min;
- return (result == 0) ? max - other.max : result;
- }
- }
-
- static void init(
- final Class<?> cls,
- final List<Field> requiredFields,
- final Map<String, Field> optionName2Field,
- final Map<Character, Field> singleCharOption2Field,
- final List<Field> positionalParametersFields) {
- final Field[] declaredFields = cls.getDeclaredFields();
- for (final Field field : declaredFields) {
- field.setAccessible(true);
- if (field.isAnnotationPresent(Option.class)) {
- final Option option = field.getAnnotation(Option.class);
- if (option.required()) {
- requiredFields.add(field);
- }
- for (final String name : option.names()) { // cannot be null or empty
- final Field existing = optionName2Field.put(name, field);
- if (existing != null && existing != field) {
- throw DuplicateOptionAnnotationsException.create(name, field, existing);
- }
- if (name.length() == 2 && name.startsWith("-")) {
- final char flag = name.charAt(1);
- final Field existing2 = singleCharOption2Field.put(flag, field);
- if (existing2 != null && existing2 != field) {
- throw DuplicateOptionAnnotationsException.create(name, field, existing2);
- }
- }
- }
- }
- if (field.isAnnotationPresent(Parameters.class)) {
- if (field.isAnnotationPresent(Option.class)) {
- throw new DuplicateOptionAnnotationsException(
- "A field can be either @Option or @Parameters, but '" + field.getName() + "' is both.");
- }
- positionalParametersFields.add(field);
- final Range arity = Range.parameterArity(field);
- if (arity.min > 0) {
- requiredFields.add(field);
- }
- }
- }
- }
-
- static void validatePositionalParameters(final List<Field> positionalParametersFields) {
- int min = 0;
- for (final Field field : positionalParametersFields) {
- final Range index = Range.parameterIndex(field);
- if (index.min > min) {
- throw new ParameterIndexGapException("Missing field annotated with @Parameter(index=" + min
- + "). Nearest field '" + field.getName() + "' has index=" + index.min);
- }
- min = Math.max(min, index.max);
- min = min == Integer.MAX_VALUE ? min : min + 1;
- }
- }
-
- private static <T> Stack<T> reverse(final Stack<T> stack) {
- Collections.reverse(stack);
- return stack;
- }
- /**
- * Helper class responsible for processing command line arguments.
- */
- private class Interpreter {
- private final Map<String, CommandLine> commands = new LinkedHashMap<String, CommandLine>();
- private final Map<Class<?>, ITypeConverter<?>> converterRegistry = new HashMap<Class<?>, ITypeConverter<?>>();
- private final Map<String, Field> optionName2Field = new HashMap<String, Field>();
- private final Map<Character, Field> singleCharOption2Field = new HashMap<Character, Field>();
- private final List<Field> requiredFields = new ArrayList<Field>();
- private final List<Field> positionalParametersFields = new ArrayList<Field>();
- private final Object command;
- private boolean isHelpRequested;
- private String separator = Help.DEFAULT_SEPARATOR;
- private int position;
-
- Interpreter(final Object command) {
- converterRegistry.put(Path.class, new BuiltIn.PathConverter());
- converterRegistry.put(Object.class, new BuiltIn.StringConverter());
- converterRegistry.put(String.class, new BuiltIn.StringConverter());
- converterRegistry.put(StringBuilder.class, new BuiltIn.StringBuilderConverter());
- converterRegistry.put(CharSequence.class, new BuiltIn.CharSequenceConverter());
- converterRegistry.put(Byte.class, new BuiltIn.ByteConverter());
- converterRegistry.put(Byte.TYPE, new BuiltIn.ByteConverter());
- converterRegistry.put(Boolean.class, new BuiltIn.BooleanConverter());
- converterRegistry.put(Boolean.TYPE, new BuiltIn.BooleanConverter());
- converterRegistry.put(Character.class, new BuiltIn.CharacterConverter());
- converterRegistry.put(Character.TYPE, new BuiltIn.CharacterConverter());
- converterRegistry.put(Short.class, new BuiltIn.ShortConverter());
- converterRegistry.put(Short.TYPE, new BuiltIn.ShortConverter());
- converterRegistry.put(Integer.class, new BuiltIn.IntegerConverter());
- converterRegistry.put(Integer.TYPE, new BuiltIn.IntegerConverter());
- converterRegistry.put(Long.class, new BuiltIn.LongConverter());
- converterRegistry.put(Long.TYPE, new BuiltIn.LongConverter());
- converterRegistry.put(Float.class, new BuiltIn.FloatConverter());
- converterRegistry.put(Float.TYPE, new BuiltIn.FloatConverter());
- converterRegistry.put(Double.class, new BuiltIn.DoubleConverter());
- converterRegistry.put(Double.TYPE, new BuiltIn.DoubleConverter());
- converterRegistry.put(File.class, new BuiltIn.FileConverter());
- converterRegistry.put(URI.class, new BuiltIn.URIConverter());
- converterRegistry.put(URL.class, new BuiltIn.URLConverter());
- converterRegistry.put(Date.class, new BuiltIn.ISO8601DateConverter());
- converterRegistry.put(Time.class, new BuiltIn.ISO8601TimeConverter());
- converterRegistry.put(BigDecimal.class, new BuiltIn.BigDecimalConverter());
- converterRegistry.put(BigInteger.class, new BuiltIn.BigIntegerConverter());
- converterRegistry.put(Charset.class, new BuiltIn.CharsetConverter());
- converterRegistry.put(InetAddress.class, new BuiltIn.InetAddressConverter());
- converterRegistry.put(Pattern.class, new BuiltIn.PatternConverter());
- converterRegistry.put(UUID.class, new BuiltIn.UUIDConverter());
-
- this.command = Assert.notNull(command, "command");
- Class<?> cls = command.getClass();
- String declaredName = null;
- String declaredSeparator = null;
- boolean hasCommandAnnotation = false;
- while (cls != null) {
- init(cls, requiredFields, optionName2Field, singleCharOption2Field, positionalParametersFields);
- if (cls.isAnnotationPresent(Command.class)) {
- hasCommandAnnotation = true;
- final Command cmd = cls.getAnnotation(Command.class);
- declaredSeparator = (declaredSeparator == null) ? cmd.separator() : declaredSeparator;
- declaredName = (declaredName == null) ? cmd.name() : declaredName;
- CommandLine.this.versionLines.addAll(Arrays.asList(cmd.version()));
-
- for (final Class<?> sub : cmd.subcommands()) {
- final Command subCommand = sub.getAnnotation(Command.class);
- if (subCommand == null || Help.DEFAULT_COMMAND_NAME.equals(subCommand.name())) {
- throw new InitializationException("Subcommand " + sub.getName()
- + " is missing the mandatory @Command annotation with a 'name' attribute");
- }
- try {
- final Constructor<?> constructor = sub.getDeclaredConstructor();
- constructor.setAccessible(true);
- final CommandLine commandLine = toCommandLine(constructor.newInstance());
- commandLine.parent = CommandLine.this;
- commands.put(subCommand.name(), commandLine);
- } catch (final InitializationException ex) {
- throw ex;
- } catch (final NoSuchMethodException ex) {
- throw new InitializationException(
- "Cannot instantiate subcommand " + sub.getName() + ": the class has no constructor",
- ex);
- } catch (final Exception ex) {
- throw new InitializationException(
- "Could not instantiate and add subcommand " + sub.getName() + ": " + ex, ex);
- }
- }
- }
- cls = cls.getSuperclass();
- }
- separator = declaredSeparator != null ? declaredSeparator : separator;
- CommandLine.this.commandName = declaredName != null ? declaredName : CommandLine.this.commandName;
- Collections.sort(positionalParametersFields, new PositionalParametersSorter());
- validatePositionalParameters(positionalParametersFields);
-
- if (positionalParametersFields.isEmpty() && optionName2Field.isEmpty() && !hasCommandAnnotation) {
- throw new InitializationException(command + " (" + command.getClass()
- + ") is not a command: it has no @Command, @Option or @Parameters annotations");
- }
- }
-
- /**
- * Entry point into parsing command line arguments.
- * @param args the command line arguments
- * @return a list with all commands and subcommands initialized by this method
- * @throws ParameterException if the specified command line arguments are invalid
- */
- List<CommandLine> parse(final String... args) {
- Assert.notNull(args, "argument array");
- if (tracer.isInfo()) {
- tracer.info("Parsing %d command line args %s%n", args.length, Arrays.toString(args));
- }
- final Stack<String> arguments = new Stack<String>();
- for (int i = args.length - 1; i >= 0; i--) {
- arguments.push(args[i]);
- }
- final List<CommandLine> result = new ArrayList<CommandLine>();
- parse(result, arguments, args);
- return result;
- }
-
- private void parse(
- final List<CommandLine> parsedCommands,
- final Stack<String> argumentStack,
- final String[] originalArgs) {
- // first reset any state in case this CommandLine instance is being reused
- isHelpRequested = false;
- CommandLine.this.versionHelpRequested = false;
- CommandLine.this.usageHelpRequested = false;
-
- final Class<?> cmdClass = this.command.getClass();
- if (tracer.isDebug()) {
- tracer.debug(
- "Initializing %s: %d options, %d positional parameters, %d required, %d subcommands.%n",
- cmdClass.getName(),
- new HashSet<Field>(optionName2Field.values()).size(),
- positionalParametersFields.size(),
- requiredFields.size(),
- commands.size());
- }
- parsedCommands.add(CommandLine.this);
- final List<Field> required = new ArrayList<Field>(requiredFields);
- final Set<Field> initialized = new HashSet<Field>();
- Collections.sort(required, new PositionalParametersSorter());
- try {
- processArguments(parsedCommands, argumentStack, required, initialized, originalArgs);
- } catch (final ParameterException ex) {
- throw ex;
- } catch (final Exception ex) {
- final int offendingArgIndex = originalArgs.length - argumentStack.size() - 1;
- final String arg = offendingArgIndex >= 0 && offendingArgIndex < originalArgs.length
- ? originalArgs[offendingArgIndex]
- : "?";
- throw ParameterException.create(CommandLine.this, ex, arg, offendingArgIndex, originalArgs);
- }
- if (!isAnyHelpRequested() && !required.isEmpty()) {
- for (final Field missing : required) {
- if (missing.isAnnotationPresent(Option.class)) {
- throw MissingParameterException.create(CommandLine.this, required, separator);
- } else {
- assertNoMissingParameters(missing, Range.parameterArity(missing).min, argumentStack);
- }
- }
- }
- if (!unmatchedArguments.isEmpty()) {
- if (!isUnmatchedArgumentsAllowed()) {
- throw new UnmatchedArgumentException(CommandLine.this, unmatchedArguments);
- }
- if (tracer.isWarn()) {
- tracer.warn("Unmatched arguments: %s%n", unmatchedArguments);
- }
- }
- }
-
- private void processArguments(
- final List<CommandLine> parsedCommands,
- final Stack<String> args,
- final Collection<Field> required,
- final Set<Field> initialized,
- final String[] originalArgs)
- throws Exception {
- // arg must be one of:
- // 1. the "--" double dash separating options from positional arguments
- // 1. a stand-alone flag, like "-v" or "--verbose": no value required, must map to boolean or Boolean field
- // 2. a short option followed by an argument, like "-f file" or "-ffile": may map to any type of field
- // 3. a long option followed by an argument, like "-file out.txt" or "-file=out.txt"
- // 3. one or more remaining arguments without any associated options. Must be the last in the list.
- // 4. a combination of stand-alone options, like "-vxr". Equivalent to "-v -x -r", "-v true -x true -r true"
- // 5. a combination of stand-alone options and one option with an argument, like "-vxrffile"
-
- while (!args.isEmpty()) {
- String arg = args.pop();
- if (tracer.isDebug()) {
- tracer.debug(
- "Processing argument '%s'. Remainder=%s%n", arg, reverse((Stack<String>) args.clone()));
- }
-
- // Double-dash separates options from positional arguments.
- // If found, then interpret the remaining args as positional parameters.
- if ("--".equals(arg)) {
- tracer.info("Found end-of-options delimiter '--'. Treating remainder as positional parameters.%n");
- processRemainderAsPositionalParameters(required, initialized, args);
- return; // we are done
- }
-
- // if we find another command, we are done with the current command
- if (commands.containsKey(arg)) {
- if (!isHelpRequested && !required.isEmpty()) { // ensure current command portion is valid
- throw MissingParameterException.create(CommandLine.this, required, separator);
- }
- if (tracer.isDebug()) {
- tracer.debug(
- "Found subcommand '%s' (%s)%n",
- arg,
- commands.get(arg).interpreter.command.getClass().getName());
- }
- commands.get(arg).interpreter.parse(parsedCommands, args, originalArgs);
- return; // remainder done by the command
- }
-
- // First try to interpret the argument as a single option (as opposed to a compact group of options).
- // A single option may be without option parameters, like "-v" or "--verbose" (a boolean value),
- // or an option may have one or more option parameters.
- // A parameter may be attached to the option.
- boolean paramAttachedToOption = false;
- final int separatorIndex = arg.indexOf(separator);
- if (separatorIndex > 0) {
- final String key = arg.substring(0, separatorIndex);
- // be greedy. Consume the whole arg as an option if possible.
- if (optionName2Field.containsKey(key) && !optionName2Field.containsKey(arg)) {
- paramAttachedToOption = true;
- final String optionParam = arg.substring(separatorIndex + separator.length());
- args.push(optionParam);
- arg = key;
- if (tracer.isDebug()) {
- tracer.debug("Separated '%s' option from '%s' option parameter%n", key, optionParam);
- }
- } else {
- if (tracer.isDebug()) {
- tracer.debug(
- "'%s' contains separator '%s' but '%s' is not a known option%n",
- arg, separator, key);
- }
- }
- } else {
- if (tracer.isDebug()) {
- tracer.debug("'%s' cannot be separated into <option>%s<option-parameter>%n", arg, separator);
- }
- }
- if (optionName2Field.containsKey(arg)) {
- processStandaloneOption(required, initialized, arg, args, paramAttachedToOption);
- }
- // Compact (single-letter) options can be grouped with other options or with an argument.
- // only single-letter options can be combined with other options or with an argument
- else if (arg.length() > 2 && arg.startsWith("-")) {
- if (tracer.isDebug()) {
- tracer.debug("Trying to process '%s' as clustered short options%n", arg, args);
- }
- processClusteredShortOptions(required, initialized, arg, args);
- }
- // The argument could not be interpreted as an option.
- // We take this to mean that the remainder are positional arguments
- else {
- args.push(arg);
- if (tracer.isDebug()) {
- tracer.debug(
- "Could not find option '%s', deciding whether to treat as unmatched option or positional parameter...%n",
- arg);
- }
- if (resemblesOption(arg)) {
- handleUnmatchedArguments(args.pop());
- continue;
- } // #149
- if (tracer.isDebug()) {
- tracer.debug(
- "No option named '%s' found. Processing remainder as positional parameters%n", arg);
- }
- processPositionalParameter(required, initialized, args);
- }
- }
- }
-
- private boolean resemblesOption(final String arg) {
- int count = 0;
- for (final String optionName : optionName2Field.keySet()) {
- for (int i = 0; i < arg.length(); i++) {
- if (optionName.length() > i && arg.charAt(i) == optionName.charAt(i)) {
- count++;
- } else {
- break;
- }
- }
- }
- final boolean result = count > 0
- && count * 10
- >= optionName2Field.size()
- * 9; // at least one prefix char in common with 9 out of 10 options
- if (tracer.isDebug()) {
- tracer.debug(
- "%s %s an option: %d matching prefix chars out of %d option names%n",
- arg, (result ? "resembles" : "doesn't resemble"), count, optionName2Field.size());
- }
- return result;
- }
-
- private void handleUnmatchedArguments(final String arg) {
- final Stack<String> args = new Stack<String>();
- args.add(arg);
- handleUnmatchedArguments(args);
- }
-
- private void handleUnmatchedArguments(final Stack<String> args) {
- while (!args.isEmpty()) {
- unmatchedArguments.add(args.pop());
- } // addAll would give args in reverse order
- }
-
- private void processRemainderAsPositionalParameters(
- final Collection<Field> required, final Set<Field> initialized, final Stack<String> args)
- throws Exception {
- while (!args.empty()) {
- processPositionalParameter(required, initialized, args);
- }
- }
-
- private void processPositionalParameter(
- final Collection<Field> required, final Set<Field> initialized, final Stack<String> args)
- throws Exception {
- if (tracer.isDebug()) {
- tracer.debug(
- "Processing next arg as a positional parameter at index=%d. Remainder=%s%n",
- position, reverse((Stack<String>) args.clone()));
- }
- int consumed = 0;
- for (final Field positionalParam : positionalParametersFields) {
- final Range indexRange = Range.parameterIndex(positionalParam);
- if (!indexRange.contains(position)) {
- continue;
- }
- @SuppressWarnings("unchecked")
- final Stack<String> argsCopy = (Stack<String>) args.clone();
- final Range arity = Range.parameterArity(positionalParam);
- if (tracer.isDebug()) {
- tracer.debug(
- "Position %d is in index range %s. Trying to assign args to %s, arity=%s%n",
- position, indexRange, positionalParam, arity);
- }
- assertNoMissingParameters(positionalParam, arity.min, argsCopy);
- final int originalSize = argsCopy.size();
- applyOption(
- positionalParam,
- Parameters.class,
- arity,
- false,
- argsCopy,
- initialized,
- "args[" + indexRange + "] at position " + position);
- final int count = originalSize - argsCopy.size();
- if (count > 0) {
- required.remove(positionalParam);
- }
- consumed = Math.max(consumed, count);
- }
- // remove processed args from the stack
- for (int i = 0; i < consumed; i++) {
- args.pop();
- }
- position += consumed;
- if (tracer.isDebug()) {
- tracer.debug("Consumed %d arguments, moving position to index %d.%n", consumed, position);
- }
- if (consumed == 0 && !args.isEmpty()) {
- handleUnmatchedArguments(args.pop());
- }
- }
-
- private void processStandaloneOption(
- final Collection<Field> required,
- final Set<Field> initialized,
- final String arg,
- final Stack<String> args,
- final boolean paramAttachedToKey)
- throws Exception {
- final Field field = optionName2Field.get(arg);
- required.remove(field);
- Range arity = Range.optionArity(field);
- if (paramAttachedToKey) {
- arity = arity.min(Math.max(1, arity.min)); // if key=value, minimum arity is at least 1
- }
- if (tracer.isDebug()) {
- tracer.debug("Found option named '%s': field %s, arity=%s%n", arg, field, arity);
- }
- applyOption(field, Option.class, arity, paramAttachedToKey, args, initialized, "option " + arg);
- }
-
- private void processClusteredShortOptions(
- final Collection<Field> required,
- final Set<Field> initialized,
- final String arg,
- final Stack<String> args)
- throws Exception {
- final String prefix = arg.substring(0, 1);
- String cluster = arg.substring(1);
- boolean paramAttachedToOption = true;
- do {
- if (cluster.length() > 0 && singleCharOption2Field.containsKey(cluster.charAt(0))) {
- final Field field = singleCharOption2Field.get(cluster.charAt(0));
- Range arity = Range.optionArity(field);
- final String argDescription = "option " + prefix + cluster.charAt(0);
- if (tracer.isDebug()) {
- tracer.debug(
- "Found option '%s%s' in %s: field %s, arity=%s%n",
- prefix, cluster.charAt(0), arg, field, arity);
- }
- required.remove(field);
- cluster = cluster.length() > 0 ? cluster.substring(1) : "";
- paramAttachedToOption = cluster.length() > 0;
- if (cluster.startsWith(separator)) { // attached with separator, like -f=FILE or -v=true
- cluster = cluster.substring(separator.length());
- arity = arity.min(Math.max(1, arity.min)); // if key=value, minimum arity is at least 1
- }
- if (arity.min > 0 && !empty(cluster)) {
- if (tracer.isDebug()) {
- tracer.debug("Trying to process '%s' as option parameter%n", cluster);
- }
- }
- // arity may be >= 1, or
- // arity <= 0 && !cluster.startsWith(separator)
- // e.g., boolean @Option("-v", arity=0, varargs=true); arg "-rvTRUE", remainder cluster="TRUE"
- if (!empty(cluster)) {
- args.push(cluster); // interpret remainder as option parameter
- }
- final int consumed = applyOption(
- field, Option.class, arity, paramAttachedToOption, args, initialized, argDescription);
- // only return if cluster (and maybe more) was consumed, otherwise continue do-while loop
- if (empty(cluster) || consumed > 0 || args.isEmpty()) {
- return;
- }
- cluster = args.pop();
- } else { // cluster is empty || cluster.charAt(0) is not a short option key
- if (cluster.length() == 0) { // we finished parsing a group of short options like -rxv
- return; // return normally and parse the next arg
- }
- // We get here when the remainder of the cluster group is neither an option,
- // nor a parameter that the last option could consume.
- if (arg.endsWith(cluster)) {
- args.push(paramAttachedToOption ? prefix + cluster : cluster);
- if (args.peek().equals(arg)) { // #149 be consistent between unmatched short and long options
- if (tracer.isDebug()) {
- tracer.debug(
- "Could not match any short options in %s, deciding whether to treat as unmatched option or positional parameter...%n",
- arg);
- }
- if (resemblesOption(arg)) {
- handleUnmatchedArguments(args.pop());
- return;
- } // #149
- processPositionalParameter(required, initialized, args);
- return;
- }
- // remainder was part of a clustered group that could not be completely parsed
- if (tracer.isDebug()) {
- tracer.debug("No option found for %s in %s%n", cluster, arg);
- }
- handleUnmatchedArguments(args.pop());
- } else {
- args.push(cluster);
- if (tracer.isDebug()) {
- tracer.debug("%s is not an option parameter for %s%n", cluster, arg);
- }
- processPositionalParameter(required, initialized, args);
- }
- return;
- }
- } while (true);
- }
-
- private int applyOption(
- final Field field,
- final Class<?> annotation,
- final Range arity,
- final boolean valueAttachedToOption,
- final Stack<String> args,
- final Set<Field> initialized,
- final String argDescription)
- throws Exception {
- updateHelpRequested(field);
- final int length = args.size();
- assertNoMissingParameters(field, arity.min, args);
-
- Class<?> cls = field.getType();
- if (cls.isArray()) {
- return applyValuesToArrayField(field, annotation, arity, args, cls, argDescription);
- }
- if (Collection.class.isAssignableFrom(cls)) {
- return applyValuesToCollectionField(field, annotation, arity, args, cls, argDescription);
- }
- if (Map.class.isAssignableFrom(cls)) {
- return applyValuesToMapField(field, annotation, arity, args, cls, argDescription);
- }
- cls = getTypeAttribute(field)[
- 0]; // field may be interface/abstract type, use annotation to get concrete type
- return applyValueToSingleValuedField(field, arity, args, cls, initialized, argDescription);
- }
-
- private int applyValueToSingleValuedField(
- final Field field,
- final Range arity,
- final Stack<String> args,
- final Class<?> cls,
- final Set<Field> initialized,
- final String argDescription)
- throws Exception {
- final boolean noMoreValues = args.isEmpty();
- String value = args.isEmpty() ? null : trim(args.pop()); // unquote the value
- int result = arity.min; // the number or args we need to consume
-
- // special logic for booleans: BooleanConverter accepts only "true" or "false".
- if ((cls == Boolean.class || cls == Boolean.TYPE) && arity.min <= 0) {
-
- // boolean option with arity = 0..1 or 0..*: value MAY be a param
- if (arity.max > 0 && ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value))) {
- result = 1; // if it is a varargs we only consume 1 argument if it is a boolean value
- } else {
- if (value != null) {
- args.push(value); // we don't consume the value
- }
- final Boolean currentValue = (Boolean) field.get(command);
- value = String.valueOf(currentValue == null || !currentValue); // #147 toggle existing boolean value
- }
- }
- if (noMoreValues && value == null) {
- return 0;
- }
- final ITypeConverter<?> converter = getTypeConverter(cls, field);
- final Object newValue = tryConvert(field, -1, converter, value, cls);
- final Object oldValue = field.get(command);
- TraceLevel level = TraceLevel.INFO;
- String traceMessage = "Setting %s field '%s.%s' to '%5$s' (was '%4$s') for %6$s%n";
- if (initialized != null) {
- if (initialized.contains(field)) {
- if (!isOverwrittenOptionsAllowed()) {
- throw new OverwrittenOptionException(
- CommandLine.this, optionDescription("", field, 0) + " should be specified only once");
- }
- level = TraceLevel.WARN;
- traceMessage = "Overwriting %s field '%s.%s' value '%s' with '%s' for %s%n";
- }
- initialized.add(field);
- }
- if (tracer.level.isEnabled(level)) {
- level.print(
- tracer,
- traceMessage,
- field.getType().getSimpleName(),
- field.getDeclaringClass().getSimpleName(),
- field.getName(),
- String.valueOf(oldValue),
- String.valueOf(newValue),
- argDescription);
- }
- field.set(command, newValue);
- return result;
- }
-
- private int applyValuesToMapField(
- final Field field,
- final Class<?> annotation,
- final Range arity,
- final Stack<String> args,
- final Class<?> cls,
- final String argDescription)
- throws Exception {
- final Class<?>[] classes = getTypeAttribute(field);
- if (classes.length < 2) {
- throw new ParameterException(
- CommandLine.this,
- "Field " + field + " needs two types (one for the map key, one for the value) but only has "
- + classes.length + " types configured.");
- }
- final ITypeConverter<?> keyConverter = getTypeConverter(classes[0], field);
- final ITypeConverter<?> valueConverter = getTypeConverter(classes[1], field);
- Map<Object, Object> result = (Map<Object, Object>) field.get(command);
- if (result == null) {
- result = createMap(cls);
- field.set(command, result);
- }
- final int originalSize = result.size();
- consumeMapArguments(field, arity, args, classes, keyConverter, valueConverter, result, argDescription);
- return result.size() - originalSize;
- }
-
- private void consumeMapArguments(
- final Field field,
- final Range arity,
- final Stack<String> args,
- final Class<?>[] classes,
- final ITypeConverter<?> keyConverter,
- final ITypeConverter<?> valueConverter,
- final Map<Object, Object> result,
- final String argDescription)
- throws Exception {
- // first do the arity.min mandatory parameters
- for (int i = 0; i < arity.min; i++) {
- consumeOneMapArgument(
- field, arity, args, classes, keyConverter, valueConverter, result, i, argDescription);
- }
- // now process the varargs if any
- for (int i = arity.min; i < arity.max && !args.isEmpty(); i++) {
- if (!field.isAnnotationPresent(Parameters.class)) {
- if (commands.containsKey(args.peek()) || isOption(args.peek())) {
- return;
- }
- }
- consumeOneMapArgument(
- field, arity, args, classes, keyConverter, valueConverter, result, i, argDescription);
- }
- }
-
- private void consumeOneMapArgument(
- final Field field,
- final Range arity,
- final Stack<String> args,
- final Class<?>[] classes,
- final ITypeConverter<?> keyConverter,
- final ITypeConverter<?> valueConverter,
- final Map<Object, Object> result,
- final int index,
- final String argDescription)
- throws Exception {
- final String[] values = split(trim(args.pop()), field);
- for (final String value : values) {
- final String[] keyValue = value.split("=");
- if (keyValue.length < 2) {
- final String splitRegex = splitRegex(field);
- if (splitRegex.length() == 0) {
- throw new ParameterException(
- CommandLine.this,
- "Value for option " + optionDescription("", field, 0)
- + " should be in KEY=VALUE format but was " + value);
- } else {
- throw new ParameterException(
- CommandLine.this,
- "Value for option " + optionDescription("", field, 0) + " should be in KEY=VALUE["
- + splitRegex + "KEY=VALUE]... format but was " + value);
- }
- }
- final Object mapKey = tryConvert(field, index, keyConverter, keyValue[0], classes[0]);
- final Object mapValue = tryConvert(field, index, valueConverter, keyValue[1], classes[1]);
- result.put(mapKey, mapValue);
- if (tracer.isInfo()) {
- tracer.info(
- "Putting [%s : %s] in %s<%s, %s> field '%s.%s' for %s%n",
- String.valueOf(mapKey),
- String.valueOf(mapValue),
- result.getClass().getSimpleName(),
- classes[0].getSimpleName(),
- classes[1].getSimpleName(),
- field.getDeclaringClass().getSimpleName(),
- field.getName(),
- argDescription);
- }
- }
- }
-
- private void checkMaxArityExceeded(
- final Range arity, final int remainder, final Field field, final String[] values) {
- if (values.length <= remainder) {
- return;
- }
- final String desc = arity.max == remainder ? "" + remainder : arity + ", remainder=" + remainder;
- throw new MaxValuesforFieldExceededException(
- CommandLine.this,
- optionDescription("", field, -1) + " max number of values ("
- + arity.max + ") exceeded: remainder is " + remainder + " but " + values.length
- + " values were specified: " + Arrays.toString(values));
- }
-
- private int applyValuesToArrayField(
- final Field field,
- final Class<?> annotation,
- final Range arity,
- final Stack<String> args,
- final Class<?> cls,
- final String argDescription)
- throws Exception {
- final Object existing = field.get(command);
- final int length = existing == null ? 0 : Array.getLength(existing);
- final Class<?> type = getTypeAttribute(field)[0];
- final List<Object> converted =
- consumeArguments(field, annotation, arity, args, type, length, argDescription);
- final List<Object> newValues = new ArrayList<Object>();
- for (int i = 0; i < length; i++) {
- newValues.add(Array.get(existing, i));
- }
- for (final Object obj : converted) {
- if (obj instanceof Collection<?>) {
- newValues.addAll((Collection<?>) obj);
- } else {
- newValues.add(obj);
- }
- }
- final Object array = Array.newInstance(type, newValues.size());
- field.set(command, array);
- for (int i = 0; i < newValues.size(); i++) {
- Array.set(array, i, newValues.get(i));
- }
- return converted.size(); // return how many args were consumed
- }
-
- @SuppressWarnings("unchecked")
- private int applyValuesToCollectionField(
- final Field field,
- final Class<?> annotation,
- final Range arity,
- final Stack<String> args,
- final Class<?> cls,
- final String argDescription)
- throws Exception {
- Collection<Object> collection = (Collection<Object>) field.get(command);
- final Class<?> type = getTypeAttribute(field)[0];
- final int length = collection == null ? 0 : collection.size();
- final List<Object> converted =
- consumeArguments(field, annotation, arity, args, type, length, argDescription);
- if (collection == null) {
- collection = createCollection(cls);
- field.set(command, collection);
- }
- for (final Object element : converted) {
- if (element instanceof Collection<?>) {
- collection.addAll((Collection<?>) element);
- } else {
- collection.add(element);
- }
- }
- return converted.size();
- }
-
- private List<Object> consumeArguments(
- final Field field,
- final Class<?> annotation,
- final Range arity,
- final Stack<String> args,
- final Class<?> type,
- final int originalSize,
- final String argDescription)
- throws Exception {
- final List<Object> result = new ArrayList<Object>();
-
- // first do the arity.min mandatory parameters
- for (int i = 0; i < arity.min; i++) {
- consumeOneArgument(field, arity, args, type, result, i, originalSize, argDescription);
- }
- // now process the varargs if any
- for (int i = arity.min; i < arity.max && !args.isEmpty(); i++) {
- if (annotation
- != Parameters
- .class) { // for vararg Options, we stop if we encounter '--', a command, or another
- // option
- if (commands.containsKey(args.peek()) || isOption(args.peek())) {
- return result;
- }
- }
- consumeOneArgument(field, arity, args, type, result, i, originalSize, argDescription);
- }
- return result;
- }
-
- private int consumeOneArgument(
- final Field field,
- final Range arity,
- final Stack<String> args,
- final Class<?> type,
- final List<Object> result,
- int index,
- final int originalSize,
- final String argDescription)
- throws Exception {
- final String[] values = split(trim(args.pop()), field);
- final ITypeConverter<?> converter = getTypeConverter(type, field);
-
- for (int j = 0; j < values.length; j++) {
- result.add(tryConvert(field, index, converter, values[j], type));
- if (tracer.isInfo()) {
- if (field.getType().isArray()) {
- tracer.info(
- "Adding [%s] to %s[] field '%s.%s' for %s%n",
- String.valueOf(result.get(result.size() - 1)),
- type.getSimpleName(),
- field.getDeclaringClass().getSimpleName(),
- field.getName(),
- argDescription);
- } else {
- tracer.info(
- "Adding [%s] to %s<%s> field '%s.%s' for %s%n",
- String.valueOf(result.get(result.size() - 1)),
- field.getType().getSimpleName(),
- type.getSimpleName(),
- field.getDeclaringClass().getSimpleName(),
- field.getName(),
- argDescription);
- }
- }
- }
- // checkMaxArityExceeded(arity, max, field, values);
- return ++index;
- }
-
- private String splitRegex(final Field field) {
- if (field.isAnnotationPresent(Option.class)) {
- return field.getAnnotation(Option.class).split();
- }
- if (field.isAnnotationPresent(Parameters.class)) {
- return field.getAnnotation(Parameters.class).split();
- }
- return "";
- }
-
- private String[] split(final String value, final Field field) {
- final String regex = splitRegex(field);
- return regex.length() == 0 ? new String[] {value} : value.split(regex);
- }
-
- /**
- * Called when parsing varargs parameters for a multi-value option.
- * When an option is encountered, the remainder should not be interpreted as vararg elements.
- * @param arg the string to determine whether it is an option or not
- * @return true if it is an option, false otherwise
- */
- private boolean isOption(final String arg) {
- if ("--".equals(arg)) {
- return true;
- }
- // not just arg prefix: we may be in the middle of parsing -xrvfFILE
- if (optionName2Field.containsKey(arg)) { // -v or -f or --file (not attached to param or other option)
- return true;
- }
- final int separatorIndex = arg.indexOf(separator);
- if (separatorIndex > 0) { // -f=FILE or --file==FILE (attached to param via separator)
- if (optionName2Field.containsKey(arg.substring(0, separatorIndex))) {
- return true;
- }
- }
- return (arg.length() > 2 && arg.startsWith("-") && singleCharOption2Field.containsKey(arg.charAt(1)));
- }
-
- private Object tryConvert(
- final Field field,
- final int index,
- final ITypeConverter<?> converter,
- final String value,
- final Class<?> type)
- throws Exception {
- try {
- return converter.convert(value);
- } catch (final TypeConversionException ex) {
- throw new ParameterException(
- CommandLine.this, ex.getMessage() + optionDescription(" for ", field, index));
- } catch (final Exception other) {
- final String desc = optionDescription(" for ", field, index) + ": " + other;
- throw new ParameterException(
- CommandLine.this, "Could not convert '" + value + "' to " + type.getSimpleName() + desc, other);
- }
- }
-
- private String optionDescription(final String prefix, final Field field, final int index) {
- final Help.IParamLabelRenderer labelRenderer = Help.createMinimalParamLabelRenderer();
- String desc = "";
- if (field.isAnnotationPresent(Option.class)) {
- desc = prefix + "option '" + field.getAnnotation(Option.class).names()[0] + "'";
- if (index >= 0) {
- final Range arity = Range.optionArity(field);
- if (arity.max > 1) {
- desc += " at index " + index;
- }
- desc += " ("
- + labelRenderer.renderParameterLabel(field, Help.Ansi.OFF, Collections.<IStyle>emptyList())
- + ")";
- }
- } else if (field.isAnnotationPresent(Parameters.class)) {
- final Range indexRange = Range.parameterIndex(field);
- final Text label =
- labelRenderer.renderParameterLabel(field, Help.Ansi.OFF, Collections.<IStyle>emptyList());
- desc = prefix + "positional parameter at index " + indexRange + " (" + label + ")";
- }
- return desc;
- }
-
- private boolean isAnyHelpRequested() {
- return isHelpRequested || versionHelpRequested || usageHelpRequested;
- }
-
- private void updateHelpRequested(final Field field) {
- if (field.isAnnotationPresent(Option.class)) {
- isHelpRequested |=
- is(field, "help", field.getAnnotation(Option.class).help());
- CommandLine.this.versionHelpRequested |= is(
- field, "versionHelp", field.getAnnotation(Option.class).versionHelp());
- CommandLine.this.usageHelpRequested |=
- is(field, "usageHelp", field.getAnnotation(Option.class).usageHelp());
- }
- }
-
- private boolean is(final Field f, final String description, final boolean value) {
- if (value) {
- if (tracer.isInfo()) {
- tracer.info(
- "Field '%s.%s' has '%s' annotation: not validating required fields%n",
- f.getDeclaringClass().getSimpleName(), f.getName(), description);
- }
- }
- return value;
- }
-
- @SuppressWarnings("unchecked")
- private Collection<Object> createCollection(final Class<?> collectionClass) throws Exception {
- if (collectionClass.isInterface()) {
- if (List.class.isAssignableFrom(collectionClass)) {
- return new ArrayList<Object>();
- } else if (SortedSet.class.isAssignableFrom(collectionClass)) {
- return new TreeSet<Object>();
- } else if (Set.class.isAssignableFrom(collectionClass)) {
- return new LinkedHashSet<Object>();
- } else if (Queue.class.isAssignableFrom(collectionClass)) {
- return new LinkedList<Object>(); // ArrayDeque is only available since 1.6
- }
- return new ArrayList<Object>();
- }
- // custom Collection implementation class must have default constructor
- return (Collection<Object>) collectionClass.getDeclaredConstructor().newInstance();
- }
-
- private Map<Object, Object> createMap(final Class<?> mapClass) throws Exception {
- try { // if it is an implementation class, instantiate it
- return (Map<Object, Object>) mapClass.getDeclaredConstructor().newInstance();
- } catch (final Exception ignored) {
- }
- return new LinkedHashMap<Object, Object>();
- }
-
- private ITypeConverter<?> getTypeConverter(final Class<?> type, final Field field) {
- final ITypeConverter<?> result = converterRegistry.get(type);
- if (result != null) {
- return result;
- }
- if (type.isEnum()) {
- return (ITypeConverter<Object>) value -> Enum.valueOf((Class<Enum>) type, value);
- }
- throw new MissingTypeConverterException(
- CommandLine.this, "No TypeConverter registered for " + type.getName() + " of field " + field);
- }
-
- private void assertNoMissingParameters(final Field field, final int arity, final Stack<String> args) {
- if (arity > args.size()) {
- if (arity == 1) {
- if (field.isAnnotationPresent(Option.class)) {
- throw new MissingParameterException(
- CommandLine.this, "Missing required parameter for " + optionDescription("", field, 0));
- }
- final Range indexRange = Range.parameterIndex(field);
- final Help.IParamLabelRenderer labelRenderer = Help.createMinimalParamLabelRenderer();
- String sep = "";
- final StringBuilder names = new StringBuilder();
- int count = 0;
- for (int i = indexRange.min; i < positionalParametersFields.size(); i++) {
- if (Range.parameterArity(positionalParametersFields.get(i)).min > 0) {
- names.append(sep)
- .append(labelRenderer.renderParameterLabel(
- positionalParametersFields.get(i),
- Help.Ansi.OFF,
- Collections.<IStyle>emptyList()));
- sep = ", ";
- count++;
- }
- }
- String msg = "Missing required parameter";
- final Range paramArity = Range.parameterArity(field);
- if (paramArity.isVariable) {
- msg += "s at positions " + indexRange + ": ";
- } else {
- msg += (count > 1 ? "s: " : ": ");
- }
- throw new MissingParameterException(CommandLine.this, msg + names);
- }
- if (args.isEmpty()) {
- throw new MissingParameterException(
- CommandLine.this,
- optionDescription("", field, 0) + " requires at least " + arity
- + " values, but none were specified.");
- }
- throw new MissingParameterException(
- CommandLine.this,
- optionDescription("", field, 0) + " requires at least " + arity + " values, but only "
- + args.size() + " were specified: " + reverse(args));
- }
- }
-
- private String trim(final String value) {
- return unquote(value);
- }
-
- private String unquote(final String value) {
- return value == null
- ? null
- : (value.length() > 1 && value.startsWith("\"") && value.endsWith("\""))
- ? value.substring(1, value.length() - 1)
- : value;
- }
- }
-
- private static class PositionalParametersSorter implements Comparator<Field> {
- @Override
- public int compare(final Field o1, final Field o2) {
- final int result = Range.parameterIndex(o1).compareTo(Range.parameterIndex(o2));
- return (result == 0) ? Range.parameterArity(o1).compareTo(Range.parameterArity(o2)) : result;
- }
- }
-
- /**
- * Inner class to group the built-in {@link ITypeConverter} implementations.
- */
- private static final class BuiltIn {
- @SuppressFBWarnings("PATH_TRAVERSAL_IN")
- static class PathConverter implements ITypeConverter<Path> {
- @Override
- public Path convert(final String value) {
- return Paths.get(value);
- }
- }
-
- static class StringConverter implements ITypeConverter<String> {
- @Override
- public String convert(final String value) {
- return value;
- }
- }
-
- static class StringBuilderConverter implements ITypeConverter<StringBuilder> {
- @Override
- public StringBuilder convert(final String value) {
- return new StringBuilder(value);
- }
- }
-
- static class CharSequenceConverter implements ITypeConverter<CharSequence> {
- @Override
- public String convert(final String value) {
- return value;
- }
- }
-
- /** Converts text to a {@code Byte} by delegating to {@link Byte#valueOf(String)}.*/
- static class ByteConverter implements ITypeConverter<Byte> {
- @Override
- public Byte convert(final String value) {
- return Byte.valueOf(value);
- }
- }
-
- /** Converts {@code "true"} or {@code "false"} to a {@code Boolean}. Other values result in a ParameterException.*/
- static class BooleanConverter implements ITypeConverter<Boolean> {
- @Override
- public Boolean convert(final String value) {
- if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
- return Boolean.parseBoolean(value);
- } else {
- throw new TypeConversionException("'" + value + "' is not a boolean");
- }
- }
- }
-
- static class CharacterConverter implements ITypeConverter<Character> {
- @Override
- public Character convert(final String value) {
- if (value.length() > 1) {
- throw new TypeConversionException("'" + value + "' is not a single character");
- }
- return value.charAt(0);
- }
- }
-
- /** Converts text to a {@code Short} by delegating to {@link Short#valueOf(String)}.*/
- static class ShortConverter implements ITypeConverter<Short> {
- @Override
- public Short convert(final String value) {
- return Short.valueOf(value);
- }
- }
-
- /** Converts text to an {@code Integer} by delegating to {@link Integer#valueOf(String)}.*/
- static class IntegerConverter implements ITypeConverter<Integer> {
- @Override
- public Integer convert(final String value) {
- return Integer.valueOf(value);
- }
- }
-
- /** Converts text to a {@code Long} by delegating to {@link Long#valueOf(String)}.*/
- static class LongConverter implements ITypeConverter<Long> {
- @Override
- public Long convert(final String value) {
- return Long.valueOf(value);
- }
- }
-
- static class FloatConverter implements ITypeConverter<Float> {
- @Override
- public Float convert(final String value) {
- return Float.valueOf(value);
- }
- }
-
- static class DoubleConverter implements ITypeConverter<Double> {
- @Override
- public Double convert(final String value) {
- return Double.valueOf(value);
- }
- }
-
- @SuppressFBWarnings("PATH_TRAVERSAL_IN")
- static class FileConverter implements ITypeConverter<File> {
- @Override
- public File convert(final String value) {
- return new File(value);
- }
- }
-
- static class URLConverter implements ITypeConverter<URL> {
- @Override
- public URL convert(final String value) throws MalformedURLException {
- return new URL(value);
- }
- }
-
- static class URIConverter implements ITypeConverter<URI> {
- @Override
- public URI convert(final String value) throws URISyntaxException {
- return new URI(value);
- }
- }
-
- /** Converts text in {@code yyyy-mm-dd} format to a {@code java.util.Date}. ParameterException on failure. */
- static class ISO8601DateConverter implements ITypeConverter<Date> {
- @Override
- public Date convert(final String value) {
- try {
- return new SimpleDateFormat("yyyy-MM-dd").parse(value);
- } catch (final ParseException e) {
- throw new TypeConversionException("'" + value + "' is not a yyyy-MM-dd date");
- }
- }
- }
-
- /** Converts text in any of the following formats to a {@code java.sql.Time}: {@code HH:mm}, {@code HH:mm:ss},
- * {@code HH:mm:ss.SSS}, {@code HH:mm:ss,SSS}. Other formats result in a ParameterException. */
- static class ISO8601TimeConverter implements ITypeConverter<Time> {
- @Override
- public Time convert(final String value) {
- try {
- if (value.length() <= 5) {
- return new Time(
- new SimpleDateFormat("HH:mm").parse(value).getTime());
- } else if (value.length() <= 8) {
- return new Time(
- new SimpleDateFormat("HH:mm:ss").parse(value).getTime());
- } else if (value.length() <= 12) {
- try {
- return new Time(new SimpleDateFormat("HH:mm:ss.SSS")
- .parse(value)
- .getTime());
- } catch (final ParseException e2) {
- return new Time(new SimpleDateFormat("HH:mm:ss,SSS")
- .parse(value)
- .getTime());
- }
- }
- } catch (final ParseException ignored) {
- // ignored because we throw a ParameterException below
- }
- throw new TypeConversionException("'" + value + "' is not a HH:mm[:ss[.SSS]] time");
- }
- }
-
- static class BigDecimalConverter implements ITypeConverter<BigDecimal> {
- @Override
- public BigDecimal convert(final String value) {
- return new BigDecimal(value);
- }
- }
-
- static class BigIntegerConverter implements ITypeConverter<BigInteger> {
- @Override
- public BigInteger convert(final String value) {
- return new BigInteger(value);
- }
- }
-
- static class CharsetConverter implements ITypeConverter<Charset> {
- @Override
- public Charset convert(final String s) {
- return Charset.forName(s);
- }
- }
-
- /** Converts text to a {@code InetAddress} by delegating to {@link InetAddress#getByName(String)}. */
- static class InetAddressConverter implements ITypeConverter<InetAddress> {
- @Override
- public InetAddress convert(final String s) throws Exception {
- return InetAddress.getByName(s);
- }
- }
-
- static class PatternConverter implements ITypeConverter<Pattern> {
- @Override
- public Pattern convert(final String s) {
- return Pattern.compile(s);
- }
- }
-
- static class UUIDConverter implements ITypeConverter<UUID> {
- @Override
- public UUID convert(final String s) throws Exception {
- return UUID.fromString(s);
- }
- }
-
- private BuiltIn() {} // private constructor: never instantiate
- }
-
- /**
- * A collection of methods and inner classes that provide fine-grained control over the contents and layout of
- * the usage help message to display to end users when help is requested or invalid input values were specified.
- * <h2>Layered API</h2>
- * <p>The {@link Command} annotation provides the easiest way to customize usage help messages. See
- * the <a href="https://remkop.github.io/picocli/index.html#_usage_help">Manual</a> for details.</p>
- * <p>This Help class provides high-level functions to create sections of the usage help message and headings
- * for these sections. Instead of calling the {@link CommandLine#usage(PrintStream, CommandLine.Help.ColorScheme)}
- * method, application authors may want to create a custom usage help message by reorganizing sections in a
- * different order and/or adding custom sections.</p>
- * <p>Finally, the Help class contains inner classes and interfaces that can be used to create custom help messages.</p>
- * <h3>IOptionRenderer and IParameterRenderer</h3>
- * <p>Renders a field annotated with {@link Option} or {@link Parameters} to an array of {@link Text} values.
- * By default, these values are</p><ul>
- * <li>mandatory marker character (if the option/parameter is {@link Option#required() required})</li>
- * <li>short option name (empty for parameters)</li>
- * <li>comma or empty (empty for parameters)</li>
- * <li>long option names (the parameter {@link IParamLabelRenderer label} for parameters)</li>
- * <li>description</li>
- * </ul>
- * <p>Other components rely on this ordering.</p>
- * <h3>Layout</h3>
- * <p>Delegates to the renderers to create {@link Text} values for the annotated fields, and uses a
- * {@link TextTable} to display these values in tabular format. Layout is responsible for deciding which values
- * to display where in the table. By default, Layout shows one option or parameter per table row.</p>
- * <h3>TextTable</h3>
- * <p>Responsible for spacing out {@link Text} values according to the {@link Column} definitions the table was
- * created with. Columns have a width, indentation, and an overflow policy that decides what to do if a value is
- * longer than the column's width.</p>
- * <h3>Text</h3>
- * <p>Encapsulates rich text with styles and colors in a way that other components like {@link TextTable} are
- * unaware of the embedded ANSI escape codes.</p>
- */
- public static class Help {
- /** Constant String holding the default program name: {@value} */
- protected static final String DEFAULT_COMMAND_NAME = "<main class>";
-
- /** Constant String holding the default string that separates options from option parameters: {@value} */
- protected static final String DEFAULT_SEPARATOR = "=";
-
- private static final int usageHelpWidth = 80;
- private static final int optionsColumnWidth = 2 + 2 + 1 + 24;
- private final Object command;
- private final Map<String, Help> commands = new LinkedHashMap<String, Help>();
- final ColorScheme colorScheme;
-
- /** Immutable list of fields annotated with {@link Option}, in declaration order. */
- public final List<Field> optionFields;
-
- /** Immutable list of fields annotated with {@link Parameters}, or an empty list if no such field exists. */
- public final List<Field> positionalParametersFields;
-
- /** The String to use as the separator between options and option parameters. {@code "="} by default,
- * initialized from {@link Command#separator()} if defined.
- * @see #parameterLabelRenderer */
- public String separator;
-
- /** The String to use as the program name in the synopsis line of the help message.
- * {@link #DEFAULT_COMMAND_NAME} by default, initialized from {@link Command#name()} if defined. */
- public String commandName = DEFAULT_COMMAND_NAME;
-
- /** Optional text lines to use as the description of the help message, displayed between the synopsis and the
- * options list. Initialized from {@link Command#description()} if the {@code Command} annotation is present,
- * otherwise this is an empty array and the help message has no description.
- * Applications may programmatically set this field to create a custom help message. */
- public String[] description = {};
-
- /** Optional custom synopsis lines to use instead of the auto-generated synopsis.
- * Initialized from {@link Command#customSynopsis()} if the {@code Command} annotation is present,
- * otherwise this is an empty array and the synopsis is generated.
- * Applications may programmatically set this field to create a custom help message. */
- public String[] customSynopsis = {};
-
- /** Optional header lines displayed at the top of the help message. For subcommands, the first header line is
- * displayed in the list of commands. Values are initialized from {@link Command#header()}
- * if the {@code Command} annotation is present, otherwise this is an empty array and the help message has no
- * header. Applications may programmatically set this field to create a custom help message. */
- public String[] header = {};
-
- /** Optional footer text lines displayed at the bottom of the help message. Initialized from
- * {@link Command#footer()} if the {@code Command} annotation is present, otherwise this is an empty array and
- * the help message has no footer.
- * Applications may programmatically set this field to create a custom help message. */
- public String[] footer = {};
-
- /** Option and positional parameter value label renderer used for the synopsis line(s) and the option list.
- * By default initialized to the result of {@link #createDefaultParamLabelRenderer()}, which takes a snapshot
- * of the {@link #separator} at construction time. If the separator is modified after Help construction, you
- * may need to re-initialize this field by calling {@link #createDefaultParamLabelRenderer()} again. */
- public IParamLabelRenderer parameterLabelRenderer;
-
- /** If {@code true}, the synopsis line(s) will show an abbreviated synopsis without detailed option names. */
- public Boolean abbreviateSynopsis;
-
- /** If {@code true}, the options list is sorted alphabetically. */
- public Boolean sortOptions;
-
- /** If {@code true}, the options list will show default values for all options except booleans. */
- public Boolean showDefaultValues;
-
- /** Character used to prefix required options in the options list. */
- public Character requiredOptionMarker;
-
- /** Optional heading preceding the header section. Initialized from {@link Command#headerHeading()}, or null. */
- public String headerHeading;
-
- /** Optional heading preceding the synopsis. Initialized from {@link Command#synopsisHeading()}, {@code "Usage: "} by default. */
- public String synopsisHeading;
-
- /** Optional heading preceding the description section. Initialized from {@link Command#descriptionHeading()}, or null. */
- public String descriptionHeading;
-
- /** Optional heading preceding the parameter list. Initialized from {@link Command#parameterListHeading()}, or null. */
- public String parameterListHeading;
-
- /** Optional heading preceding the options list. Initialized from {@link Command#optionListHeading()}, or null. */
- public String optionListHeading;
-
- /** Optional heading preceding the subcommand list. Initialized from {@link Command#commandListHeading()}. {@code "Commands:%n"} by default. */
- public String commandListHeading;
-
- /** Optional heading preceding the footer section. Initialized from {@link Command#footerHeading()}, or null. */
- public String footerHeading;
-
- /** Constructs a new {@code Help} instance with a default color scheme, initialized from annotatations
- * on the specified class and superclasses.
- * @param command the annotated object to create usage help for */
- public Help(final Object command) {
- this(command, Ansi.AUTO);
- }
-
- /** Constructs a new {@code Help} instance with a default color scheme, initialized from annotatations
- * on the specified class and superclasses.
- * @param command the annotated object to create usage help for
- * @param ansi whether to emit ANSI escape codes or not */
- public Help(final Object command, final Ansi ansi) {
- this(command, defaultColorScheme(ansi));
- }
-
- /** Constructs a new {@code Help} instance with the specified color scheme, initialized from annotatations
- * on the specified class and superclasses.
- * @param command the annotated object to create usage help for
- * @param colorScheme the color scheme to use */
- public Help(final Object command, final ColorScheme colorScheme) {
- this.command = Assert.notNull(command, "command");
- this.colorScheme = Assert.notNull(colorScheme, "colorScheme").applySystemProperties();
- final List<Field> options = new ArrayList<Field>();
- final List<Field> operands = new ArrayList<Field>();
- Class<?> cls = command.getClass();
- while (cls != null) {
- for (final Field field : cls.getDeclaredFields()) {
- field.setAccessible(true);
- if (field.isAnnotationPresent(Option.class)) {
- final Option option = field.getAnnotation(Option.class);
- if (!option.hidden()) { // hidden options should not appear in usage help
- // TODO remember longest concatenated option string length (issue #45)
- options.add(field);
- }
- }
- if (field.isAnnotationPresent(Parameters.class)) {
- operands.add(field);
- }
- }
- // superclass values should not overwrite values if both class and superclass have a @Command annotation
- if (cls.isAnnotationPresent(Command.class)) {
- final Command cmd = cls.getAnnotation(Command.class);
- if (DEFAULT_COMMAND_NAME.equals(commandName)) {
- commandName = cmd.name();
- }
- separator = (separator == null) ? cmd.separator() : separator;
- abbreviateSynopsis = (abbreviateSynopsis == null) ? cmd.abbreviateSynopsis() : abbreviateSynopsis;
- sortOptions = (sortOptions == null) ? cmd.sortOptions() : sortOptions;
- requiredOptionMarker =
- (requiredOptionMarker == null) ? cmd.requiredOptionMarker() : requiredOptionMarker;
- showDefaultValues = (showDefaultValues == null) ? cmd.showDefaultValues() : showDefaultValues;
- customSynopsis = empty(customSynopsis) ? cmd.customSynopsis() : customSynopsis;
- description = empty(description) ? cmd.description() : description;
- header = empty(header) ? cmd.header() : header;
- footer = empty(footer) ? cmd.footer() : footer;
- headerHeading = empty(headerHeading) ? cmd.headerHeading() : headerHeading;
- synopsisHeading = empty(synopsisHeading) || "Usage: ".equals(synopsisHeading)
- ? cmd.synopsisHeading()
- : synopsisHeading;
- descriptionHeading = empty(descriptionHeading) ? cmd.descriptionHeading() : descriptionHeading;
- parameterListHeading =
- empty(parameterListHeading) ? cmd.parameterListHeading() : parameterListHeading;
- optionListHeading = empty(optionListHeading) ? cmd.optionListHeading() : optionListHeading;
- commandListHeading = empty(commandListHeading) || "Commands:%n".equals(commandListHeading)
- ? cmd.commandListHeading()
- : commandListHeading;
- footerHeading = empty(footerHeading) ? cmd.footerHeading() : footerHeading;
- }
- cls = cls.getSuperclass();
- }
- sortOptions = sortOptions == null || sortOptions;
- abbreviateSynopsis = abbreviateSynopsis != null && abbreviateSynopsis;
- requiredOptionMarker = (requiredOptionMarker == null) ? ' ' : requiredOptionMarker;
- showDefaultValues = showDefaultValues != null && showDefaultValues;
- synopsisHeading = (synopsisHeading == null) ? "Usage: " : synopsisHeading;
- commandListHeading = (commandListHeading == null) ? "Commands:%n" : commandListHeading;
- separator = (separator == null) ? DEFAULT_SEPARATOR : separator;
- parameterLabelRenderer = createDefaultParamLabelRenderer(); // uses help separator
- Collections.sort(operands, new PositionalParametersSorter());
- positionalParametersFields = Collections.unmodifiableList(operands);
- optionFields = Collections.unmodifiableList(options);
- }
-
- /** Registers all specified subcommands with this Help.
- * @param commands maps the command names to the associated CommandLine object
- * @return this Help instance (for method chaining)
- * @see CommandLine#getSubcommands()
- */
- public Help addAllSubcommands(final Map<String, CommandLine> commands) {
- if (commands != null) {
- for (final Map.Entry<String, CommandLine> entry : commands.entrySet()) {
- addSubcommand(entry.getKey(), entry.getValue().getCommand());
- }
- }
- return this;
- }
-
- /** Registers the specified subcommand with this Help.
- * @param commandName the name of the subcommand to display in the usage message
- * @param command the annotated object to get more information from
- * @return this Help instance (for method chaining)
- */
- public Help addSubcommand(final String commandName, final Object command) {
- commands.put(commandName, new Help(command));
- return this;
- }
-
- /** Returns a synopsis for the command without reserving space for the synopsis heading.
- * @return a synopsis
- * @see #abbreviatedSynopsis()
- * @see #detailedSynopsis(Comparator, boolean)
- * @deprecated use {@link #synopsis(int)} instead
- */
- @Deprecated
- public String synopsis() {
- return synopsis(0);
- }
-
- /**
- * Returns a synopsis for the command, reserving the specified space for the synopsis heading.
- * @param synopsisHeadingLength the length of the synopsis heading that will be displayed on the same line
- * @return a synopsis
- * @see #abbreviatedSynopsis()
- * @see #detailedSynopsis(Comparator, boolean)
- * @see #synopsisHeading
- */
- public String synopsis(final int synopsisHeadingLength) {
- if (!empty(customSynopsis)) {
- return customSynopsis();
- }
- return abbreviateSynopsis
- ? abbreviatedSynopsis()
- : detailedSynopsis(synopsisHeadingLength, createShortOptionArityAndNameComparator(), true);
- }
-
- /** Generates a generic synopsis like {@code <command name> [OPTIONS] [PARAM1 [PARAM2]...]}, omitting parts
- * that don't apply to the command (e.g., does not show [OPTIONS] if the command has no options).
- * @return a generic synopsis */
- public String abbreviatedSynopsis() {
- final StringBuilder sb = new StringBuilder();
- if (!optionFields.isEmpty()) { // only show if annotated object actually has options
- sb.append(" [OPTIONS]");
- }
- // sb.append(" [--] "); // implied
- for (final Field positionalParam : positionalParametersFields) {
- if (!positionalParam.getAnnotation(Parameters.class).hidden()) {
- sb.append(' ')
- .append(parameterLabelRenderer.renderParameterLabel(
- positionalParam, ansi(), colorScheme.parameterStyles));
- }
- }
- return colorScheme.commandText(commandName).toString()
- + (sb.toString())
- + System.getProperty("line.separator");
- }
- /** Generates a detailed synopsis message showing all options and parameters. Follows the unix convention of
- * showing optional options and parameters in square brackets ({@code [ ]}).
- * @param optionSort comparator to sort options or {@code null} if options should not be sorted
- * @param clusterBooleanOptions {@code true} if boolean short options should be clustered into a single string
- * @return a detailed synopsis
- * @deprecated use {@link #detailedSynopsis(int, Comparator, boolean)} instead. */
- @Deprecated
- public String detailedSynopsis(final Comparator<Field> optionSort, final boolean clusterBooleanOptions) {
- return detailedSynopsis(0, optionSort, clusterBooleanOptions);
- }
-
- /** Generates a detailed synopsis message showing all options and parameters. Follows the unix convention of
- * showing optional options and parameters in square brackets ({@code [ ]}).
- * @param synopsisHeadingLength the length of the synopsis heading that will be displayed on the same line
- * @param optionSort comparator to sort options or {@code null} if options should not be sorted
- * @param clusterBooleanOptions {@code true} if boolean short options should be clustered into a single string
- * @return a detailed synopsis */
- public String detailedSynopsis(
- final int synopsisHeadingLength,
- final Comparator<Field> optionSort,
- final boolean clusterBooleanOptions) {
- Text optionText = ansi().new Text(0);
- final List<Field> fields = new ArrayList<Field>(optionFields); // iterate in declaration order
- if (optionSort != null) {
- Collections.sort(fields, optionSort); // iterate in specified sort order
- }
- if (clusterBooleanOptions) { // cluster all short boolean options into a single string
- final List<Field> booleanOptions = new ArrayList<Field>();
- final StringBuilder clusteredRequired = new StringBuilder("-");
- final StringBuilder clusteredOptional = new StringBuilder("-");
- for (final Field field : fields) {
- if (field.getType() == boolean.class || field.getType() == Boolean.class) {
- final Option option = field.getAnnotation(Option.class);
- final String shortestName = ShortestFirst.sort(option.names())[0];
- if (shortestName.length() == 2 && shortestName.startsWith("-")) {
- booleanOptions.add(field);
- if (option.required()) {
- clusteredRequired.append(shortestName.substring(1));
- } else {
- clusteredOptional.append(shortestName.substring(1));
- }
- }
- }
- }
- fields.removeAll(booleanOptions);
- if (clusteredRequired.length() > 1) { // initial length was 1
- optionText = optionText.append(" ").append(colorScheme.optionText(clusteredRequired.toString()));
- }
- if (clusteredOptional.length() > 1) { // initial length was 1
- optionText = optionText
- .append(" [")
- .append(colorScheme.optionText(clusteredOptional.toString()))
- .append("]");
- }
- }
- for (final Field field : fields) {
- final Option option = field.getAnnotation(Option.class);
- if (!option.hidden()) {
- if (option.required()) {
- optionText =
- appendOptionSynopsis(optionText, field, ShortestFirst.sort(option.names())[0], " ", "");
- if (isMultiValue(field)) {
- optionText = appendOptionSynopsis(
- optionText, field, ShortestFirst.sort(option.names())[0], " [", "]...");
- }
- } else {
- optionText = appendOptionSynopsis(
- optionText, field, ShortestFirst.sort(option.names())[0], " [", "]");
- if (isMultiValue(field)) {
- optionText = optionText.append("...");
- }
- }
- }
- }
- for (final Field positionalParam : positionalParametersFields) {
- if (!positionalParam.getAnnotation(Parameters.class).hidden()) {
- optionText = optionText.append(" ");
- final Text label = parameterLabelRenderer.renderParameterLabel(
- positionalParam, colorScheme.ansi(), colorScheme.parameterStyles);
- optionText = optionText.append(label);
- }
- }
- // Fix for #142: first line of synopsis overshoots max. characters
- final int firstColumnLength = commandName.length() + synopsisHeadingLength;
-
- // synopsis heading ("Usage: ") may be on the same line, so adjust column width
- final TextTable textTable = new TextTable(ansi(), firstColumnLength, usageHelpWidth - firstColumnLength);
- textTable.indentWrappedLines =
- 1; // don't worry about first line: options (2nd column) always start with a space
-
- // right-adjust the command name by length of synopsis heading
- final Text PADDING = Ansi.OFF.new Text(stringOf('X', synopsisHeadingLength));
- textTable.addRowValues(new Text[] {PADDING.append(colorScheme.commandText(commandName)), optionText});
- return textTable.toString().substring(synopsisHeadingLength); // cut off leading synopsis heading spaces
- }
-
- private Text appendOptionSynopsis(
- final Text optionText,
- final Field field,
- final String optionName,
- final String prefix,
- final String suffix) {
- final Text optionParamText = parameterLabelRenderer.renderParameterLabel(
- field, colorScheme.ansi(), colorScheme.optionParamStyles);
- return optionText
- .append(prefix)
- .append(colorScheme.optionText(optionName))
- .append(optionParamText)
- .append(suffix);
- }
-
- /** Returns the number of characters the synopsis heading will take on the same line as the synopsis.
- * @return the number of characters the synopsis heading will take on the same line as the synopsis.
- * @see #detailedSynopsis(int, Comparator, boolean)
- */
- public int synopsisHeadingLength() {
- final String[] lines = Ansi.OFF.new Text(synopsisHeading).toString().split("\\r?\\n|\\r|%n", -1);
- return lines[lines.length - 1].length();
- }
- /**
- * <p>Returns a description of the {@linkplain Option options} supported by the application.
- * This implementation {@linkplain #createShortOptionNameComparator() sorts options alphabetically}, and shows
- * only the {@linkplain Option#hidden() non-hidden} options in a {@linkplain TextTable tabular format}
- * using the {@linkplain #createDefaultOptionRenderer() default renderer} and {@linkplain Layout default layout}.</p>
- * @return the fully formatted option list
- * @see #optionList(Layout, Comparator, IParamLabelRenderer)
- */
- public String optionList() {
- final Comparator<Field> sortOrder =
- sortOptions == null || sortOptions.booleanValue() ? createShortOptionNameComparator() : null;
- return optionList(createDefaultLayout(), sortOrder, parameterLabelRenderer);
- }
-
- /** Sorts all {@code Options} with the specified {@code comparator} (if the comparator is non-{@code null}),
- * then {@linkplain Layout#addOption(Field, CommandLine.Help.IParamLabelRenderer) adds} all non-hidden options to the
- * specified TextTable and returns the result of TextTable.toString().
- * @param layout responsible for rendering the option list
- * @param optionSort determines in what order {@code Options} should be listed. Declared order if {@code null}
- * @param valueLabelRenderer used for options with a parameter
- * @return the fully formatted option list
- */
- public String optionList(
- final Layout layout, final Comparator<Field> optionSort, final IParamLabelRenderer valueLabelRenderer) {
- final List<Field> fields = new ArrayList<Field>(optionFields); // options are stored in order of declaration
- if (optionSort != null) {
- Collections.sort(fields, optionSort); // default: sort options ABC
- }
- layout.addOptions(fields, valueLabelRenderer);
- return layout.toString();
- }
-
- /**
- * Returns the section of the usage help message that lists the parameters with their descriptions.
- * @return the section of the usage help message that lists the parameters
- */
- public String parameterList() {
- return parameterList(createDefaultLayout(), parameterLabelRenderer);
- }
- /**
- * Returns the section of the usage help message that lists the parameters with their descriptions.
- * @param layout the layout to use
- * @param paramLabelRenderer for rendering parameter names
- * @return the section of the usage help message that lists the parameters
- */
- public String parameterList(final Layout layout, final IParamLabelRenderer paramLabelRenderer) {
- layout.addPositionalParameters(positionalParametersFields, paramLabelRenderer);
- return layout.toString();
- }
-
- private static String heading(final Ansi ansi, final String values, final Object... params) {
- final StringBuilder sb = join(ansi, new String[] {values}, new StringBuilder(), params);
- String result = sb.toString();
- result = result.endsWith(System.getProperty("line.separator"))
- ? result.substring(
- 0,
- result.length()
- - System.getProperty("line.separator").length())
- : result;
- return result + new String(spaces(countTrailingSpaces(values)));
- }
-
- private static char[] spaces(final int length) {
- final char[] result = new char[length];
- Arrays.fill(result, ' ');
- return result;
- }
-
- private static int countTrailingSpaces(final String str) {
- if (str == null) {
- return 0;
- }
- int trailingSpaces = 0;
- for (int i = str.length() - 1; i >= 0 && str.charAt(i) == ' '; i--) {
- trailingSpaces++;
- }
- return trailingSpaces;
- }
-
- /** Formats each of the specified values and appends it to the specified StringBuilder.
- * @param ansi whether the result should contain ANSI escape codes or not
- * @param values the values to format and append to the StringBuilder
- * @param sb the StringBuilder to collect the formatted strings
- * @param params the parameters to pass to the format method when formatting each value
- * @return the specified StringBuilder */
- public static StringBuilder join(
- final Ansi ansi, final String[] values, final StringBuilder sb, final Object... params) {
- if (values != null) {
- final TextTable table = new TextTable(ansi, usageHelpWidth);
- table.indentWrappedLines = 0;
- for (final String summaryLine : values) {
- final Text[] lines = ansi.new Text(format(summaryLine, params)).splitLines();
- for (final Text line : lines) {
- table.addRowValues(line);
- }
- }
- table.toString(sb);
- }
- return sb;
- }
-
- private static String format(final String formatString, final Object... params) {
- return formatString == null ? "" : String.format(formatString, params);
- }
- /** Returns command custom synopsis as a string. A custom synopsis can be zero or more lines, and can be
- * specified declaratively with the {@link Command#customSynopsis()} annotation attribute or programmatically
- * by setting the Help instance's {@link Help#customSynopsis} field.
- * @param params Arguments referenced by the format specifiers in the synopsis strings
- * @return the custom synopsis lines combined into a single String (which may be empty)
- */
- public String customSynopsis(final Object... params) {
- return join(ansi(), customSynopsis, new StringBuilder(), params).toString();
- }
- /** Returns command description text as a string. Description text can be zero or more lines, and can be specified
- * declaratively with the {@link Command#description()} annotation attribute or programmatically by
- * setting the Help instance's {@link Help#description} field.
- * @param params Arguments referenced by the format specifiers in the description strings
- * @return the description lines combined into a single String (which may be empty)
- */
- public String description(final Object... params) {
- return join(ansi(), description, new StringBuilder(), params).toString();
- }
- /** Returns the command header text as a string. Header text can be zero or more lines, and can be specified
- * declaratively with the {@link Command#header()} annotation attribute or programmatically by
- * setting the Help instance's {@link Help#header} field.
- * @param params Arguments referenced by the format specifiers in the header strings
- * @return the header lines combined into a single String (which may be empty)
- */
- public String header(final Object... params) {
- return join(ansi(), header, new StringBuilder(), params).toString();
- }
- /** Returns command footer text as a string. Footer text can be zero or more lines, and can be specified
- * declaratively with the {@link Command#footer()} annotation attribute or programmatically by
- * setting the Help instance's {@link Help#footer} field.
- * @param params Arguments referenced by the format specifiers in the footer strings
- * @return the footer lines combined into a single String (which may be empty)
- */
- public String footer(final Object... params) {
- return join(ansi(), footer, new StringBuilder(), params).toString();
- }
-
- /** Returns the text displayed before the header text; the result of {@code String.format(headerHeading, params)}.
- * @param params the parameters to use to format the header heading
- * @return the formatted header heading */
- public String headerHeading(final Object... params) {
- return heading(ansi(), headerHeading, params);
- }
-
- /** Returns the text displayed before the synopsis text; the result of {@code String.format(synopsisHeading, params)}.
- * @param params the parameters to use to format the synopsis heading
- * @return the formatted synopsis heading */
- public String synopsisHeading(final Object... params) {
- return heading(ansi(), synopsisHeading, params);
- }
-
- /** Returns the text displayed before the description text; an empty string if there is no description,
- * otherwise the result of {@code String.format(descriptionHeading, params)}.
- * @param params the parameters to use to format the description heading
- * @return the formatted description heading */
- public String descriptionHeading(final Object... params) {
- return empty(descriptionHeading) ? "" : heading(ansi(), descriptionHeading, params);
- }
-
- /** Returns the text displayed before the positional parameter list; an empty string if there are no positional
- * parameters, otherwise the result of {@code String.format(parameterListHeading, params)}.
- * @param params the parameters to use to format the parameter list heading
- * @return the formatted parameter list heading */
- public String parameterListHeading(final Object... params) {
- return positionalParametersFields.isEmpty() ? "" : heading(ansi(), parameterListHeading, params);
- }
-
- /** Returns the text displayed before the option list; an empty string if there are no options,
- * otherwise the result of {@code String.format(optionListHeading, params)}.
- * @param params the parameters to use to format the option list heading
- * @return the formatted option list heading */
- public String optionListHeading(final Object... params) {
- return optionFields.isEmpty() ? "" : heading(ansi(), optionListHeading, params);
- }
-
- /** Returns the text displayed before the command list; an empty string if there are no commands,
- * otherwise the result of {@code String.format(commandListHeading, params)}.
- * @param params the parameters to use to format the command list heading
- * @return the formatted command list heading */
- public String commandListHeading(final Object... params) {
- return commands.isEmpty() ? "" : heading(ansi(), commandListHeading, params);
- }
-
- /** Returns the text displayed before the footer text; the result of {@code String.format(footerHeading, params)}.
- * @param params the parameters to use to format the footer heading
- * @return the formatted footer heading */
- public String footerHeading(final Object... params) {
- return heading(ansi(), footerHeading, params);
- }
- /** Returns a 2-column list with command names and the first line of their header or (if absent) description.
- * @return a usage help section describing the added commands */
- public String commandList() {
- if (commands.isEmpty()) {
- return "";
- }
- final int commandLength = maxLength(commands.keySet());
- final Help.TextTable textTable = new Help.TextTable(
- ansi(),
- new Help.Column(commandLength + 2, 2, Help.Column.Overflow.SPAN),
- new Help.Column(usageHelpWidth - (commandLength + 2), 2, Help.Column.Overflow.WRAP));
-
- for (final Map.Entry<String, Help> entry : commands.entrySet()) {
- final Help command = entry.getValue();
- final String header = command.header != null && command.header.length > 0
- ? command.header[0]
- : (command.description != null && command.description.length > 0 ? command.description[0] : "");
- textTable.addRowValues(colorScheme.commandText(entry.getKey()), ansi().new Text(header));
- }
- return textTable.toString();
- }
-
- private static int maxLength(final Collection<String> any) {
- final List<String> strings = new ArrayList<String>(any);
- Collections.sort(strings, Collections.reverseOrder(Help.shortestFirst()));
- return strings.get(0).length();
- }
-
- private static String join(final String[] names, final int offset, final int length, final String separator) {
- if (names == null) {
- return "";
- }
- final StringBuilder result = new StringBuilder();
- for (int i = offset; i < offset + length; i++) {
- result.append((i > offset) ? separator : "").append(names[i]);
- }
- return result.toString();
- }
-
- private static String stringOf(final char chr, final int length) {
- final char[] buff = new char[length];
- Arrays.fill(buff, chr);
- return new String(buff);
- }
-
- /** Returns a {@code Layout} instance configured with the user preferences captured in this Help instance.
- * @return a Layout */
- public Layout createDefaultLayout() {
- return new Layout(
- colorScheme,
- new TextTable(colorScheme.ansi()),
- createDefaultOptionRenderer(),
- createDefaultParameterRenderer());
- }
- /** Returns a new default OptionRenderer which converts {@link Option Options} to five columns of text to match
- * the default {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
- * <ol>
- * <li>the required option marker</li>
- * <li>2-character short option name (or empty string if no short option exists)</li>
- * <li>comma separator (only if both short option and long option exist, empty string otherwise)</li>
- * <li>comma-separated string with long option name(s)</li>
- * <li>first element of the {@link Option#description()} array</li>
- * </ol>
- * <p>Following this, there will be one row for each of the remaining elements of the {@link
- * Option#description()} array, and these rows look like {@code {"", "", "", "", option.description()[i]}}.</p>
- * <p>If configured, this option renderer adds an additional row to display the default field value.</p>
- * @return a new default OptionRenderer
- */
- public IOptionRenderer createDefaultOptionRenderer() {
- final DefaultOptionRenderer result = new DefaultOptionRenderer();
- result.requiredMarker = String.valueOf(requiredOptionMarker);
- if (showDefaultValues != null && showDefaultValues.booleanValue()) {
- result.command = this.command;
- }
- return result;
- }
- /** Returns a new minimal OptionRenderer which converts {@link Option Options} to a single row with two columns
- * of text: an option name and a description. If multiple names or descriptions exist, the first value is used.
- * @return a new minimal OptionRenderer */
- public static IOptionRenderer createMinimalOptionRenderer() {
- return new MinimalOptionRenderer();
- }
-
- /** Returns a new default ParameterRenderer which converts {@link Parameters Parameters} to four columns of
- * text to match the default {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
- * <ol>
- * <li>empty string </li>
- * <li>empty string </li>
- * <li>parameter(s) label as rendered by the {@link IParamLabelRenderer}</li>
- * <li>first element of the {@link Parameters#description()} array</li>
- * </ol>
- * <p>Following this, there will be one row for each of the remaining elements of the {@link
- * Parameters#description()} array, and these rows look like {@code {"", "", "", param.description()[i]}}.</p>
- * <p>If configured, this parameter renderer adds an additional row to display the default field value.</p>
- * @return a new default ParameterRenderer
- */
- public IParameterRenderer createDefaultParameterRenderer() {
- final DefaultParameterRenderer result = new DefaultParameterRenderer();
- result.requiredMarker = String.valueOf(requiredOptionMarker);
- return result;
- }
- /** Returns a new minimal ParameterRenderer which converts {@link Parameters Parameters} to a single row with
- * two columns of text: an option name and a description. If multiple descriptions exist, the first value is used.
- * @return a new minimal ParameterRenderer */
- public static IParameterRenderer createMinimalParameterRenderer() {
- return new MinimalParameterRenderer();
- }
-
- /** Returns a value renderer that returns the {@code paramLabel} if defined or the field name otherwise.
- * @return a new minimal ParamLabelRenderer */
- public static IParamLabelRenderer createMinimalParamLabelRenderer() {
- return new IParamLabelRenderer() {
- @Override
- public Text renderParameterLabel(final Field field, final Ansi ansi, final List<IStyle> styles) {
- final String text = DefaultParamLabelRenderer.renderParameterName(field);
- return ansi.apply(text, styles);
- }
-
- @Override
- public String separator() {
- return "";
- }
- };
- }
- /** Returns a new default value renderer that separates option parameters from their {@linkplain Option
- * options} with the specified separator string, surrounds optional parameters with {@code '['} and {@code ']'}
- * characters and uses ellipses ("...") to indicate that any number of a parameter are allowed.
- * @return a new default ParamLabelRenderer
- */
- public IParamLabelRenderer createDefaultParamLabelRenderer() {
- return new DefaultParamLabelRenderer(separator);
- }
- /** Sorts Fields annotated with {@code Option} by their option name in case-insensitive alphabetic order. If an
- * Option has multiple names, the shortest name is used for the sorting. Help options follow non-help options.
- * @return a comparator that sorts fields by their option name in case-insensitive alphabetic order */
- public static Comparator<Field> createShortOptionNameComparator() {
- return new SortByShortestOptionNameAlphabetically();
- }
- /** Sorts Fields annotated with {@code Option} by their option {@linkplain Range#max max arity} first, by
- * {@linkplain Range#min min arity} next, and by {@linkplain #createShortOptionNameComparator() option name} last.
- * @return a comparator that sorts fields by arity first, then their option name */
- public static Comparator<Field> createShortOptionArityAndNameComparator() {
- return new SortByOptionArityAndNameAlphabetically();
- }
- /** Sorts short strings before longer strings.
- * @return a comparators that sorts short strings before longer strings */
- public static Comparator<String> shortestFirst() {
- return new ShortestFirst();
- }
-
- /** Returns whether ANSI escape codes are enabled or not.
- * @return whether ANSI escape codes are enabled or not
- */
- public Ansi ansi() {
- return colorScheme.ansi;
- }
-
- /** When customizing online help for {@link Option Option} details, a custom {@code IOptionRenderer} can be
- * used to create textual representation of an Option in a tabular format: one or more rows, each containing
- * one or more columns. The {@link Layout Layout} is responsible for placing these text values in the
- * {@link TextTable TextTable}. */
- public interface IOptionRenderer {
- /**
- * Returns a text representation of the specified Option and the Field that captures the option value.
- * @param option the command line option to show online usage help for
- * @param field the field that will hold the value for the command line option
- * @param parameterLabelRenderer responsible for rendering option parameters to text
- * @param scheme color scheme for applying ansi color styles to options and option parameters
- * @return a 2-dimensional array of text values: one or more rows, each containing one or more columns
- */
- Text[][] render(Option option, Field field, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme);
- }
- /** The DefaultOptionRenderer converts {@link Option Options} to five columns of text to match the default
- * {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
- * <ol>
- * <li>the required option marker (if the option is required)</li>
- * <li>2-character short option name (or empty string if no short option exists)</li>
- * <li>comma separator (only if both short option and long option exist, empty string otherwise)</li>
- * <li>comma-separated string with long option name(s)</li>
- * <li>first element of the {@link Option#description()} array</li>
- * </ol>
- * <p>Following this, there will be one row for each of the remaining elements of the {@link
- * Option#description()} array, and these rows look like {@code {"", "", "", option.description()[i]}}.</p>
- */
- static class DefaultOptionRenderer implements IOptionRenderer {
- public String requiredMarker = " ";
- public Object command;
- private String sep;
- private boolean showDefault;
-
- @Override
- public Text[][] render(
- final Option option,
- final Field field,
- final IParamLabelRenderer paramLabelRenderer,
- final ColorScheme scheme) {
- final String[] names = ShortestFirst.sort(option.names());
- final int shortOptionCount = names[0].length() == 2 ? 1 : 0;
- final String shortOption = shortOptionCount > 0 ? names[0] : "";
- sep = shortOptionCount > 0 && names.length > 1 ? "," : "";
-
- final String longOption = join(names, shortOptionCount, names.length - shortOptionCount, ", ");
- final Text longOptionText = createLongOptionText(field, paramLabelRenderer, scheme, longOption);
-
- showDefault = command != null && !option.help() && !isBoolean(field.getType());
- final Object defaultValue = createDefaultValue(field);
-
- final String requiredOption = option.required() ? requiredMarker : "";
- return renderDescriptionLines(
- option, scheme, requiredOption, shortOption, longOptionText, defaultValue);
- }
-
- private Object createDefaultValue(final Field field) {
- Object defaultValue = null;
- try {
- defaultValue = field.get(command);
- if (defaultValue == null) {
- showDefault = false;
- } // #201 don't show null default values
- else if (field.getType().isArray()) {
- final StringBuilder sb = new StringBuilder();
- for (int i = 0; i < Array.getLength(defaultValue); i++) {
- sb.append(i > 0 ? ", " : "").append(Array.get(defaultValue, i));
- }
- defaultValue = sb.insert(0, "[").append("]").toString();
- }
- } catch (final Exception ex) {
- showDefault = false;
- }
- return defaultValue;
- }
-
- private Text createLongOptionText(
- final Field field,
- final IParamLabelRenderer renderer,
- final ColorScheme scheme,
- final String longOption) {
- Text paramLabelText = renderer.renderParameterLabel(field, scheme.ansi(), scheme.optionParamStyles);
-
- // if no long option, fill in the space between the short option name and the param label value
- if (paramLabelText.length > 0 && longOption.length() == 0) {
- sep = renderer.separator();
- // #181 paramLabelText may be =LABEL or [=LABEL...]
- final int sepStart = paramLabelText.plainString().indexOf(sep);
- final Text prefix = paramLabelText.substring(0, sepStart);
- paramLabelText = prefix.append(paramLabelText.substring(sepStart + sep.length()));
- }
- Text longOptionText = scheme.optionText(longOption);
- longOptionText = longOptionText.append(paramLabelText);
- return longOptionText;
- }
-
- private Text[][] renderDescriptionLines(
- final Option option,
- final ColorScheme scheme,
- final String requiredOption,
- final String shortOption,
- final Text longOptionText,
- final Object defaultValue) {
- final Text EMPTY = Ansi.EMPTY_TEXT;
- final List<Text[]> result = new ArrayList<Text[]>();
- Text[] descriptionFirstLines = scheme.ansi().new Text(str(option.description(), 0)).splitLines();
- if (descriptionFirstLines.length == 0) {
- if (showDefault) {
- descriptionFirstLines = new Text[] {scheme.ansi().new Text(" Default: " + defaultValue)};
- showDefault = false; // don't show the default value twice
- } else {
- descriptionFirstLines = new Text[] {EMPTY};
- }
- }
- result.add(new Text[] {
- scheme.optionText(requiredOption),
- scheme.optionText(shortOption),
- scheme.ansi().new Text(sep),
- longOptionText,
- descriptionFirstLines[0]
- });
- for (int i = 1; i < descriptionFirstLines.length; i++) {
- result.add(new Text[] {EMPTY, EMPTY, EMPTY, EMPTY, descriptionFirstLines[i]});
- }
- for (int i = 1; i < option.description().length; i++) {
- final Text[] descriptionNextLines = scheme.ansi().new Text(option.description()[i]).splitLines();
- for (final Text line : descriptionNextLines) {
- result.add(new Text[] {EMPTY, EMPTY, EMPTY, EMPTY, line});
- }
- }
- if (showDefault) {
- result.add(
- new Text[] {EMPTY, EMPTY, EMPTY, EMPTY, scheme.ansi().new Text(" Default: " + defaultValue)
- });
- }
- return result.toArray(new Text[result.size()][]);
- }
- }
- /** The MinimalOptionRenderer converts {@link Option Options} to a single row with two columns of text: an
- * option name and a description. If multiple names or description lines exist, the first value is used. */
- static class MinimalOptionRenderer implements IOptionRenderer {
- @Override
- public Text[][] render(
- final Option option,
- final Field field,
- final IParamLabelRenderer parameterLabelRenderer,
- final ColorScheme scheme) {
- Text optionText = scheme.optionText(option.names()[0]);
- final Text paramLabelText =
- parameterLabelRenderer.renderParameterLabel(field, scheme.ansi(), scheme.optionParamStyles);
- optionText = optionText.append(paramLabelText);
- return new Text[][] {
- {optionText, scheme.ansi().new Text(option.description().length == 0 ? "" : option.description()[0])
- }
- };
- }
- }
- /** The MinimalParameterRenderer converts {@link Parameters Parameters} to a single row with two columns of
- * text: the parameters label and a description. If multiple description lines exist, the first value is used. */
- static class MinimalParameterRenderer implements IParameterRenderer {
- @Override
- public Text[][] render(
- final Parameters param,
- final Field field,
- final IParamLabelRenderer parameterLabelRenderer,
- final ColorScheme scheme) {
- return new Text[][] {
- {
- parameterLabelRenderer.renderParameterLabel(field, scheme.ansi(), scheme.parameterStyles),
- scheme.ansi().new Text(param.description().length == 0 ? "" : param.description()[0])
- }
- };
- }
- }
- /** When customizing online help for {@link Parameters Parameters} details, a custom {@code IParameterRenderer}
- * can be used to create textual representation of a Parameters field in a tabular format: one or more rows,
- * each containing one or more columns. The {@link Layout Layout} is responsible for placing these text
- * values in the {@link TextTable TextTable}. */
- public interface IParameterRenderer {
- /**
- * Returns a text representation of the specified Parameters and the Field that captures the parameter values.
- * @param parameters the command line parameters to show online usage help for
- * @param field the field that will hold the value for the command line parameters
- * @param parameterLabelRenderer responsible for rendering parameter labels to text
- * @param scheme color scheme for applying ansi color styles to positional parameters
- * @return a 2-dimensional array of text values: one or more rows, each containing one or more columns
- */
- Text[][] render(
- Parameters parameters, Field field, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme);
- }
- /** The DefaultParameterRenderer converts {@link Parameters Parameters} to five columns of text to match the
- * default {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
- * <ol>
- * <li>the required option marker (if the parameter's arity is to have at least one value)</li>
- * <li>empty string </li>
- * <li>empty string </li>
- * <li>parameter(s) label as rendered by the {@link IParamLabelRenderer}</li>
- * <li>first element of the {@link Parameters#description()} array</li>
- * </ol>
- * <p>Following this, there will be one row for each of the remaining elements of the {@link
- * Parameters#description()} array, and these rows look like {@code {"", "", "", param.description()[i]}}.</p>
- */
- static class DefaultParameterRenderer implements IParameterRenderer {
- public String requiredMarker = " ";
-
- @Override
- public Text[][] render(
- final Parameters params,
- final Field field,
- final IParamLabelRenderer paramLabelRenderer,
- final ColorScheme scheme) {
- final Text label =
- paramLabelRenderer.renderParameterLabel(field, scheme.ansi(), scheme.parameterStyles);
- final Text requiredParameter =
- scheme.parameterText(Range.parameterArity(field).min > 0 ? requiredMarker : "");
-
- final Text EMPTY = Ansi.EMPTY_TEXT;
- final List<Text[]> result = new ArrayList<Text[]>();
- Text[] descriptionFirstLines = scheme.ansi().new Text(str(params.description(), 0)).splitLines();
- if (descriptionFirstLines.length == 0) {
- descriptionFirstLines = new Text[] {EMPTY};
- }
- result.add(new Text[] {requiredParameter, EMPTY, EMPTY, label, descriptionFirstLines[0]});
- for (int i = 1; i < descriptionFirstLines.length; i++) {
- result.add(new Text[] {EMPTY, EMPTY, EMPTY, EMPTY, descriptionFirstLines[i]});
- }
- for (int i = 1; i < params.description().length; i++) {
- final Text[] descriptionNextLines = scheme.ansi().new Text(params.description()[i]).splitLines();
- for (final Text line : descriptionNextLines) {
- result.add(new Text[] {EMPTY, EMPTY, EMPTY, EMPTY, line});
- }
- }
- return result.toArray(new Text[result.size()][]);
- }
- }
- /** When customizing online usage help for an option parameter or a positional parameter, a custom
- * {@code IParamLabelRenderer} can be used to render the parameter name or label to a String. */
- public interface IParamLabelRenderer {
-
- /** Returns a text rendering of the Option parameter or positional parameter; returns an empty string
- * {@code ""} if the option is a boolean and does not take a parameter.
- * @param field the annotated field with a parameter label
- * @param ansi determines whether ANSI escape codes should be emitted or not
- * @param styles the styles to apply to the parameter label
- * @return a text rendering of the Option parameter or positional parameter */
- Text renderParameterLabel(Field field, Ansi ansi, List<IStyle> styles);
-
- /** Returns the separator between option name and param label.
- * @return the separator between option name and param label */
- String separator();
- }
- /**
- * DefaultParamLabelRenderer separates option parameters from their {@linkplain Option options} with a
- * {@linkplain DefaultParamLabelRenderer#separator separator} string, surrounds optional values
- * with {@code '['} and {@code ']'} characters and uses ellipses ("...") to indicate that any number of
- * values is allowed for options or parameters with variable arity.
- */
- static class DefaultParamLabelRenderer implements IParamLabelRenderer {
- /** The string to use to separate option parameters from their options. */
- public final String separator;
- /** Constructs a new DefaultParamLabelRenderer with the specified separator string. */
- public DefaultParamLabelRenderer(final String separator) {
- this.separator = Assert.notNull(separator, "separator");
- }
-
- @Override
- public String separator() {
- return separator;
- }
-
- @Override
- public Text renderParameterLabel(final Field field, final Ansi ansi, final List<IStyle> styles) {
- final boolean isOptionParameter = field.isAnnotationPresent(Option.class);
- final Range arity = isOptionParameter ? Range.optionArity(field) : Range.parameterCapacity(field);
- final String split = isOptionParameter
- ? field.getAnnotation(Option.class).split()
- : field.getAnnotation(Parameters.class).split();
- Text result = ansi.new Text("");
- String sep = isOptionParameter ? separator : "";
- Text paramName = ansi.apply(renderParameterName(field), styles);
- if (!empty(split)) {
- paramName = paramName.append("[" + split).append(paramName).append("]...");
- } // #194
- for (int i = 0; i < arity.min; i++) {
- result = result.append(sep).append(paramName);
- sep = " ";
- }
- if (arity.isVariable) {
- if (result.length == 0) { // arity="*" or arity="0..*"
- result = result.append(sep + "[").append(paramName).append("]...");
- } else if (!result.plainString().endsWith("...")) { // split param may already end with "..."
- result = result.append("...");
- }
- } else {
- sep = result.length == 0 ? (isOptionParameter ? separator : "") : " ";
- for (int i = arity.min; i < arity.max; i++) {
- if (sep.trim().length() == 0) {
- result = result.append(sep + "[").append(paramName);
- } else {
- result = result.append("[" + sep).append(paramName);
- }
- sep = " ";
- }
- for (int i = arity.min; i < arity.max; i++) {
- result = result.append("]");
- }
- }
- return result;
- }
-
- private static String renderParameterName(final Field field) {
- String result = null;
- if (field.isAnnotationPresent(Option.class)) {
- result = field.getAnnotation(Option.class).paramLabel();
- } else if (field.isAnnotationPresent(Parameters.class)) {
- result = field.getAnnotation(Parameters.class).paramLabel();
- }
- if (result != null && result.trim().length() > 0) {
- return result.trim();
- }
- String name = field.getName();
- if (Map.class.isAssignableFrom(field.getType())) { // #195 better param labels for map fields
- final Class<?>[] paramTypes = getTypeAttribute(field);
- if (paramTypes.length < 2 || paramTypes[0] == null || paramTypes[1] == null) {
- name = "String=String";
- } else {
- name = paramTypes[0].getSimpleName() + "=" + paramTypes[1].getSimpleName();
- }
- }
- return "<" + name + ">";
- }
- }
- /** Use a Layout to format usage help text for options and parameters in tabular format.
- * <p>Delegates to the renderers to create {@link Text} values for the annotated fields, and uses a
- * {@link TextTable} to display these values in tabular format. Layout is responsible for deciding which values
- * to display where in the table. By default, Layout shows one option or parameter per table row.</p>
- * <p>Customize by overriding the {@link #layout(Field, CommandLine.Help.Ansi.Text[][])} method.</p>
- * @see IOptionRenderer rendering options to text
- * @see IParameterRenderer rendering parameters to text
- * @see TextTable showing values in a tabular format
- */
- public static class Layout {
- protected final ColorScheme colorScheme;
- protected final TextTable table;
- protected IOptionRenderer optionRenderer;
- protected IParameterRenderer parameterRenderer;
-
- /** Constructs a Layout with the specified color scheme, a new default TextTable, the
- * {@linkplain Help#createDefaultOptionRenderer() default option renderer}, and the
- * {@linkplain Help#createDefaultParameterRenderer() default parameter renderer}.
- * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message */
- public Layout(final ColorScheme colorScheme) {
- this(colorScheme, new TextTable(colorScheme.ansi()));
- }
-
- /** Constructs a Layout with the specified color scheme, the specified TextTable, the
- * {@linkplain Help#createDefaultOptionRenderer() default option renderer}, and the
- * {@linkplain Help#createDefaultParameterRenderer() default parameter renderer}.
- * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message
- * @param textTable the TextTable to lay out parts of the usage help message in tabular format */
- public Layout(final ColorScheme colorScheme, final TextTable textTable) {
- this(colorScheme, textTable, new DefaultOptionRenderer(), new DefaultParameterRenderer());
- }
- /** Constructs a Layout with the specified color scheme, the specified TextTable, the
- * specified option renderer and the specified parameter renderer.
- * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message
- * @param optionRenderer the object responsible for rendering Options to Text
- * @param parameterRenderer the object responsible for rendering Parameters to Text
- * @param textTable the TextTable to lay out parts of the usage help message in tabular format */
- public Layout(
- final ColorScheme colorScheme,
- final TextTable textTable,
- final IOptionRenderer optionRenderer,
- final IParameterRenderer parameterRenderer) {
- this.colorScheme = Assert.notNull(colorScheme, "colorScheme");
- this.table = Assert.notNull(textTable, "textTable");
- this.optionRenderer = Assert.notNull(optionRenderer, "optionRenderer");
- this.parameterRenderer = Assert.notNull(parameterRenderer, "parameterRenderer");
- }
- /**
- * Copies the specified text values into the correct cells in the {@link TextTable}. This implementation
- * delegates to {@link TextTable#addRowValues(CommandLine.Help.Ansi.Text...)} for each row of values.
- * <p>Subclasses may override.</p>
- * @param field the field annotated with the specified Option or Parameters
- * @param cellValues the text values representing the Option/Parameters, to be displayed in tabular form
- */
- public void layout(final Field field, final Text[][] cellValues) {
- for (final Text[] oneRow : cellValues) {
- table.addRowValues(oneRow);
- }
- }
- /** Calls {@link #addOption(Field, CommandLine.Help.IParamLabelRenderer)} for all non-hidden Options in the list.
- * @param fields fields annotated with {@link Option} to add usage descriptions for
- * @param paramLabelRenderer object that knows how to render option parameters */
- public void addOptions(final List<Field> fields, final IParamLabelRenderer paramLabelRenderer) {
- for (final Field field : fields) {
- final Option option = field.getAnnotation(Option.class);
- if (!option.hidden()) {
- addOption(field, paramLabelRenderer);
- }
- }
- }
- /**
- * Delegates to the {@link #optionRenderer option renderer} of this layout to obtain
- * text values for the specified {@link Option}, and then calls the {@link #layout(Field, CommandLine.Help.Ansi.Text[][])}
- * method to write these text values into the correct cells in the TextTable.
- * @param field the field annotated with the specified Option
- * @param paramLabelRenderer knows how to render option parameters
- */
- public void addOption(final Field field, final IParamLabelRenderer paramLabelRenderer) {
- final Option option = field.getAnnotation(Option.class);
- final Text[][] values = optionRenderer.render(option, field, paramLabelRenderer, colorScheme);
- layout(field, values);
- }
- /** Calls {@link #addPositionalParameter(Field, CommandLine.Help.IParamLabelRenderer)} for all non-hidden Parameters in the list.
- * @param fields fields annotated with {@link Parameters} to add usage descriptions for
- * @param paramLabelRenderer knows how to render option parameters */
- public void addPositionalParameters(
- final List<Field> fields, final IParamLabelRenderer paramLabelRenderer) {
- for (final Field field : fields) {
- final Parameters parameters = field.getAnnotation(Parameters.class);
- if (!parameters.hidden()) {
- addPositionalParameter(field, paramLabelRenderer);
- }
- }
- }
- /**
- * Delegates to the {@link #parameterRenderer parameter renderer} of this layout
- * to obtain text values for the specified {@link Parameters}, and then calls
- * {@link #layout(Field, CommandLine.Help.Ansi.Text[][])} to write these text values into the correct cells in the TextTable.
- * @param field the field annotated with the specified Parameters
- * @param paramLabelRenderer knows how to render option parameters
- */
- public void addPositionalParameter(final Field field, final IParamLabelRenderer paramLabelRenderer) {
- final Parameters option = field.getAnnotation(Parameters.class);
- final Text[][] values = parameterRenderer.render(option, field, paramLabelRenderer, colorScheme);
- layout(field, values);
- }
- /** Returns the section of the usage help message accumulated in the TextTable owned by this layout. */
- @Override
- public String toString() {
- return table.toString();
- }
- }
- /** Sorts short strings before longer strings. */
- static class ShortestFirst implements Comparator<String> {
- @Override
- public int compare(final String o1, final String o2) {
- return o1.length() - o2.length();
- }
- /** Sorts the specified array of Strings shortest-first and returns it. */
- public static String[] sort(final String[] names) {
- Arrays.sort(names, new ShortestFirst());
- return names;
- }
- }
- /** Sorts {@code Option} instances by their name in case-insensitive alphabetic order. If an Option has
- * multiple names, the shortest name is used for the sorting. Help options follow non-help options. */
- static class SortByShortestOptionNameAlphabetically implements Comparator<Field> {
- @Override
- public int compare(final Field f1, final Field f2) {
- final Option o1 = f1.getAnnotation(Option.class);
- final Option o2 = f2.getAnnotation(Option.class);
- if (o1 == null) {
- return 1;
- } else if (o2 == null) {
- return -1;
- } // options before params
- final String[] names1 = ShortestFirst.sort(o1.names());
- final String[] names2 = ShortestFirst.sort(o2.names());
- int result = toRootUpperCase(names1[0]).compareTo(toRootUpperCase(names2[0])); // case insensitive sort
- result = result == 0 ? -names1[0].compareTo(names2[0]) : result; // lower case before upper case
- return o1.help() == o2.help() ? result : o2.help() ? -1 : 1; // help options come last
- }
- }
- /** Sorts {@code Option} instances by their max arity first, then their min arity, then delegates to super class. */
- static class SortByOptionArityAndNameAlphabetically extends SortByShortestOptionNameAlphabetically {
- @Override
- public int compare(final Field f1, final Field f2) {
- final Option o1 = f1.getAnnotation(Option.class);
- final Option o2 = f2.getAnnotation(Option.class);
- final Range arity1 = Range.optionArity(f1);
- final Range arity2 = Range.optionArity(f2);
- int result = arity1.max - arity2.max;
- if (result == 0) {
- result = arity1.min - arity2.min;
- }
- if (result == 0) { // arity is same
- if (isMultiValue(f1) && !isMultiValue(f2)) {
- result = 1;
- } // f1 > f2
- if (!isMultiValue(f1) && isMultiValue(f2)) {
- result = -1;
- } // f1 < f2
- }
- return result == 0 ? super.compare(f1, f2) : result;
- }
- }
- /**
- * <p>Responsible for spacing out {@link Text} values according to the {@link Column} definitions the table was
- * created with. Columns have a width, indentation, and an overflow policy that decides what to do if a value is
- * longer than the column's width.</p>
- */
- public static class TextTable {
- /**
- * Helper class to index positions in a {@code Help.TextTable}.
- * @since 2.0
- */
- public static class Cell {
- /** Table column index (zero based). */
- public final int column;
- /** Table row index (zero based). */
- public final int row;
- /** Constructs a new Cell with the specified coordinates in the table.
- * @param column the zero-based table column
- * @param row the zero-based table row */
- public Cell(final int column, final int row) {
- this.column = column;
- this.row = row;
- }
- }
-
- /** The column definitions of this table. */
- public final Column[] columns;
-
- /** The {@code char[]} slots of the {@code TextTable} to copy text values into. */
- protected final List<Text> columnValues = new ArrayList<Text>();
-
- /** By default, indent wrapped lines by 2 spaces. */
- public int indentWrappedLines = 2;
-
- private final Ansi ansi;
-
- /** Constructs a TextTable with five columns as follows:
- * <ol>
- * <li>required option/parameter marker (width: 2, indent: 0, TRUNCATE on overflow)</li>
- * <li>short option name (width: 2, indent: 0, TRUNCATE on overflow)</li>
- * <li>comma separator (width: 1, indent: 0, TRUNCATE on overflow)</li>
- * <li>long option name(s) (width: 24, indent: 1, SPAN multiple columns on overflow)</li>
- * <li>description line(s) (width: 51, indent: 1, WRAP to next row on overflow)</li>
- * </ol>
- * @param ansi whether to emit ANSI escape codes or not
- */
- public TextTable(final Ansi ansi) {
- // "* -c, --create Creates a ...."
- this(ansi, new Column[] {
- new Column(2, 0, TRUNCATE), // "*"
- new Column(2, 0, TRUNCATE), // "-c"
- new Column(1, 0, TRUNCATE), // ","
- new Column(optionsColumnWidth - 2 - 2 - 1, 1, SPAN), // " --create"
- new Column(usageHelpWidth - optionsColumnWidth, 1, WRAP) // " Creates a ..."
- });
- }
-
- /** Constructs a new TextTable with columns with the specified width, all SPANning multiple columns on
- * overflow except the last column which WRAPS to the next row.
- * @param ansi whether to emit ANSI escape codes or not
- * @param columnWidths the width of the table columns (all columns have zero indent)
- */
- public TextTable(final Ansi ansi, final int... columnWidths) {
- this.ansi = Assert.notNull(ansi, "ansi");
- columns = new Column[columnWidths.length];
- for (int i = 0; i < columnWidths.length; i++) {
- columns[i] = new Column(columnWidths[i], 0, i == columnWidths.length - 1 ? SPAN : WRAP);
- }
- }
- /** Constructs a {@code TextTable} with the specified columns.
- * @param ansi whether to emit ANSI escape codes or not
- * @param columns columns to construct this TextTable with */
- public TextTable(final Ansi ansi, final Column... columns) {
- this.ansi = Assert.notNull(ansi, "ansi");
- this.columns = Assert.notNull(columns, "columns");
- if (columns.length == 0) {
- throw new IllegalArgumentException("At least one column is required");
- }
- }
- /** Returns the {@code Text} slot at the specified row and column to write a text value into.
- * @param row the row of the cell whose Text to return
- * @param col the column of the cell whose Text to return
- * @return the Text object at the specified row and column
- * @since 2.0 */
- public Text textAt(final int row, final int col) {
- return columnValues.get(col + (row * columns.length));
- }
-
- /** Returns the {@code Text} slot at the specified row and column to write a text value into.
- * @param row the row of the cell whose Text to return
- * @param col the column of the cell whose Text to return
- * @return the Text object at the specified row and column
- * @deprecated use {@link #textAt(int, int)} instead */
- @Deprecated
- public Text cellAt(final int row, final int col) {
- return textAt(row, col);
- }
-
- /** Returns the current number of rows of this {@code TextTable}.
- * @return the current number of rows in this TextTable */
- public int rowCount() {
- return columnValues.size() / columns.length;
- }
-
- /** Adds the required {@code char[]} slots for a new row to the {@link #columnValues} field. */
- public void addEmptyRow() {
- for (int i = 0; i < columns.length; i++) {
- columnValues.add(ansi.new Text(columns[i].width));
- }
- }
-
- /** Delegates to {@link #addRowValues(CommandLine.Help.Ansi.Text...)}.
- * @param values the text values to display in each column of the current row */
- public void addRowValues(final String... values) {
- final Text[] array = new Text[values.length];
- for (int i = 0; i < array.length; i++) {
- array[i] = values[i] == null ? Ansi.EMPTY_TEXT : ansi.new Text(values[i]);
- }
- addRowValues(array);
- }
- /**
- * Adds a new {@linkplain TextTable#addEmptyRow() empty row}, then calls {@link
- * TextTable#putValue(int, int, CommandLine.Help.Ansi.Text) putValue} for each of the specified values, adding more empty rows
- * if the return value indicates that the value spanned multiple columns or was wrapped to multiple rows.
- * @param values the values to write into a new row in this TextTable
- * @throws IllegalArgumentException if the number of values exceeds the number of Columns in this table
- */
- public void addRowValues(final Text... values) {
- if (values.length > columns.length) {
- throw new IllegalArgumentException(
- values.length + " values don't fit in " + columns.length + " columns");
- }
- addEmptyRow();
- for (int col = 0; col < values.length; col++) {
- final int row = rowCount() - 1; // write to last row: previous value may have wrapped to next row
- final Cell cell = putValue(row, col, values[col]);
-
- // add row if a value spanned/wrapped and there are still remaining values
- if ((cell.row != row || cell.column != col) && col != values.length - 1) {
- addEmptyRow();
- }
- }
- }
- /**
- * Writes the specified value into the cell at the specified row and column and returns the last row and
- * column written to. Depending on the Column's {@link Column#overflow Overflow} policy, the value may span
- * multiple columns or wrap to multiple rows when larger than the column width.
- * @param row the target row in the table
- * @param col the target column in the table to write to
- * @param value the value to write
- * @return a Cell indicating the position in the table that was last written to (since 2.0)
- * @throws IllegalArgumentException if the specified row exceeds the table's {@linkplain
- * TextTable#rowCount() row count}
- * @since 2.0 (previous versions returned a {@code java.awt.Point} object)
- */
- public Cell putValue(int row, int col, Text value) {
- if (row > rowCount() - 1) {
- throw new IllegalArgumentException("Cannot write to row " + row + ": rowCount=" + rowCount());
- }
- if (value == null || value.plain.length() == 0) {
- return new Cell(col, row);
- }
- final Column column = columns[col];
- int indent = column.indent;
- switch (column.overflow) {
- case TRUNCATE:
- copy(value, textAt(row, col), indent);
- return new Cell(col, row);
- case SPAN:
- final int startColumn = col;
- do {
- final boolean lastColumn = col == columns.length - 1;
- final int charsWritten = lastColumn
- ? copy(BreakIterator.getLineInstance(), value, textAt(row, col), indent)
- : copy(value, textAt(row, col), indent);
- value = value.substring(charsWritten);
- indent = 0;
- if (value.length > 0) { // value did not fit in column
- ++col; // write remainder of value in next column
- }
- if (value.length > 0 && col >= columns.length) { // we filled up all columns on this row
- addEmptyRow();
- row++;
- col = startColumn;
- indent = column.indent + indentWrappedLines;
- }
- } while (value.length > 0);
- return new Cell(col, row);
- case WRAP:
- final BreakIterator lineBreakIterator = BreakIterator.getLineInstance();
- do {
- final int charsWritten = copy(lineBreakIterator, value, textAt(row, col), indent);
- value = value.substring(charsWritten);
- indent = column.indent + indentWrappedLines;
- if (value.length > 0) { // value did not fit in column
- ++row; // write remainder of value in next row
- addEmptyRow();
- }
- } while (value.length > 0);
- return new Cell(col, row);
- }
- throw new IllegalStateException(column.overflow.toString());
- }
-
- private static int length(final Text str) {
- return str.length; // TODO count some characters as double length
- }
-
- private int copy(final BreakIterator line, final Text text, final Text columnValue, final int offset) {
- // Deceive the BreakIterator to ensure no line breaks after '-' character
- line.setText(text.plainString().replace("-", "\u00ff"));
- int done = 0;
- for (int start = line.first(), end = line.next();
- end != BreakIterator.DONE;
- start = end, end = line.next()) {
- final Text word = text.substring(start, end); // .replace("\u00ff", "-"); // not needed
- if (columnValue.maxLength >= offset + done + length(word)) {
- done += copy(word, columnValue, offset + done); // TODO localized length
- } else {
- break;
- }
- }
- if (done == 0 && length(text) > columnValue.maxLength) {
- // The value is a single word that is too big to be written to the column. Write as much as we can.
- done = copy(text, columnValue, offset);
- }
- return done;
- }
-
- private static int copy(final Text value, final Text destination, final int offset) {
- final int length = Math.min(value.length, destination.maxLength - offset);
- value.getStyledChars(value.from, length, destination, offset);
- return length;
- }
-
- /** Copies the text representation that we built up from the options into the specified StringBuilder.
- * @param text the StringBuilder to write into
- * @return the specified StringBuilder object (to allow method chaining and a more fluid API) */
- public StringBuilder toString(final StringBuilder text) {
- final int columnCount = this.columns.length;
- final StringBuilder row = new StringBuilder(usageHelpWidth);
- for (int i = 0; i < columnValues.size(); i++) {
- final Text column = columnValues.get(i);
- row.append(column.toString());
- row.append(new String(spaces(columns[i % columnCount].width - column.length)));
- if (i % columnCount == columnCount - 1) {
- int lastChar = row.length() - 1;
- while (lastChar >= 0 && row.charAt(lastChar) == ' ') {
- lastChar--;
- } // rtrim
- row.setLength(lastChar + 1);
- text.append(row.toString()).append(System.getProperty("line.separator"));
- row.setLength(0);
- }
- }
- // if (Ansi.enabled()) { text.append(Style.reset.off()); }
- return text;
- }
-
- @Override
- public String toString() {
- return toString(new StringBuilder()).toString();
- }
- }
- /** Columns define the width, indent (leading number of spaces in a column before the value) and
- * {@linkplain Overflow Overflow} policy of a column in a {@linkplain TextTable TextTable}. */
- public static class Column {
-
- /** Policy for handling text that is longer than the column width:
- * span multiple columns, wrap to the next row, or simply truncate the portion that doesn't fit. */
- public enum Overflow {
- TRUNCATE,
- SPAN,
- WRAP
- }
-
- /** Column width in characters */
- public final int width;
-
- /** Indent (number of empty spaces at the start of the column preceding the text value) */
- public final int indent;
-
- /** Policy that determines how to handle values larger than the column width. */
- public final Overflow overflow;
-
- public Column(final int width, final int indent, final Overflow overflow) {
- this.width = width;
- this.indent = indent;
- this.overflow = Assert.notNull(overflow, "overflow");
- }
- }
-
- /** All usage help message are generated with a color scheme that assigns certain styles and colors to common
- * parts of a usage message: the command name, options, positional parameters and option parameters.
- * Users may customize these styles by creating Help with a custom color scheme.
- * <p>Note that these options and styles may not be rendered if ANSI escape codes are not
- * {@linkplain Ansi#enabled() enabled}.</p>
- * @see Help#defaultColorScheme(Ansi)
- */
- public static class ColorScheme {
- public final List<IStyle> commandStyles = new ArrayList<IStyle>();
- public final List<IStyle> optionStyles = new ArrayList<IStyle>();
- public final List<IStyle> parameterStyles = new ArrayList<IStyle>();
- public final List<IStyle> optionParamStyles = new ArrayList<IStyle>();
- private final Ansi ansi;
-
- /** Constructs a new ColorScheme with {@link Help.Ansi#AUTO}. */
- public ColorScheme() {
- this(Ansi.AUTO);
- }
-
- /** Constructs a new ColorScheme with the specified Ansi enabled mode.
- * @param ansi whether to emit ANSI escape codes or not
- */
- public ColorScheme(final Ansi ansi) {
- this.ansi = Assert.notNull(ansi, "ansi");
- }
-
- /** Adds the specified styles to the registered styles for commands in this color scheme and returns this color scheme.
- * @param styles the styles to add to the registered styles for commands in this color scheme
- * @return this color scheme to enable method chaining for a more fluent API */
- public ColorScheme commands(final IStyle... styles) {
- return addAll(commandStyles, styles);
- }
- /** Adds the specified styles to the registered styles for options in this color scheme and returns this color scheme.
- * @param styles the styles to add to registered the styles for options in this color scheme
- * @return this color scheme to enable method chaining for a more fluent API */
- public ColorScheme options(final IStyle... styles) {
- return addAll(optionStyles, styles);
- }
- /** Adds the specified styles to the registered styles for positional parameters in this color scheme and returns this color scheme.
- * @param styles the styles to add to registered the styles for parameters in this color scheme
- * @return this color scheme to enable method chaining for a more fluent API */
- public ColorScheme parameters(final IStyle... styles) {
- return addAll(parameterStyles, styles);
- }
- /** Adds the specified styles to the registered styles for option parameters in this color scheme and returns this color scheme.
- * @param styles the styles to add to the registered styles for option parameters in this color scheme
- * @return this color scheme to enable method chaining for a more fluent API */
- public ColorScheme optionParams(final IStyle... styles) {
- return addAll(optionParamStyles, styles);
- }
- /** Returns a Text with all command styles applied to the specified command string.
- * @param command the command string to apply the registered command styles to
- * @return a Text with all command styles applied to the specified command string */
- public Ansi.Text commandText(final String command) {
- return ansi().apply(command, commandStyles);
- }
- /** Returns a Text with all option styles applied to the specified option string.
- * @param option the option string to apply the registered option styles to
- * @return a Text with all option styles applied to the specified option string */
- public Ansi.Text optionText(final String option) {
- return ansi().apply(option, optionStyles);
- }
- /** Returns a Text with all parameter styles applied to the specified parameter string.
- * @param parameter the parameter string to apply the registered parameter styles to
- * @return a Text with all parameter styles applied to the specified parameter string */
- public Ansi.Text parameterText(final String parameter) {
- return ansi().apply(parameter, parameterStyles);
- }
- /** Returns a Text with all optionParam styles applied to the specified optionParam string.
- * @param optionParam the option parameter string to apply the registered option parameter styles to
- * @return a Text with all option parameter styles applied to the specified option parameter string */
- public Ansi.Text optionParamText(final String optionParam) {
- return ansi().apply(optionParam, optionParamStyles);
- }
-
- /** Replaces colors and styles in this scheme with ones specified in system properties, and returns this scheme.
- * Supported property names:<ul>
- * <li>{@code picocli.color.commands}</li>
- * <li>{@code picocli.color.options}</li>
- * <li>{@code picocli.color.parameters}</li>
- * <li>{@code picocli.color.optionParams}</li>
- * </ul><p>Property values can be anything that {@link Help.Ansi.Style#parse(String)} can handle.</p>
- * @return this ColorScheme
- */
- public ColorScheme applySystemProperties() {
- replace(commandStyles, System.getProperty("picocli.color.commands"));
- replace(optionStyles, System.getProperty("picocli.color.options"));
- replace(parameterStyles, System.getProperty("picocli.color.parameters"));
- replace(optionParamStyles, System.getProperty("picocli.color.optionParams"));
- return this;
- }
-
- private void replace(final List<IStyle> styles, final String property) {
- if (property != null) {
- styles.clear();
- addAll(styles, Style.parse(property));
- }
- }
-
- private ColorScheme addAll(final List<IStyle> styles, final IStyle... add) {
- styles.addAll(Arrays.asList(add));
- return this;
- }
-
- public Ansi ansi() {
- return ansi;
- }
- }
... 798 lines suppressed ...