/*
 * Copyright (c) 2004, 2017, 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 javax.script.ScriptException;
import sun.jvm.hotspot.debugger.*;
import sun.jvm.hotspot.classfile.*;
import sun.jvm.hotspot.memory.*;
import sun.jvm.hotspot.oops.*;
import sun.jvm.hotspot.runtime.*;
import sun.jvm.hotspot.utilities.*;
import java.lang.reflect.Method;

public class JSJavaHeap extends DefaultScriptObject {
    private static final int FIELD_CAPACITY = 0;
    private static final int FIELD_USED = 1;
    private static final int FIELD_FOR_EACH_OBJECT = 2;
    private static final int FIELD_FOR_EACH_CLASS = 3;

    private static final int FIELD_UNDEFINED = -1;

    public JSJavaHeap(JSJavaFactory fac) {
        this.factory = fac;
    }

    public Object get(String name) {
        int fieldID = getFieldID(name);
        switch (fieldID) {
        case FIELD_CAPACITY:
            return new Long(getCapacity());
        case FIELD_USED:
            return new Long(getUsed());
        case FIELD_FOR_EACH_OBJECT:
            return new MethodCallable(this, forEachObjectMethod);
        case FIELD_FOR_EACH_CLASS:
            return new MethodCallable(this, forEachClassMethod);
        case FIELD_UNDEFINED:
        default:
            return super.get(name);
        }
    }

    public Object[] getIds() {
        Object[] superIds = super.getIds();
        Object[] tmp = fields.keySet().toArray();
        Object[] res = new Object[superIds.length + tmp.length];
        System.arraycopy(tmp, 0, res, 0, tmp.length);
        System.arraycopy(superIds, 0, res, tmp.length, superIds.length);
        return res;
    }

    public boolean has(String name) {
        if (getFieldID(name) != FIELD_UNDEFINED) {
            return true;
        } else {
            return super.has(name);
        }
    }

    public void put(String name, Object value) {
        if (getFieldID(name) == FIELD_UNDEFINED) {
            super.put(name, value);
        }
    }

    public void forEachObject(Object[] args) {
        boolean subtypes = true;
        Klass kls = null;
        Callable func = null;
        switch (args.length) {
        case 3: {
            Object b = args[2];
            if (b != null && b instanceof Boolean) {
                subtypes = ((Boolean)b).booleanValue();
            }
        }
        case 2: {
            Object k = args[1];
            if (k == null) return;
            if (k instanceof JSJavaKlass) {
                kls = ((JSJavaKlass)k).getKlass();
            } else if (k instanceof String) {
                kls = SystemDictionaryHelper.findInstanceKlass((String)k);
                if (kls == null) return;
            }
        }
        case 1: {
            Object f = args[0];
            if (f != null && f instanceof Callable) {
                func = (Callable) f;
            } else {
                // unknown target - just return
                return ;
            }
        }
        break;

        default:
            return;
        }

        final Callable finalFunc = func;
      HeapVisitor visitor = new DefaultHeapVisitor() {
                public boolean doObj(Oop oop) {
                    JSJavaObject jo = factory.newJSJavaObject(oop);
                    if (jo != null) {
                  try {
                    finalFunc.call(new Object[] { jo });
                  } catch (ScriptException exp) {
                    throw new RuntimeException(exp);
                  }
                    }
                return false;
                }
            };
        ObjectHeap heap = VM.getVM().getObjectHeap();
        if (kls == null) {
            kls = SystemDictionaryHelper.findInstanceKlass("java.lang.Object");
        }
        heap.iterateObjectsOfKlass(visitor, kls, subtypes);
    }

    public void forEachClass(Object[] args) {
        boolean withLoader = false;
        Callable func = null;
        switch (args.length) {
        case 2: {
            Object b = args[1];
            if (b instanceof Boolean) {
                withLoader = ((Boolean)b).booleanValue();
            }
        }
        case 1: {
            Object f = args[0];
            if (f instanceof Callable) {
                func = (Callable) f;
            } else {
                return;
            }
        }
        break;
        default:
            return;
        }

      final Callable finalFunc = func;
        ClassLoaderDataGraph cldg = VM.getVM().getClassLoaderDataGraph();
        if (withLoader) {
            cldg.classesDo(new ClassLoaderDataGraph.ClassVisitor() {
                    public void visit(Klass kls) {
                        JSJavaKlass  jk = factory.newJSJavaKlass(kls);
                        Oop loader = kls.getClassLoader();
                        if (jk == null) {
                            return;
                        }
                        JSJavaObject k = jk.getJSJavaClass();
                        JSJavaObject l = factory.newJSJavaObject(loader);
                        if (k != null) {
                          if (l != null) {
                            try {
                              finalFunc.call(new Object[] { k, l });
                            } catch (ScriptException exp) {
                              throw new RuntimeException(exp);
                            }
                          }
                       }
                    }
                });

        } else {
            cldg.classesDo(new ClassLoaderDataGraph.ClassVisitor() {
                    public void visit(Klass kls) {
                        JSJavaKlass jk = factory.newJSJavaKlass(kls);
                        if (jk == null) {
                            return;
                        }
                        JSJavaClass k = jk.getJSJavaClass();
                        if (k != null) {
                            if (k != null) {
                        try {
                                  finalFunc.call(new Object[] { k });
                        } catch (ScriptException exp) {
                          throw new RuntimeException(exp);
                        }
                            }
                        }
                    }
                });
        }
    }

    public String toString() {
        StringBuffer buf = new StringBuffer();
        buf.append("Java Heap (capacity=");
        buf.append(getCapacity());
        buf.append(", used=");
        buf.append(getUsed());
        buf.append(")");
        return buf.toString();
    }

    //-- Internals only below this point
    private static Map fields = new HashMap();
    private static void addField(String name, int fieldId) {
        fields.put(name, new Integer(fieldId));
    }

    private static int getFieldID(String name) {
        Integer res = (Integer) fields.get(name);
        return (res != null)? res.intValue() : FIELD_UNDEFINED;
    }

    static {
        addField("capacity", FIELD_CAPACITY);
        addField("used", FIELD_USED);
        addField("forEachObject", FIELD_FOR_EACH_OBJECT);
        addField("forEachClass", FIELD_FOR_EACH_CLASS);
      try {
          Class myClass = JSJavaHeap.class;
          forEachObjectMethod = myClass.getMethod("forEachObject",
                                new Class[] { Object[].class });
          forEachClassMethod = myClass.getMethod("forEachClass",
                                new Class[] {Object[].class });
      } catch (RuntimeException re) {
          throw re;
      } catch (Exception exp) {
          throw new RuntimeException(exp);
      }
    }

    private long getCapacity() {
        return VM.getVM().getUniverse().heap().capacity();
    }

    private long getUsed() {
        return VM.getVM().getUniverse().heap().used();
    }

    private final JSJavaFactory factory;
    private static Method forEachObjectMethod;
    private static Method forEachClassMethod;
}