    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements. See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache license, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License. You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the license for the specific language governing permissions and
    015     * limitations under the license.
    016     */
    017
    018    package org.apache.logging.log4j.core.tools;
    019
    020    import java.io.PrintStream;
    021    import java.util.ArrayList;
    022    import java.util.Arrays;
    023    import java.util.List;
    024
    025    /**
    026     * Generates source code for custom or extended logger wrappers.
    027     * <p>
    028     * Usage:
    029     * <p>
    030     * To generate source code for an extended logger that adds custom log levels to the existing ones: <br>
    031     * {@code java org.apache.logging.log4j.core.tools.Generate$ExtendedLogger <logger.class.name> <CUSTOMLEVEL>=<WEIGHT>
    032     * [CUSTOMLEVEL2=WEIGHT2 [CUSTOMLEVEL3=WEIGHT3] ...]}
    033     * <p>
    034     * Example of creating an extended logger:<br>
    035     * {@code java org.apache.logging.log4j.core.tools.Generate$ExtendedLogger com.mycomp.ExtLogger DIAG=350 NOTICE=450 VERBOSE=550}
    036     * <p>
    037     * To generate source code for a custom logger that replaces the existing log levels with custom ones: <br>
    038     * {@code java org.apache.logging.log4j.core.tools.Generate$CustomLogger <logger.class.name> <CUSTOMLEVEL>=<WEIGHT>
    039     * [CUSTOMLEVEL2=WEIGHT2 [CUSTOMLEVEL3=WEIGHT3] ...]}
    040     * <p>
    041     * Example of creating a custom logger:<br>
    042     * {@code java org.apache.logging.log4j.core.tools.Generate$CustomLogger com.mycomp.MyLogger DEFCON1=350 DEFCON2=450 DEFCON3=550}
    043     */
    044    public final class Generate {
    045
    046        static final String PACKAGE_DECLARATION = "package %s;%n%n";
    047
    048        static enum Type {
    049            CUSTOM {
    050                @Override
    051                String imports() {
    052                    return "" //
    053                            + "import java.io.Serializable;%n" //
    054                            + "import org.apache.logging.log4j.Level;%n" //
    055                            + "import org.apache.logging.log4j.LogManager;%n" + "import org.apache.logging.log4j.Logger;%n" //
    056                            + "import org.apache.logging.log4j.Marker;%n" //
    057                            + "import org.apache.logging.log4j.message.Message;%n" //
    058                            + "import org.apache.logging.log4j.message.MessageFactory;%n" //
    059                            + "import org.apache.logging.log4j.spi.AbstractLogger;%n" //
    060                            + "import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;%n" //
    061                            + "%n";
    062                }
    063
    064                @Override
    065                String declaration() {
    066                    return "" //
    067                            + "/**%n" //
    068                            + " * Custom Logger interface with convenience methods for%n" //
    069                            + " * %s%n" //
    070                            + " */%n" //
    071                            + "public final class %s implements Serializable {%n" //
    072                            + "    private static final long serialVersionUID = " + System.nanoTime() + "L;%n" //
    073                            + "    private final ExtendedLoggerWrapper logger;%n" //
    074                            + "%n" //
    075                    ;
    076                }
    077
    078                @Override
    079                String constructor() {
    080                    return "" //
    081                            + "%n" //
    082                            + "    private %s(final Logger logger) {%n" //
    083                            + "        this.logger = new ExtendedLoggerWrapper((AbstractLogger) logger, logger.getName(), logger.getMessageFactory());%n" //
    084                            + "    }%n" //
    085                    ;
    086                }
    087
    088                @Override
    089                Class<?> generator() {
    090                    return CustomLogger.class;
    091                }
    092            },
    093            EXTEND {
    094                @Override
    095                String imports() {
    096                    return "" //
    097                            + "import org.apache.logging.log4j.Level;%n" //
    098                            + "import org.apache.logging.log4j.LogManager;%n" + "import org.apache.logging.log4j.Logger;%n" //
    099                            + "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    }