/*
 * Copyright (c) 2004, 2009, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

package sun.jvm.hotspot.utilities.soql;

import java.util.*;
import sun.jvm.hotspot.debugger.*;
import sun.jvm.hotspot.oops.*;
import sun.jvm.hotspot.runtime.*;

/**
 * Wraps a JavaThread instance in the target VM.
 */
public class JSJavaThread extends JSJavaInstance {
    public JSJavaThread(Instance threadOop, JSJavaFactory fac) {
        super(threadOop, fac);
        // JavaThread retrieved from java.lang.Thread instance may be null.
        // This is the case for threads not-started and for zombies. Wherever
        // appropriate, check for null instead of resulting in NullPointerException.
        this.jthread = OopUtilities.threadOopGetJavaThread(threadOop);
    }

    public JSJavaThread(JavaThread jt, JSJavaFactory fac) {
        super((Instance) jt.getThreadObj(), fac);
        this.jthread = jt;
    }

    public String toString() {
        String name = getName();
        StringBuffer buf = new StringBuffer();
        buf.append("Thread (address=");
        buf.append(getOop().getHandle());
        buf.append(", name=");
        if (name != null) {
            buf.append(name);
        } else {
            buf.append("<unnamed>");
        }
        buf.append(')');
        return buf.toString();
    }

    protected Object getFieldValue(String name) {
        if (name.equals("name")) {
            return getName();
        } else if (name.equals("frames")) {
            return getFrames();
        } else if (name.equals("monitors")) {
            return getOwnedMonitors();
        } else {
            return super.getFieldValue(name);
        }
    }

    protected String[] getFieldNames() {
        String[] flds = super.getFieldNames();
        String[] res = new String[flds.length + 2];
        System.arraycopy(flds, 0, res, 0, flds.length);
        res[flds.length] = "frames";
        res[flds.length + 1] = "monitors";
        return res;
    }

    protected boolean hasField(String name) {
        if (name.equals("frames") || name.equals("monitors")) {
            return true;
        } else {
            return super.hasField(name);
        }
    }

    //-- Internals only below this point
    private String getName() {
        return OopUtilities.threadOopGetName(getOop());
    }

    private synchronized JSList getFrames() {
        if (framesCache == null) {
            final List list = new ArrayList(0);
            if (jthread != null) {
                JavaVFrame jvf = jthread.getLastJavaVFrameDbg();
                while (jvf != null) {
                    list.add(jvf);
                    jvf = jvf.javaSender();
                }
            }
            framesCache = factory.newJSList(list);
        }
        return framesCache;
    }

    private synchronized JSList getOwnedMonitors() {
        if (monitorsCache == null) {
            final List ownedMonitors = new ArrayList(0);
            if (jthread != null) {
                List lockedObjects = new ArrayList(); // List<OopHandle>

                ObjectMonitor waitingMonitor = jthread.getCurrentWaitingMonitor();
                OopHandle waitingObj = null;
                if (waitingMonitor != null) {
                   // save object of current wait() call (if any) for later comparison
                   waitingObj = waitingMonitor.object();
                }

                ObjectMonitor pendingMonitor = jthread.getCurrentPendingMonitor();
                OopHandle pendingObj = null;
                if (pendingMonitor != null) {
                    // save object of current enter() call (if any) for later comparison
                    pendingObj = pendingMonitor.object();
                }

                JavaVFrame frame = jthread.getLastJavaVFrameDbg();
                while (frame != null) {
                    List frameMonitors = frame.getMonitors();  // List<MonitorInfo>
                    for (Iterator miItr = frameMonitors.iterator(); miItr.hasNext(); ) {
                        MonitorInfo mi = (MonitorInfo) miItr.next();

                        if (mi.eliminated() && frame.isCompiledFrame()) {
                          continue; // skip eliminated monitor
                        }
                        OopHandle obj = mi.owner();
                        if (obj == null) {
                            // this monitor doesn't have an owning object so skip it
                            continue;
                        }

                        if (obj.equals(waitingObj)) {
                           // the thread is waiting on this monitor so it isn't really owned
                           continue;
                        }

                        if (obj.equals(pendingObj)) {
                            // the thread is pending on this monitor so it isn't really owned
                            continue;
                        }

                        boolean found = false;
                        for (Iterator loItr = lockedObjects.iterator(); loItr.hasNext(); ) {
                            // check for recursive locks
                            if (obj.equals(loItr.next())) {
                                found = true;
                                break;
                            }
                        }
                        if (found) {
                            // already have this object so don't include it
                            continue;
                        }
                        // add the owning object to our list
                        lockedObjects.add(obj);
                    }
                    frame = (JavaVFrame) frame.javaSender();
                }

                // now convert List<OopHandle> to List<Oop>
                ObjectHeap heap = VM.getVM().getObjectHeap();
                for (Iterator loItr = lockedObjects.iterator(); loItr.hasNext(); ) {
                    ownedMonitors.add(heap.newOop((OopHandle)loItr.next()));
                }
            }
            monitorsCache = factory.newJSList(ownedMonitors);
        }
        return monitorsCache;
    }

    private JavaThread jthread;
    private JSList framesCache;
    private JSList monitorsCache;
}
