/*
 * Copyright 2013 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package io.netty.util;

import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;

public final class ResourceLeakDetector<T> {

    private static final boolean DISABLED = SystemPropertyUtil.getBoolean("io.netty.noResourceLeakDetection", false);

    private static volatile boolean disabled = DISABLED;

    public static final boolean ENABLED = !DISABLED;

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(ResourceLeakDetector.class);

    static {
        logger.debug("-Dio.netty.noResourceLeakDetection: {}", DISABLED);
    }

    private static final int DEFAULT_SAMPLING_INTERVAL = 113;

    private static final ResourceLeak NOOP = new ResourceLeak() {
        @Override
        public boolean close() {
            return false;
        }
    };

    /**
     * Allow to disable resource leak detection
     */
    public static void setDisabled(boolean disabled) {
        ResourceLeakDetector.disabled = disabled;
    }

    /**
     * Returns {@code true} if resource leak detection is turned off.
     */
    public static boolean isDisabled() {
        return disabled;
    }

    /** the linked list of active resources */
    private final DefaultResourceLeak head = new DefaultResourceLeak(null);
    private final DefaultResourceLeak tail = new DefaultResourceLeak(null);

    private final ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
    private final ConcurrentMap<Exception, Boolean> reportedLeaks = PlatformDependent.newConcurrentHashMap();

    private final String resourceType;
    private final int samplingInterval;
    private final long maxActive;
    private long active;
    private final AtomicBoolean loggedTooManyActive = new AtomicBoolean();

    private long leakCheckCnt;

    public ResourceLeakDetector(Class<?> resourceType) {
        this(resourceType.getSimpleName());
    }

    public ResourceLeakDetector(String resourceType) {
        this(resourceType, DEFAULT_SAMPLING_INTERVAL, Long.MAX_VALUE);
    }

    public ResourceLeakDetector(Class<?> resourceType, int samplingInterval, long maxActive) {
        this(resourceType.getSimpleName(), samplingInterval, maxActive);
    }

    public ResourceLeakDetector(String resourceType, int samplingInterval, long maxActive) {
        if (resourceType == null) {
            throw new NullPointerException("resourceType");
        }
        if (samplingInterval <= 0) {
            throw new IllegalArgumentException("samplingInterval: " + samplingInterval + " (expected: 1+)");
        }
        if (maxActive <= 0) {
            throw new IllegalArgumentException("maxActive: " + maxActive + " (expected: 1+)");
        }

        this.resourceType = resourceType;
        this.samplingInterval = samplingInterval;
        this.maxActive = maxActive;

        head.next = tail;
        tail.prev = head;
    }

    public ResourceLeak open(T obj) {
        if (disabled || leakCheckCnt ++ % samplingInterval != 0) {
            return NOOP;
        }

        reportLeak();

        return new DefaultResourceLeak(obj);
    }

    private void reportLeak() {
        if (!logger.isWarnEnabled()) {
            for (;;) {
                @SuppressWarnings("unchecked")
                DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
                if (ref == null) {
                    break;
                }
                ref.close();
            }
            return;
        }

        // Report too many instances.
        if (active * samplingInterval > maxActive && loggedTooManyActive.compareAndSet(false, true)) {
            logger.warn(
                    "LEAK: You are creating too many " + resourceType + " instances.  " +
                    resourceType + " is a shared resource that must be reused across the JVM," +
                    "so that only a few instances are created.");
        }

        // Detect and report previous leaks.
        for (;;) {
            @SuppressWarnings("unchecked")
            DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
            if (ref == null) {
                break;
            }

            ref.clear();

            if (!ref.close()) {
                continue;
            }

            if (reportedLeaks.putIfAbsent(ref.exception, Boolean.TRUE) == null) {
                logger.warn(
                        "LEAK: " + resourceType + " was GC'd before being released correctly.  " +
                        "The following stack trace shows where the leaked object was created, " +
                        "rather than where you failed to release it.", ref.exception);
            }
        }
    }

    private final class DefaultResourceLeak extends PhantomReference<Object> implements ResourceLeak {

        private final ResourceLeakException exception;
        private final AtomicBoolean freed;
        private DefaultResourceLeak prev;
        private DefaultResourceLeak next;

        public DefaultResourceLeak(Object referent) {
            super(referent, referent != null? refQueue : null);

            if (referent != null) {
                exception = new ResourceLeakException(
                        referent.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(referent)));

                // TODO: Use CAS to update the list.
                synchronized (head) {
                    prev = head;
                    next = head.next;
                    head.next.prev = this;
                    head.next = this;
                    active ++;
                }
                freed = new AtomicBoolean();
            } else {
                exception = null;
                freed = new AtomicBoolean(true);
            }
        }

        @Override
        public boolean close() {
            if (freed.compareAndSet(false, true)) {
                synchronized (head) {
                    active --;
                    prev.next = next;
                    next.prev = prev;
                    prev = null;
                    next = null;
                }
                return true;
            }
            return false;
        }
    }
}

@@ -1107,217 +1107,33 @@
 tic 
-final boolean DISABLED = SystemPropertyUtil.getBoolean(%22io.netty.noResourceLeakDetection%22, false);%0A%0A    private static volatile boolean disabled = DISABLED;%0A%0A    public static final boolean ENABLED = !DISABLED
+volatile boolean disabled
 ;%0A%0A 
@@ -1257,16 +1257,123 @@
 tatic %7B%0A
+        final boolean DISABLED = SystemPropertyUtil.getBoolean(%22io.netty.noResourceLeakDetection%22, false);%0A
         
@@ -1438,16 +1438,45 @@
 ABLED);%0A
+        disabled = DISABLED;%0A
     %7D%0A%0A 
@@ -1717,24 +1717,26 @@
   * 
-Allow to
+Enables or
  disable
  res
@@ -1731,16 +1731,21 @@
  disable
+d the
  resourc
@@ -1760,16 +1760,17 @@
 etection
+.
 %0A     */
@@ -1796,19 +1796,18 @@
 void set
-Dis
+En
 abled(bo
@@ -1808,27 +1808,26 @@
 led(boolean 
-dis
+en
 abled) %7B%0A   
@@ -1831,37 +1831,16 @@
         
-ResourceLeakDetector.
 disabled
@@ -1842,19 +1842,19 @@
 abled = 
-dis
+!en
 abled;%0A 
@@ -1929,18 +1929,15 @@
  is 
-turned off
+enabled
 .%0A  
@@ -1934,32 +1934,33 @@
 nabled.%0A     */%0A
+%0A
     public stati
@@ -1975,11 +1975,10 @@
 n is
-Dis
+En
 able
@@ -1998,16 +1998,17 @@
  return 
+!
 disabled

Remove ResourceLeakDetector.ENABLED / Replace ResourceLeakDetector.is/setDisabled() with is/setEnabled()