View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements. See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache license, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License. You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the license for the specific language governing permissions and
    15   * limitations under the license.
    16   */
    17
    18  package org.apache.logging.log4j.core.tools;
    19
    20  import java.io.PrintStream;
    21  import java.util.ArrayList;
    22  import java.util.Arrays;
    23  import java.util.List;
    24
    25  /**
    26   * Generates source code for custom or extended logger wrappers.
    27   * <p>
    28   * Usage:
    29   * <p>
    30   * To generate source code for an extended logger that adds custom log levels to the existing ones: <br>
    31   * {@code java org.apache.logging.log4j.core.tools.Generate$ExtendedLogger <logger.class.name> <CUSTOMLEVEL>=<WEIGHT>
    32   * [CUSTOMLEVEL2=WEIGHT2 [CUSTOMLEVEL3=WEIGHT3] ...]}
    33   * <p>
    34   * Example of creating an extended logger:<br>
    35   * {@code java org.apache.logging.log4j.core.tools.Generate$ExtendedLogger com.mycomp.ExtLogger DIAG=350 NOTICE=450 VERBOSE=550}
    36   * <p>
    37   * To generate source code for a custom logger that replaces the existing log levels with custom ones: <br>
    38   * {@code java org.apache.logging.log4j.core.tools.Generate$CustomLogger <logger.class.name> <CUSTOMLEVEL>=<WEIGHT>
    39   * [CUSTOMLEVEL2=WEIGHT2 [CUSTOMLEVEL3=WEIGHT3] ...]}
    40   * <p>
    41   * Example of creating a custom logger:<br>
    42   * {@code java org.apache.logging.log4j.core.tools.Generate$CustomLogger com.mycomp.MyLogger DEFCON1=350 DEFCON2=450 DEFCON3=550}
    43   */
    44  public final class Generate {
    45
    46      static final String PACKAGE_DECLARATION = "package %s;%n%n";
    47
    48      static enum Type {
    49          CUSTOM {
    50              @Override
    51              String imports() {
    52                  return "" //
    53                          + "import java.io.Serializable;%n" //
    54                          + "import org.apache.logging.log4j.Level;%n" //
    55                          + "import org.apache.logging.log4j.LogManager;%n" + "import org.apache.logging.log4j.Logger;%n" //
    56                          + "import org.apache.logging.log4j.Marker;%n" //
    57                          + "import org.apache.logging.log4j.message.Message;%n" //
    58                          + "import org.apache.logging.log4j.message.MessageFactory;%n" //
    59                          + "import org.apache.logging.log4j.spi.AbstractLogger;%n" //
    60                          + "import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;%n" //
    61                          + "%n";
    62              }
    63
    64              @Override
    65              String declaration() {
    66                  return "" //
    67                          + "/**%n" //
    68                          + " * Custom Logger interface with convenience methods for%n" //
    69                          + " * %s%n" //
    70                          + " */%n" //
    71                          + "public final class %s implements Serializable {%n" //
    72                          + "    private static final long serialVersionUID = " + System.nanoTime() + "L;%n" //
    73                          + "    private final ExtendedLoggerWrapper logger;%n" //
    74                          + "%n" //
    75                  ;
    76              }
    77
    78              @Override
    79              String constructor() {
    80                  return "" //
    81                          + "%n" //
    82                          + "    private %s(final Logger logger) {%n" //
    83                          + "        this.logger = new ExtendedLoggerWrapper((AbstractLogger) logger, logger.getName(), logger.getMessageFactory());%n" //
    84                          + "    }%n" //
    85                  ;
    86              }
    87
    88              @Override
    89              Class<?> generator() {
    90                  return CustomLogger.class;
    91              }
    92          },
    93          EXTEND {
    94              @Override
    95              String imports() {
    96                  return "" //
    97                          + "import org.apache.logging.log4j.Level;%n" //
    98                          + "import org.apache.logging.log4j.LogManager;%n" + "import org.apache.logging.log4j.Logger;%n" //
    99                          + "import org.apache.logging.log4j.Marker;%n" //
    100                         + "import org.apache.logging.log4j.message.Message;%n" //
    101                         + "import org.apache.logging.log4j.message.MessageFactory;%n" //
    102                         + "import org.apache.logging.log4j.spi.AbstractLogger;%n" //
    103                         + "import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;%n" //
    104                         + "%n";
    105             }
    106
    107             @Override
    108             String declaration() {
    109                 return "" //
    110                         + "/**%n" //
    111                         + " * Extended Logger interface with convenience methods for%n" //
    112                         + " * %s%n" //
    113                         + " */%n" //
    114                         + "public final class %s extends ExtendedLoggerWrapper {%n" //
    115                         + "    private static final long serialVersionUID = " + System.nanoTime() + "L;%n" //
    116                         + "    private final ExtendedLoggerWrapper logger;%n" //
    117                         + "%n" //
    118                 ;
    119             }
    120
    121             @Override
    122             String constructor() {
    123                 return "" //
    124                         + "%n" //
    125                         + "    private %s(final Logger logger) {%n" //
    126                         + "        super((AbstractLogger) logger, logger.getName(), logger.getMessageFactory());%n" //
    127                         + "        this.logger = this;%n" //
    128                         + "    }%n" //
    129                 ;
    130             }
    131
    132             @Override
    133             Class<?> generator() {
    134                 return ExtendedLogger.class;
    135             }
    136         };
    137         abstract String imports();
    138
    139         abstract String declaration();
    140
    141         abstract String constructor();
    142
    143         abstract Class<?> generator();
    144     }
    145
    146     static final String FQCN_FIELD = "" //
    147             + "    private static final String FQCN = %s.class.getName();%n";
    148
    149     static final String LEVEL_FIELD = "" //
    150             + "    private static final Level %s = Level.forName(\"%s\", %d);%n";
    151
    152     static final String FACTORY_METHODS = "" //
    153             + "%n" //
    154             + "    /**%n" //
    155             + "     * Returns a custom Logger with the name of the calling class.%n" //
    156             + "     * %n" //
    157             + "     * @return The custom Logger for the calling class.%n" //
    158             + "     */%n" //
    159             + "    public static CLASSNAME create() {%n" //
    160             + "        final Logger wrapped = LogManager.getLogger();%n" //
    161             + "        return new CLASSNAME(wrapped);%n" //
    162             + "    }%n" //
    163             + "%n" //
    164             + "    /**%n" //
    165             + "     * Returns a custom Logger using the fully qualified name of the Class as%n" //
    166             + "     * the Logger name.%n" //
    167             + "     * %n" //
    168             + "     * @param loggerName The Class whose name should be used as the Logger name.%n" //
    169             + "     *            If null it will default to the calling class.%n" //
    170             + "     * @return The custom Logger.%n" //
    171             + "     */%n" //
    172             + "    public static CLASSNAME create(final Class<?> loggerName) {%n" //
    173             + "        final Logger wrapped = LogManager.getLogger(loggerName);%n" //
    174             + "        return new CLASSNAME(wrapped);%n" //
    175             + "    }%n" //
    176             + "%n" //
    177             + "    /**%n" //
    178             + "     * Returns a custom Logger using the fully qualified name of the Class as%n" //
    179             + "     * the Logger name.%n" //
    180             + "     * %n" //
    181             + "     * @param loggerName The Class whose name should be used as the Logger name.%n" //
    182             + "     *            If null it will default to the calling class.%n" //
    183             + "     * @param messageFactory The message factory is used only when creating a%n" //
    184             + "     *            logger, subsequent use does not change the logger but will log%n" //
    185             + "     *            a warning if mismatched.%n" //
    186             + "     * @return The custom Logger.%n" //
    187             + "     */%n" //
    188             + "    public static CLASSNAME create(final Class<?> loggerName, final MessageFactory factory) {%n" //
    189             + "        final Logger wrapped = LogManager.getLogger(loggerName, factory);%n" //
    190             + "        return new CLASSNAME(wrapped);%n" //
    191             + "    }%n" //
    192             + "%n" //
    193             + "    /**%n" //
    194             + "     * Returns a custom Logger using the fully qualified class name of the value%n" //
    195             + "     * as the Logger name.%n" //
    196             + "     * %n" //
    197             + "     * @param value The value whose class name should be used as the Logger%n" //
    198             + "     *            name. If null the name of the calling class will be used as%n" //
    199             + "     *            the logger name.%n" //
    200             + "     * @return The custom Logger.%n" //
    201             + "     */%n" //
    202             + "    public static CLASSNAME create(final Object value) {%n" //
    203             + "        final Logger wrapped = LogManager.getLogger(value);%n" //
    204             + "        return new CLASSNAME(wrapped);%n" //
    205             + "    }%n" //
    206             + "%n" //
    207             + "    /**%n" //
    208             + "     * Returns a custom Logger using the fully qualified class name of the value%n" //
    209             + "     * as the Logger name.%n" //
    210             + "     * %n" //
    211             + "     * @param value The value whose class name should be used as the Logger%n" //
    212             + "     *            name. If null the name of the calling class will be used as%n" //
    213             + "     *            the logger name.%n" //
    214             + "     * @param messageFactory The message factory is used only when creating a%n" //
    215             + "     *            logger, subsequent use does not change the logger but will log%n" //
    216             + "     *            a warning if mismatched.%n" //
    217             + "     * @return The custom Logger.%n" //
    218             + "     */%n" //
    219             + "    public static CLASSNAME create(final Object value, final MessageFactory factory) {%n" //
    220             + "        final Logger wrapped = LogManager.getLogger(value, factory);%n" //
    221             + "        return new CLASSNAME(wrapped);%n" //
    222             + "    }%n" //
    223             + "%n" //
    224             + "    /**%n" //
    225             + "     * Returns a custom Logger with the specified name.%n" //
    226             + "     * %n" //
    227             + "     * @param name The logger name. If null the name of the calling class will%n" //
    228             + "     *            be used.%n" //
    229             + "     * @return The custom Logger.%n" //
    230             + "     */%n" //
    231             + "    public static CLASSNAME create(final String name) {%n" //
    232             + "        final Logger wrapped = LogManager.getLogger(name);%n" //
    233             + "        return new CLASSNAME(wrapped);%n" //
    234             + "    }%n" //
    235             + "%n" //
    236             + "    /**%n" //
    237             + "     * Returns a custom Logger with the specified name.%n" //
    238             + "     * %n" //
    239             + "     * @param name The logger name. If null the name of the calling class will%n" //
    240             + "     *            be used.%n" //
    241             + "     * @param messageFactory The message factory is used only when creating a%n" //
    242             + "     *            logger, subsequent use does not change the logger but will log%n" //
    243             + "     *            a warning if mismatched.%n" //
    244             + "     * @return The custom Logger.%n" //
    245             + "     */%n" //
    246             + "    public static CLASSNAME create(final String name, final MessageFactory factory) {%n" //
    247             + "        final Logger wrapped = LogManager.getLogger(name, factory);%n" //
    248             + "        return new CLASSNAME(wrapped);%n" //
    249             + "    }%n" //
    250     ;
    251     static final String METHODS = "" //
    252             + "%n" //
    253             + "    /**%n" //
    254             + "     * Logs a message with the specific Marker at the {@code CUSTOM_LEVEL} level.%n" //
    255             + "     * %n" //
    256             + "     * @param marker the marker data specific to this log statement%n" //
    257             + "     * @param msg the message string to be logged%n" //
    258             + "     */%n" //
    259             + "    public void methodName(final Marker marker, final Message msg) {%n" //
    260             + "        logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msg, (Throwable) null);%n" //
    261             + "    }%n" //
    262             + "%n" //
    263             + "    /**%n" //
    264             + "     * Logs a message with the specific Marker at the {@code CUSTOM_LEVEL} level.%n" //
    265             + "     * %n" //
    266             + "     * @param marker the marker data specific to this log statement%n" //
    267             + "     * @param msg the message string to be logged%n" //
    268             + "     * @param t A Throwable or null.%n" //
    269             + "     */%n" //
    270             + "    public void methodName(final Marker marker, final Message msg, final Throwable t) {%n" //
    271             + "        logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msg, t);%n" //
    272             + "    }%n" //
    273             + "%n" //
    274             + "    /**%n" //
    275             + "     * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" //
    276             + "     * %n" //
    277             + "     * @param marker the marker data specific to this log statement%n" //
    278             + "     * @param message the message object to log.%n" //
    279             + "     */%n" //
    280             + "    public void methodName(final Marker marker, final Object message) {%n" //
    281             + "        logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, (Throwable) null);%n" //
    282             + "    }%n" //
    283             + "%n" //
    284             + "    /**%n" //
    285             + "     * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" //
    286             + "     * the {@link Throwable} {@code t} passed as parameter.%n" //
    287             + "     * %n" //
    288             + "     * @param marker the marker data specific to this log statement%n" //
    289             + "     * @param message the message to log.%n" //
    290             + "     * @param t the exception to log, including its stack trace.%n" //
    291             + "     */%n" //
    292             + "    public void methodName(final Marker marker, final Object message, final Throwable t) {%n" //
    293             + "        logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, t);%n" //
    294             + "    }%n" //
    295             + "%n" //
    296             + "    /**%n" //
    297             + "     * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" //
    298             + "     * %n" //
    299             + "     * @param marker the marker data specific to this log statement%n" //
    300             + "     * @param message the message object to log.%n" //
    301             + "     */%n" //
    302             + "    public void methodName(final Marker marker, final String message) {%n" //
    303             + "        logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, (Throwable) null);%n" //
    304             + "    }%n" //
    305             + "%n" //
    306             + "    /**%n" //
    307             + "     * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" //
    308             + "     * %n" //
    309             + "     * @param marker the marker data specific to this log statement%n" //
    310             + "     * @param message the message to log; the format depends on the message factory.%n" //
    311             + "     * @param params parameters to the message.%n" //
    312             + "     * @see #getMessageFactory()%n" //
    313             + "     */%n" //
    314             + "    public void methodName(final Marker marker, final String message, final Object... params) {%n" //
    315             + "        logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, params);%n" //
    316             + "    }%n" //
    317             + "%n" //
    318             + "    /**%n" //
    319             + "     * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" //
    320             + "     * the {@link Throwable} {@code t} passed as parameter.%n" //
    321             + "     * %n" //
    322             + "     * @param marker the marker data specific to this log statement%n" //
    323             + "     * @param message the message to log.%n" //
    324             + "     * @param t the exception to log, including its stack trace.%n" //
    325             + "     */%n" //
    326             + "    public void methodName(final Marker marker, final String message, final Throwable t) {%n" //
    327             + "        logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, t);%n" //
    328             + "    }%n" //
    329             + "%n" //
    330             + "    /**%n" //
    331             + "     * Logs the specified Message at the {@code CUSTOM_LEVEL} level.%n" //
    332             + "     * %n" //
    333             + "     * @param msg the message string to be logged%n" //
    334             + "     */%n" //
    335             + "    public void methodName(final Message msg) {%n" //
    336             + "        logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msg, (Throwable) null);%n" //
    337             + "    }%n" //
    338             + "%n" //
    339             + "    /**%n" //
    340             + "     * Logs the specified Message at the {@code CUSTOM_LEVEL} level.%n" //
    341             + "     * %n" //
    342             + "     * @param msg the message string to be logged%n" //
    343             + "     * @param t A Throwable or null.%n" //
    344             + "     */%n" //
    345             + "    public void methodName(final Message msg, final Throwable t) {%n" //
    346             + "        logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msg, t);%n" //
    347             + "    }%n" //
    348             + "%n" //
    349             + "    /**%n" //
    350             + "     * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" //
    351             + "     * %n" //
    352             + "     * @param message the message object to log.%n" //
    353             + "     */%n" //
    354             + "    public void methodName(final Object message) {%n" //
    355             + "        logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, (Throwable) null);%n" //
    356             + "    }%n" //
    357             + "%n" //
    358             + "    /**%n" //
    359             + "     * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" //
    360             + "     * the {@link Throwable} {@code t} passed as parameter.%n" //
    361             + "     * %n" //
    362             + "     * @param message the message to log.%n" //
    363             + "     * @param t the exception to log, including its stack trace.%n" //
    364             + "     */%n" //
    365             + "    public void methodName(final Object message, final Throwable t) {%n" //
    366             + "        logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, t);%n" //
    367             + "    }%n" //
    368             + "%n" //
    369             + "    /**%n" //
    370             + "     * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" //
    371             + "     * %n" //
    372             + "     * @param message the message object to log.%n" //
    373             + "     */%n" //
    374             + "    public void methodName(final String message) {%n" //
    375             + "        logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, (Throwable) null);%n" //
    376             + "    }%n" //
    377             + "%n" //
    378             + "    /**%n" //
    379             + "     * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" //
    380             + "     * %n" //
    381             + "     * @param message the message to log; the format depends on the message factory.%n" //
    382             + "     * @param params parameters to the message.%n" //
    383             + "     * @see #getMessageFactory()%n" //
    384             + "     */%n" //
    385             + "    public void methodName(final String message, final Object... params) {%n" //
    386             + "        logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, params);%n" //
    387             + "    }%n" //
    388             + "%n" //
    389             + "    /**%n" //
    390             + "     * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" //
    391             + "     * the {@link Throwable} {@code t} passed as parameter.%n" //
    392             + "     * %n" //
    393             + "     * @param message the message to log.%n" //
    394             + "     * @param t the exception to log, including its stack trace.%n" //
    395             + "     */%n" //
    396             + "    public void methodName(final String message, final Throwable t) {%n" //
    397             + "        logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, t);%n" //
    398             + "    }%n" //
    399     ;
    400
    401     private Generate() {
    402     }
    403
    404     /**
    405      * Generates source code for custom logger wrappers that only provide convenience methods for the specified custom
    406      * levels, not for the standard built-in levels.
    407      */
    408     public static final class CustomLogger {
    409         /**
    410          * Generates source code for custom logger wrappers that only provide convenience methods for the specified
    411          * custom levels, not for the standard built-in levels.
    412          *
    413          * @param args className of the custom logger to generate, followed by a NAME=intLevel pair for each custom log
    414          *            level to generate convenience methods for
    415          */
    416         public static void main(final String[] args) {
    417             generate(args, Type.CUSTOM);
    418         }
    419
    420         private CustomLogger() {
    421         }
    422     }
    423
    424     /**
    425      * Generates source code for extended logger wrappers that provide convenience methods for the specified custom
    426      * levels, and by extending {@code org.apache.logging.log4j.spi.ExtendedLoggerWrapper}, inherit the convenience
    427      * methods for the built-in levels provided by the {@code Logger} interface.
    428      */
    429     public static final class ExtendedLogger {
    430         /**
    431          * Generates source code for extended logger wrappers that provide convenience methods for the specified custom
    432          * levels.
    433          *
    434          * @param args className of the custom logger to generate, followed by a NAME=intLevel pair for each custom log
    435          *            level to generate convenience methods for
    436          */
    437         public static void main(final String[] args) {
    438             generate(args, Type.EXTEND);
    439         }
    440
    441         private ExtendedLogger() {
    442         }
    443     }
    444
    445     static class LevelInfo {
    446         final String name;
    447         final int intLevel;
    448
    449         LevelInfo(final String description) {
    450             final String[] parts = description.split("=");
    451             name = parts[0];
    452             intLevel = Integer.parseInt(parts[1]);
    453         }
    454
    455         public static List<LevelInfo> parse(final List<String> values, final Class<?> generator) {
    456             final List<LevelInfo> result = new ArrayList<Generate.LevelInfo>(values.size());
    457             for (int i = 0; i < values.size(); i++) {
    458                 try {
    459                     result.add(new LevelInfo(values.get(i)));
    460                 } catch (final Exception ex) {
    461                     System.err.println("Cannot parse custom level '" + values.get(i) + "': " + ex.toString());
    462                     usage(System.err, generator);
    463                     System.exit(-1);
    464                 }
    465             }
    466             return result;
    467         }
    468     }
    469
    470     private static void generate(final String[] args, final Type type) {
    471         generate(args, type, System.out);
    472     }
    473
    474     static void generate(final String[] args, final Type type, final PrintStream printStream) {
    475         if (!validate(args)) {
    476             usage(printStream, type.generator());
    477             System.exit(-1);
    478         }
    479         final List<String> values = new ArrayList<String>(Arrays.asList(args));
    480         final String classFQN = values.remove(0);
    481         final List<LevelInfo> levels = LevelInfo.parse(values, type.generator());
    482         printStream.println(generateSource(classFQN, levels, type));
    483     }
    484
    485     static boolean validate(final String[] args) {
    486         if (args.length < 2) {
    487             return false;
    488         }
    489         return true;
    490     }
    491
    492     private static void usage(final PrintStream out, final Class<?> generator) {
    493         out.println("Usage: java " + generator.getName() + " className LEVEL1=intLevel1 [LEVEL2=intLevel2...]");
    494         out.println("       Where className is the fully qualified class name of the custom/extended logger to generate,");
    495         out.println("       followed by a space-separated list of custom log levels.");
    496         out.println("       For each custom log level, specify NAME=intLevel (without spaces).");
    497     }
    498
    499     static String generateSource(final String classNameFQN, final List<LevelInfo> levels, final Type type) {
    500         final StringBuilder sb = new StringBuilder(10000 * levels.size());
    501         final int lastDot = classNameFQN.lastIndexOf('.');
    502         final String pkg = classNameFQN.substring(0, lastDot >= 0 ? lastDot : 0);
    503         if (!pkg.isEmpty()) {
    504             sb.append(String.format(PACKAGE_DECLARATION, pkg));
    505         }
    506         sb.append(String.format(type.imports(), ""));
    507         final String className = classNameFQN.substring(classNameFQN.lastIndexOf('.') + 1);
    508         final String javadocDescr = javadocDescription(levels);
    509         sb.append(String.format(type.declaration(), javadocDescr, className));
    510         sb.append(String.format(FQCN_FIELD, className));
    511         for (final LevelInfo level : levels) {
    512             sb.append(String.format(LEVEL_FIELD, level.name, level.name, level.intLevel));
    513         }
    514         sb.append(String.format(type.constructor(), className));
    515         sb.append(String.format(FACTORY_METHODS.replaceAll("CLASSNAME", className), ""));
    516         for (final LevelInfo level : levels) {
    517             final String methodName = camelCase(level.name);
    518             final String phase1 = METHODS.replaceAll("CUSTOM_LEVEL", level.name);
    519             final String phase2 = phase1.replaceAll("methodName", methodName);
    520             sb.append(String.format(phase2, ""));
    521         }
    522
    523         sb.append(String.format("}%n", ""));
    524         return sb.toString();
    525     }
    526
    527     static String javadocDescription(final List<LevelInfo> levels) {
    528         if (levels.size() == 1) {
    529             return "the " + levels.get(0).name + " custom log level.";
    530         }
    531         final StringBuilder sb = new StringBuilder(512);
    532         sb.append("the ");
    533         String sep = "";
    534         for (int i = 0; i < levels.size(); i++) {
    535             sb.append(sep);
    536             sb.append(levels.get(i).name);
    537             sep = (i == levels.size() - 2) ? " and " : ", ";
    538         }
    539         sb.append(" custom log levels.");
    540         return sb.toString();
    541     }
    542
    543     static String camelCase(final String customLevel) {
    544         final StringBuilder sb = new StringBuilder(customLevel.length());
    545         boolean lower = true;
    546         for (final char ch : customLevel.toCharArray()) {
    547             if (ch == '_') {
    548                 lower = false;
    549                 continue;
    550             }
    551             sb.append(lower ? Character.toLowerCase(ch) : Character.toUpperCase(ch));
    552             lower = true;
    553         }
    554         return sb.toString();
    555     }
    556 }