// 
// Decompiled by Procyon v0.6.0
// 

package com.google.common.flogger;

import java.util.Set;
import java.util.Map;
import java.util.Iterator;
import java.util.Arrays;
import com.google.common.flogger.context.Tags;
import com.google.common.flogger.util.CallerFinder;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
import com.google.common.flogger.backend.Metadata;
import java.util.concurrent.TimeUnit;
import com.google.common.flogger.parser.MessageParser;
import com.google.common.flogger.util.Checks;
import com.google.common.flogger.backend.Platform;
import com.google.common.flogger.backend.TemplateContext;
import java.util.logging.Level;
import com.google.errorprone.annotations.CheckReturnValue;
import com.google.common.flogger.backend.LogData;

@CheckReturnValue
public abstract class LogContext<LOGGER extends AbstractLogger<API>, API extends LoggingApi<API>> implements LoggingApi<API>, LogData
{
    private static final String LITERAL_VALUE_MESSAGE;
    private final Level level;
    private final long timestampNanos;
    private MutableMetadata metadata;
    private LogSite logSite;
    private TemplateContext templateContext;
    private Object[] args;
    
    protected LogContext(final Level level, final boolean isForced) {
        this(level, isForced, Platform.getCurrentTimeNanos());
    }
    
    protected LogContext(final Level level, final boolean isForced, final long timestampNanos) {
        this.metadata = null;
        this.logSite = null;
        this.templateContext = null;
        this.args = null;
        this.level = Checks.checkNotNull(level, "level");
        this.timestampNanos = timestampNanos;
        if (isForced) {
            this.addMetadata(Key.WAS_FORCED, Boolean.TRUE);
        }
    }
    
    protected abstract API api();
    
    protected abstract LOGGER getLogger();
    
    protected abstract API noOp();
    
    protected abstract MessageParser getMessageParser();
    
    @Override
    public final Level getLevel() {
        return this.level;
    }
    
    @Deprecated
    @Override
    public final long getTimestampMicros() {
        return TimeUnit.NANOSECONDS.toMicros(this.timestampNanos);
    }
    
    @Override
    public final long getTimestampNanos() {
        return this.timestampNanos;
    }
    
    @Override
    public final String getLoggerName() {
        return this.getLogger().getBackend().getLoggerName();
    }
    
    @Override
    public final LogSite getLogSite() {
        if (this.logSite == null) {
            throw new IllegalStateException("cannot request log site information prior to postProcess()");
        }
        return this.logSite;
    }
    
    @Override
    public final TemplateContext getTemplateContext() {
        return this.templateContext;
    }
    
    @Override
    public final Object[] getArguments() {
        if (this.templateContext == null) {
            throw new IllegalStateException("cannot get arguments unless a template context exists");
        }
        return this.args;
    }
    
    @Override
    public final Object getLiteralArgument() {
        if (this.templateContext != null) {
            throw new IllegalStateException("cannot get literal argument if a template context exists");
        }
        return this.args[0];
    }
    
    @Override
    public final boolean wasForced() {
        return this.metadata != null && Boolean.TRUE.equals(this.metadata.findValue(Key.WAS_FORCED));
    }
    
    @Override
    public final Metadata getMetadata() {
        return (this.metadata != null) ? this.metadata : Metadata.empty();
    }
    
    protected final <T> void addMetadata(final MetadataKey<T> key, final T value) {
        if (this.metadata == null) {
            this.metadata = new MutableMetadata();
        }
        this.metadata.addValue(key, value);
    }
    
    protected final void removeMetadata(final MetadataKey<?> key) {
        if (this.metadata != null) {
            this.metadata.removeAllValues(key);
        }
    }
    
    protected boolean postProcess(@NullableDecl final LogSiteKey logSiteKey) {
        if (this.metadata != null) {
            if (logSiteKey != null) {
                final Integer rateLimitCount = this.metadata.findValue(Key.LOG_EVERY_N);
                final LogSiteStats.RateLimitPeriod rateLimitPeriod = this.metadata.findValue(Key.LOG_AT_MOST_EVERY);
                final LogSiteStats stats = LogSiteStats.getStatsForKey(logSiteKey, this.metadata);
                if (rateLimitCount != null && !stats.incrementAndCheckInvocationCount(rateLimitCount)) {
                    return false;
                }
                if (rateLimitPeriod != null && !stats.checkLastTimestamp(this.getTimestampNanos(), rateLimitPeriod)) {
                    return false;
                }
            }
            final StackSize stackSize = this.metadata.findValue(Key.CONTEXT_STACK_SIZE);
            if (stackSize != null) {
                this.removeMetadata(Key.CONTEXT_STACK_SIZE);
                final LogSiteStackTrace context = new LogSiteStackTrace(this.getMetadata().findValue(Key.LOG_CAUSE), stackSize, CallerFinder.getStackForCallerOf(LogContext.class, new Throwable(), stackSize.getMaxDepth(), 1));
                this.addMetadata(Key.LOG_CAUSE, context);
            }
        }
        return true;
    }
    
    private boolean shouldLog() {
        if (this.logSite == null) {
            this.logSite = Checks.checkNotNull(Platform.getCallerFinder().findLogSite(LogContext.class, 1), "logger backend must not return a null LogSite");
        }
        LogSiteKey logSiteKey = null;
        if (this.logSite != LogSite.INVALID) {
            logSiteKey = this.logSite;
        }
        if (!this.postProcess(logSiteKey)) {
            return false;
        }
        final Tags tags = Platform.getInjectedTags();
        if (!tags.isEmpty()) {
            this.addMetadata(Key.TAGS, tags);
        }
        return true;
    }
    
    static LogSiteKey specializeLogSiteKeyFromMetadata(LogSiteKey logSiteKey, final Metadata metadata) {
        Checks.checkNotNull(logSiteKey, "logSiteKey");
        for (int n = 0, size = metadata.size(); n < size; ++n) {
            if (Key.LOG_SITE_GROUPING_KEY.equals(metadata.getKey(n))) {
                final Object groupByQualifier = metadata.getValue(n);
                if (groupByQualifier instanceof LoggingScope) {
                    logSiteKey = ((LoggingScope)groupByQualifier).specialize(logSiteKey);
                }
                else {
                    logSiteKey = SpecializedLogSiteKey.of(logSiteKey, groupByQualifier);
                }
            }
        }
        return logSiteKey;
    }
    
    private void logImpl(final String message, final Object... args) {
        this.args = args;
        for (int n = 0; n < args.length; ++n) {
            if (args[n] instanceof LazyArg) {
                args[n] = ((LazyArg)args[n]).evaluate();
            }
        }
        if (message != LogContext.LITERAL_VALUE_MESSAGE) {
            this.templateContext = new TemplateContext(this.getMessageParser(), message);
        }
        this.getLogger().write(this);
    }
    
    @Override
    public final API withInjectedLogSite(final LogSite logSite) {
        if (this.logSite == null && logSite != null) {
            this.logSite = logSite;
        }
        return this.api();
    }
    
    @Override
    public final API withInjectedLogSite(final String internalClassName, final String methodName, final int encodedLineNumber, @NullableDecl final String sourceFileName) {
        return this.withInjectedLogSite(LogSite.injectedLogSite(internalClassName, methodName, encodedLineNumber, sourceFileName));
    }
    
    @Override
    public final boolean isEnabled() {
        return this.wasForced() || this.getLogger().isLoggable(this.level);
    }
    
    @Override
    public final <T> API with(final MetadataKey<T> key, @NullableDecl final T value) {
        Checks.checkNotNull(key, "metadata key");
        if (value != null) {
            this.addMetadata(key, value);
        }
        return this.api();
    }
    
    @Override
    public final <T> API with(final MetadataKey<Boolean> key) {
        return this.with(key, Boolean.TRUE);
    }
    
    @Override
    public final API withCause(final Throwable cause) {
        if (cause != null) {
            this.addMetadata(Key.LOG_CAUSE, cause);
        }
        return this.api();
    }
    
    @Override
    public API withStackTrace(final StackSize size) {
        if (Checks.checkNotNull(size, "stack size") != StackSize.NONE) {
            this.addMetadata(Key.CONTEXT_STACK_SIZE, size);
        }
        return this.api();
    }
    
    @Override
    public final API every(final int n) {
        if (this.wasForced()) {
            return this.api();
        }
        if (n <= 0) {
            throw new IllegalArgumentException("rate limit count must be positive");
        }
        if (n > 1) {
            this.addMetadata(Key.LOG_EVERY_N, n);
        }
        return this.api();
    }
    
    @Override
    public final API atMostEvery(final int n, final TimeUnit unit) {
        if (this.wasForced()) {
            return this.api();
        }
        if (n < 0) {
            throw new IllegalArgumentException("rate limit period cannot be negative");
        }
        if (n > 0) {
            this.addMetadata(Key.LOG_AT_MOST_EVERY, LogSiteStats.newRateLimitPeriod(n, unit));
        }
        return this.api();
    }
    
    @Override
    public final void log() {
        if (this.shouldLog()) {
            this.logImpl(LogContext.LITERAL_VALUE_MESSAGE, "");
        }
    }
    
    @Override
    public final void log(final String msg) {
        if (this.shouldLog()) {
            this.logImpl(LogContext.LITERAL_VALUE_MESSAGE, msg);
        }
    }
    
    @Override
    public final void log(final String message, @NullableDecl final Object p1) {
        if (this.shouldLog()) {
            this.logImpl(message, p1);
        }
    }
    
    @Override
    public final void log(final String message, @NullableDecl final Object p1, @NullableDecl final Object p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, @NullableDecl final Object p1, @NullableDecl final Object p2, @NullableDecl final Object p3) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2, p3);
        }
    }
    
    @Override
    public final void log(final String message, @NullableDecl final Object p1, @NullableDecl final Object p2, @NullableDecl final Object p3, @NullableDecl final Object p4) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2, p3, p4);
        }
    }
    
    @Override
    public final void log(final String msg, @NullableDecl final Object p1, @NullableDecl final Object p2, @NullableDecl final Object p3, @NullableDecl final Object p4, @NullableDecl final Object p5) {
        if (this.shouldLog()) {
            this.logImpl(msg, p1, p2, p3, p4, p5);
        }
    }
    
    @Override
    public final void log(final String msg, @NullableDecl final Object p1, @NullableDecl final Object p2, @NullableDecl final Object p3, @NullableDecl final Object p4, @NullableDecl final Object p5, @NullableDecl final Object p6) {
        if (this.shouldLog()) {
            this.logImpl(msg, p1, p2, p3, p4, p5, p6);
        }
    }
    
    @Override
    public final void log(final String msg, @NullableDecl final Object p1, @NullableDecl final Object p2, @NullableDecl final Object p3, @NullableDecl final Object p4, @NullableDecl final Object p5, @NullableDecl final Object p6, @NullableDecl final Object p7) {
        if (this.shouldLog()) {
            this.logImpl(msg, p1, p2, p3, p4, p5, p6, p7);
        }
    }
    
    @Override
    public final void log(final String msg, @NullableDecl final Object p1, @NullableDecl final Object p2, @NullableDecl final Object p3, @NullableDecl final Object p4, @NullableDecl final Object p5, @NullableDecl final Object p6, @NullableDecl final Object p7, @NullableDecl final Object p8) {
        if (this.shouldLog()) {
            this.logImpl(msg, p1, p2, p3, p4, p5, p6, p7, p8);
        }
    }
    
    @Override
    public final void log(final String msg, @NullableDecl final Object p1, @NullableDecl final Object p2, @NullableDecl final Object p3, @NullableDecl final Object p4, @NullableDecl final Object p5, @NullableDecl final Object p6, @NullableDecl final Object p7, @NullableDecl final Object p8, @NullableDecl final Object p9) {
        if (this.shouldLog()) {
            this.logImpl(msg, p1, p2, p3, p4, p5, p6, p7, p8, p9);
        }
    }
    
    @Override
    public final void log(final String msg, @NullableDecl final Object p1, @NullableDecl final Object p2, @NullableDecl final Object p3, @NullableDecl final Object p4, @NullableDecl final Object p5, @NullableDecl final Object p6, @NullableDecl final Object p7, @NullableDecl final Object p8, @NullableDecl final Object p9, @NullableDecl final Object p10) {
        if (this.shouldLog()) {
            this.logImpl(msg, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10);
        }
    }
    
    @Override
    public final void log(final String msg, @NullableDecl final Object p1, @NullableDecl final Object p2, @NullableDecl final Object p3, @NullableDecl final Object p4, @NullableDecl final Object p5, @NullableDecl final Object p6, @NullableDecl final Object p7, @NullableDecl final Object p8, @NullableDecl final Object p9, @NullableDecl final Object p10, final Object... rest) {
        if (this.shouldLog()) {
            final Object[] params = new Object[rest.length + 10];
            params[0] = p1;
            params[1] = p2;
            params[2] = p3;
            params[3] = p4;
            params[4] = p5;
            params[5] = p6;
            params[6] = p7;
            params[7] = p8;
            params[8] = p9;
            params[9] = p10;
            System.arraycopy(rest, 0, params, 10, rest.length);
            this.logImpl(msg, params);
        }
    }
    
    @Override
    public final void log(final String message, final char p1) {
        if (this.shouldLog()) {
            this.logImpl(message, p1);
        }
    }
    
    @Override
    public final void log(final String message, final byte p1) {
        if (this.shouldLog()) {
            this.logImpl(message, p1);
        }
    }
    
    @Override
    public final void log(final String message, final short p1) {
        if (this.shouldLog()) {
            this.logImpl(message, p1);
        }
    }
    
    @Override
    public final void log(final String message, final int p1) {
        if (this.shouldLog()) {
            this.logImpl(message, p1);
        }
    }
    
    @Override
    public final void log(final String message, final long p1) {
        if (this.shouldLog()) {
            this.logImpl(message, p1);
        }
    }
    
    @Override
    public final void log(final String message, @NullableDecl final Object p1, final boolean p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, @NullableDecl final Object p1, final char p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, @NullableDecl final Object p1, final byte p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, @NullableDecl final Object p1, final short p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, @NullableDecl final Object p1, final int p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, @NullableDecl final Object p1, final long p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, @NullableDecl final Object p1, final float p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, @NullableDecl final Object p1, final double p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final boolean p1, @NullableDecl final Object p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final char p1, @NullableDecl final Object p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final byte p1, @NullableDecl final Object p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final short p1, @NullableDecl final Object p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final int p1, @NullableDecl final Object p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final long p1, @NullableDecl final Object p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final float p1, @NullableDecl final Object p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final double p1, @NullableDecl final Object p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final boolean p1, final boolean p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final char p1, final boolean p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final byte p1, final boolean p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final short p1, final boolean p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final int p1, final boolean p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final long p1, final boolean p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final float p1, final boolean p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final double p1, final boolean p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final boolean p1, final char p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final char p1, final char p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final byte p1, final char p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final short p1, final char p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final int p1, final char p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final long p1, final char p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final float p1, final char p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final double p1, final char p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final boolean p1, final byte p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final char p1, final byte p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final byte p1, final byte p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final short p1, final byte p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final int p1, final byte p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final long p1, final byte p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final float p1, final byte p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final double p1, final byte p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final boolean p1, final short p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final char p1, final short p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final byte p1, final short p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final short p1, final short p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final int p1, final short p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final long p1, final short p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final float p1, final short p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final double p1, final short p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final boolean p1, final int p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final char p1, final int p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final byte p1, final int p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final short p1, final int p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final int p1, final int p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final long p1, final int p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final float p1, final int p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final double p1, final int p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final boolean p1, final long p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final char p1, final long p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final byte p1, final long p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final short p1, final long p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final int p1, final long p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final long p1, final long p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final float p1, final long p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final double p1, final long p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final boolean p1, final float p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final char p1, final float p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final byte p1, final float p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final short p1, final float p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final int p1, final float p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final long p1, final float p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final float p1, final float p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final double p1, final float p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final boolean p1, final double p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final char p1, final double p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final byte p1, final double p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final short p1, final double p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final int p1, final double p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final long p1, final double p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final float p1, final double p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void log(final String message, final double p1, final double p2) {
        if (this.shouldLog()) {
            this.logImpl(message, p1, p2);
        }
    }
    
    @Override
    public final void logVarargs(final String message, @NullableDecl final Object[] params) {
        if (this.shouldLog()) {
            this.logImpl(message, Arrays.copyOf(params, params.length));
        }
    }
    
    static {
        LITERAL_VALUE_MESSAGE = new String();
    }
    
    public static final class Key
    {
        public static final MetadataKey<Throwable> LOG_CAUSE;
        public static final MetadataKey<Integer> LOG_EVERY_N;
        public static final MetadataKey<LogSiteStats.RateLimitPeriod> LOG_AT_MOST_EVERY;
        public static final MetadataKey<Object> LOG_SITE_GROUPING_KEY;
        public static final MetadataKey<Boolean> WAS_FORCED;
        public static final MetadataKey<Tags> TAGS;
        public static final MetadataKey<StackSize> CONTEXT_STACK_SIZE;
        
        private Key() {
        }
        
        static {
            LOG_CAUSE = MetadataKey.single("cause", (Class<? extends Throwable>)Throwable.class);
            LOG_EVERY_N = MetadataKey.single("ratelimit_count", (Class<? extends Integer>)Integer.class);
            LOG_AT_MOST_EVERY = MetadataKey.single("ratelimit_period", (Class<? extends LogSiteStats.RateLimitPeriod>)LogSiteStats.RateLimitPeriod.class);
            LOG_SITE_GROUPING_KEY = new MetadataKey<Object>((Class)Object.class, true) {
                @Override
                public void emitRepeated(final Iterator<Object> keys, final KeyValueHandler out) {
                    if (keys.hasNext()) {
                        final Object first = keys.next();
                        if (!keys.hasNext()) {
                            out.handle(this.getLabel(), first);
                        }
                        else {
                            final StringBuilder buf = new StringBuilder();
                            buf.append('[').append(first);
                            do {
                                buf.append(',').append(keys.next());
                            } while (keys.hasNext());
                            out.handle(this.getLabel(), buf.append(']').toString());
                        }
                    }
                }
            };
            WAS_FORCED = MetadataKey.single("forced", (Class<? extends Boolean>)Boolean.class);
            TAGS = new MetadataKey<Tags>((Class)Tags.class, false) {
                @Override
                public void emit(final Tags tags, final KeyValueHandler out) {
                    for (final Map.Entry<String, ? extends Set<Object>> e : tags.asMap().entrySet()) {
                        final Set<Object> values = (Set<Object>)e.getValue();
                        if (!values.isEmpty()) {
                            for (final Object v : (Set)e.getValue()) {
                                out.handle(e.getKey(), v);
                            }
                        }
                        else {
                            out.handle(e.getKey(), null);
                        }
                    }
                }
            };
            CONTEXT_STACK_SIZE = MetadataKey.single("stack_size", (Class<? extends StackSize>)StackSize.class);
        }
    }
    
    static final class MutableMetadata extends Metadata
    {
        private static final int INITIAL_KEY_VALUE_CAPACITY = 4;
        private Object[] keyValuePairs;
        private int keyValueCount;
        
        MutableMetadata() {
            this.keyValuePairs = new Object[8];
            this.keyValueCount = 0;
        }
        
        @Override
        public int size() {
            return this.keyValueCount;
        }
        
        @Override
        public MetadataKey<?> getKey(final int n) {
            if (n >= this.keyValueCount) {
                throw new IndexOutOfBoundsException();
            }
            return (MetadataKey)this.keyValuePairs[2 * n];
        }
        
        @Override
        public Object getValue(final int n) {
            if (n >= this.keyValueCount) {
                throw new IndexOutOfBoundsException();
            }
            return this.keyValuePairs[2 * n + 1];
        }
        
        private int indexOf(final MetadataKey<?> key) {
            for (int index = 0; index < this.keyValueCount; ++index) {
                if (this.keyValuePairs[2 * index].equals(key)) {
                    return index;
                }
            }
            return -1;
        }
        
        @NullableDecl
        @Override
        public <T> T findValue(final MetadataKey<T> key) {
            final int index = this.indexOf(key);
            return (index != -1) ? key.cast(this.keyValuePairs[2 * index + 1]) : null;
        }
        
         <T> void addValue(final MetadataKey<T> key, final T value) {
            if (!key.canRepeat()) {
                final int index = this.indexOf(key);
                if (index != -1) {
                    this.keyValuePairs[2 * index + 1] = Checks.checkNotNull(value, "metadata value");
                    return;
                }
            }
            if (2 * (this.keyValueCount + 1) > this.keyValuePairs.length) {
                this.keyValuePairs = Arrays.copyOf(this.keyValuePairs, 2 * this.keyValuePairs.length);
            }
            this.keyValuePairs[2 * this.keyValueCount] = Checks.checkNotNull(key, "metadata key");
            this.keyValuePairs[2 * this.keyValueCount + 1] = Checks.checkNotNull(value, "metadata value");
            ++this.keyValueCount;
        }
        
        void removeAllValues(final MetadataKey<?> key) {
            final int index = this.indexOf(key);
            if (index >= 0) {
                int dest = 2 * index;
                int src;
                for (src = dest + 2; src < 2 * this.keyValueCount; src += 2) {
                    final Object nextKey = this.keyValuePairs[src];
                    if (!nextKey.equals(key)) {
                        this.keyValuePairs[dest] = nextKey;
                        this.keyValuePairs[dest + 1] = this.keyValuePairs[src + 1];
                        dest += 2;
                    }
                }
                this.keyValueCount -= src - dest >> 1;
                while (dest < src) {
                    this.keyValuePairs[dest++] = null;
                }
            }
        }
        
        @Override
        public String toString() {
            final StringBuilder out = new StringBuilder("Metadata{");
            for (int n = 0; n < this.size(); ++n) {
                out.append(" '").append(this.getKey(n)).append("': ").append(this.getValue(n));
            }
            return out.append(" }").toString();
        }
    }
}
