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

import static java.lang.String.format;
import static org.springframework.config.java.internal.enhancement.ThreadLocalUtils.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;

/**
 *
 * TODO: JAVADOC
 *
 * @author Chris Beams
 */
class ThreadLocalReader {

    private static final Log log = LogFactory.getLog(ThreadLocalReader.class);

    /**
     * Finds any {@link ThreadLocal} instances containing objects of type <var>targetType</var>
     * and eliminates them.  This works across all threads in the VM.  It does not work
     * against {@link InheritableThreadLocal} instances.
     *
     * @param targetType type of ThreadLocal to destroy
     */
    public void accept(ThreadLocalVisitor visitor) {
        Assert.notNull(visitor);
        accept(visitor, rootThreadGroup());
    }

    public void accept(ThreadLocalVisitor visitor, ThreadGroup group) {
        Assert.notNull(visitor);
        Assert.notNull(group);

        log.debug(format("accepting visitor [%s] for thread group %s", visitor , group));

        int numThreads = group.activeCount();
        int numGroups = group.activeGroupCount();
        Thread[] threads = new Thread[numThreads];
        ThreadGroup[] groups = new ThreadGroup[numGroups];

        group.enumerate(threads, false);
        group.enumerate(groups, false);

        for(ThreadGroup childGroup : groups)
            accept(visitor, childGroup);

        for(Thread childThread : threads)
            if(childThread != null)
                accept(visitor, childThread);
    }

    public void accept(ThreadLocalVisitor visitor, Thread thread) {
        Assert.notNull(visitor);
        Assert.notNull(thread);

        log.debug(format("accepting visitor [%s] for thread %s", visitor, thread));

        try {
            Object threadLocalMap = FIELD_THREAD_LOCALS.get(thread);

            if(threadLocalMap == null)
                return;

            Object[] table = (Object[]) FIELD_TABLE.get(threadLocalMap);

            for(Object mapEntry : table)
                if(mapEntry != null)
                    visitor.visit(new ThreadLocalMapEntryData(thread, threadLocalMap, mapEntry));
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * Traverses up from the current thread and returns the root (system) ThreadGroup
     */
    private ThreadGroup rootThreadGroup() {
        ThreadGroup currentThreadGroup;
        ThreadGroup rootThreadGroup;
        ThreadGroup parent;

        // Get the current thread group
        currentThreadGroup = Thread.currentThread().getThreadGroup();

        // Now go find the root thread group
        rootThreadGroup = currentThreadGroup;
        parent = rootThreadGroup.getParent();

        while (parent != null) {
            rootThreadGroup = parent;
            parent = parent.getParent();
        }

        return rootThreadGroup;
    }

}
