package jmv;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedList;

/**
 * Feasibility probe: can we read java.util.ArrayList/LinkedList internals in this JVM?
 * Plain reflection needs --add-opens; sun.misc.Unsafe field offsets do not.
 */
public final class InternalsProbe {
    public static native void emit(String json);

    public static void main(String[] args) {
        StringBuilder b = new StringBuilder("{");
        ArrayList<String> a = new ArrayList<>(); a.add("x"); a.add("y");
        LinkedList<String> l = new LinkedList<>(); l.add("p"); l.add("q");
        b.append("\"reflection\":").append(probe(a, "elementData", "size"));
        b.append(",\"unsafe\":");
        try {
            Field theUnsafe = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            Object unsafe = theUnsafe.get(null);
            Class<?> uc = unsafe.getClass();
            java.lang.reflect.Method offset = uc.getMethod("objectFieldOffset", Field.class);
            java.lang.reflect.Method getObject = uc.getMethod("getObject", Object.class, long.class);
            java.lang.reflect.Method getInt = uc.getMethod("getInt", Object.class, long.class);
            long dataOff = (Long) offset.invoke(unsafe, ArrayList.class.getDeclaredField("elementData"));
            long sizeOff = (Long) offset.invoke(unsafe, ArrayList.class.getDeclaredField("size"));
            Object data = getObject.invoke(unsafe, a, dataOff);
            int size = (Integer) getInt.invoke(unsafe, a, sizeOff);
            long firstOff = (Long) offset.invoke(unsafe, LinkedList.class.getDeclaredField("first"));
            Object first = getObject.invoke(unsafe, l, firstOff);
            Class<?> nodeClass = first.getClass();
            long itemOff = (Long) offset.invoke(unsafe, nodeClass.getDeclaredField("item"));
            long nextOff = (Long) offset.invoke(unsafe, nodeClass.getDeclaredField("next"));
            Object item = getObject.invoke(unsafe, first, itemOff);
            Object next = getObject.invoke(unsafe, first, nextOff);
            Object item2 = next == null ? null : getObject.invoke(unsafe, next, itemOff);
            b.append(Tracer.json("OK elementData len " + java.lang.reflect.Array.getLength(data) + " size " + size
                + " first.item " + item + " next.item " + item2 + " node " + nodeClass.getName()));
        } catch (Throwable e) { b.append(Tracer.json("FAIL " + e)); }
        b.append(",\"vm\":").append(Tracer.json(System.getProperty("java.vm.name") + " " + System.getProperty("java.version")));
        String json = b.append('}').toString();
        System.out.println(json);
        if (args.length == 0) emit(json);
    }

    static String probe(Object o, String... names) {
        StringBuilder b = new StringBuilder("{");
        for (String n : names) {
            if (b.length() > 1) b.append(',');
            try {
                Field f = o.getClass().getDeclaredField(n); f.setAccessible(true);
                Object v = f.get(o);
                b.append(Tracer.json(n)).append(':').append(Tracer.json("OK " + (v == null ? "null" : v.getClass().getName())));
            } catch (Throwable e) { b.append(Tracer.json(n)).append(':').append(Tracer.json("FAIL " + e.getClass().getSimpleName())); }
        }
        return b.append('}').toString();
    }
}
