/*
 * Copyright 2016 Sam Sun <[email protected]>
 *
 *   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 com.javadeobfuscator.deobfuscator.executor.defined;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.CodeSource;
import java.security.Key;
import java.security.MessageDigest;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.KeySpec;
import java.util.*;
import java.util.regex.Pattern;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import com.javadeobfuscator.deobfuscator.executor.ThreadStore;
import com.javadeobfuscator.deobfuscator.executor.defined.types.*;
import com.javadeobfuscator.deobfuscator.executor.exceptions.ExecutionException;
import com.javadeobfuscator.deobfuscator.executor.values.*; 
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import com.javadeobfuscator.deobfuscator.executor.values.JavaCharacter;
import com.javadeobfuscator.deobfuscator.executor.values.JavaInteger;
import com.javadeobfuscator.deobfuscator.executor.values.JavaValue;
import com.javadeobfuscator.deobfuscator.utils.Utils;

import com.javadeobfuscator.deobfuscator.executor.Context;
import com.javadeobfuscator.deobfuscator.executor.providers.MethodProvider;
import org.objectweb.asm.Type;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class JVMMethodProvider extends MethodProvider {
    @SuppressWarnings("serial")
    //@formatter:off
    private static final Map<String, Map<String, Function3<JavaValue, List<JavaValue>, Context, Object>>> functions = new HashMap<String, Map<String, Function3<JavaValue, List<JavaValue>, Context, Object>>>() {{
        // Java
        put("java/lang/Object", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("getClass()Ljava/lang/Class;", (targetObject, args, context) -> new JavaClass(Type.getType(targetObject.value().getClass()).getInternalName(), context));
            put("notifyAll()V", (targetObject, args, context) -> {
                synchronized (targetObject.as(Object.class)) {
                    targetObject.as(Object.class).notifyAll();
                }
                return null;
            });
            put("wait(J)V", (targetObject, args, context) -> {
                synchronized (targetObject.as(Object.class)) {
                    targetObject.as(Object.class).wait(args.get(0).longValue());
                }
                return null;
            });
            put("equals(Ljava/lang/Object;)Z", (targetObject, args, context) -> targetObject.value().equals(args.get(0).value()));
            put("clone()Ljava/lang/Object;", (targetObject, args, context) -> {
            	Method clone = Object.class.getDeclaredMethod("clone");
            	clone.setAccessible(true);
            	try
            	{
            		return clone.invoke(targetObject.value());
            	}catch(InvocationTargetException e)
            	{
            		throw e.getTargetException();
            	}
            });
            put("<init>()V", (targetObject, args, context) -> {
                expect(targetObject, targetObject.type()); 
                targetObject.initialize(new JavaObject(null, targetObject.type())); 
                initObject(context, targetObject.type(), targetObject); 
                return null;
            });
        }});
        put("java/util/zip/ZipInputStream", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>(Ljava/io/InputStream;)V", (targetObject, args, context) -> {
                System.out.println("New ZipInputStream with " + args.get(0).value());
                targetObject.initialize(new ZipInputStream(args.get(0).as(InputStream.class)));
                return null;
            });
        }});
        put("java/nio/charset/Charset", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("availableCharsets()Ljava/util/SortedMap;", (targetObject, args, context) -> Charset.availableCharsets());
        }});
        put("java/util/SortedMap", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("keySet()Ljava/util/Set;", (targetObject, args, context) -> targetObject.as(SortedMap.class).keySet());
        }});
        put("java/util/Set", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("iterator()Ljava/util/Iterator;", (targetObject, args, context) -> targetObject.as(Set.class).iterator());
        }});
        put("java/util/Iterator", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("hasNext()Z", (targetObject, args, context) -> targetObject.as(Iterator.class).hasNext());
            put("next()Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(Iterator.class).next());
        }});
        put("java/io/ByteArrayOutputStream", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>()V", (targetObject, args, context) -> {
                targetObject.initialize(new ByteArrayOutputStream());
                return null;
            });
            put("close()V", (targetObject, args, context) -> {
                try {
                    targetObject.as(ByteArrayOutputStream.class).close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            });
            put("toByteArray()[B", (targetObject, args, context) -> targetObject.as(ByteArrayOutputStream.class).toByteArray());
            put("write([B)V", (targetObject, args, context) -> {
                try {
                    targetObject.as(ByteArrayOutputStream.class).write(args.get(0).as(byte[].class));
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            });
        }});
        put("java/io/PushbackInputStream", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>(Ljava/io/InputStream;I)V", (targetObject, args, context) -> {
                targetObject.initialize(new PushbackInputStream(args.get(0).as(InputStream.class), args.get(1).intValue()));
                return null;
            });
            put("unread([BII)V", (targetObject, args, context) -> {
            	targetObject.as(PushbackInputStream.class).unread(args.get(0).as(byte[].class), args.get(1).intValue(), args.get(2).intValue());
            	return null;
            });
            put("read([BII)I", (targetObject, args, context) -> targetObject.as(PushbackInputStream.class).read(args.get(0).as(byte[].class), args.get(1).intValue(), args.get(2).intValue()));
        }});
        put("java/io/FilterInputStream", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>(Ljava/io/InputStream;)V", (targetObject, args, context) -> {
            	Constructor<FilterInputStream> init = FilterInputStream.class.getDeclaredConstructor(InputStream.class);
            	init.setAccessible(true);
            	targetObject.initialize(init.newInstance(args.get(0).as(InputStream.class)));
                return null;
            });
            put("read([BII)I", (targetObject, args, context) -> targetObject.as(FilterInputStream.class).read(args.get(0).as(byte[].class), args.get(1).intValue(), args.get(2).intValue()));
        }});
        put("java/util/zip/InflaterInputStream", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	 put("<init>(Ljava/io/InputStream;Ljava/util/zip/Inflater;)V", (targetObject, args, context) -> {
                 targetObject.initialize(new InflaterInputStream(args.get(0).as(InputStream.class), args.get(1).as(Inflater.class)));
                 return null;
             });
        	 put("read([B)I", (targetObject, args, context) -> targetObject.as(InflaterInputStream.class).read(args.get(0).as(byte[].class)));
        	 put("read([BII)I", (targetObject, args, context) -> targetObject.as(InflaterInputStream.class).read(args.get(0).as(byte[].class), args.get(1).intValue(), args.get(2).intValue()));
        }});
        put("java/util/zip/Inflater", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("<init>(Z)V", (targetObject, args, context) -> {
        		targetObject.initialize(new Inflater(args.get(0).as(boolean.class)));
        		return null;
        	});
        	put("setInput([BII)V", (targetObject, args, context) -> {
        		targetObject.as(Inflater.class).setInput(args.get(0).as(byte[].class), args.get(1).intValue(), args.get(2).intValue());
        		return null;
        	});
       }});
        put("java/io/InputStream", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("read([BII)I", (targetObject, args, context) -> targetObject.as(InputStream.class).read(args.get(0).as(byte[].class), args.get(1).intValue(), args.get(2).intValue()));
        }});
        put("java/util/List", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("add(Ljava/lang/Object;)Z", (targetObject, args, context) -> targetObject.as(List.class).add(args.get(0).as(Object.class)));
            put("size()I", (targetObject, args, context) -> targetObject.as(List.class).size());
            put("get(I)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(List.class).get(args.get(0).intValue()));
            put("set(ILjava/lang/Object;)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(List.class).set(args.get(0).intValue(), args.get(1).as(Object.class)));
            put("toArray()[Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(List.class).toArray());
            put("iterator()Ljava/util/Iterator;", (targetObject, args, context) -> targetObject.as(List.class).iterator());
        }});
        put("java/util/Arrays", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("asList([Ljava/lang/Object;)Ljava/util/List;", (targetObject, args, context) -> Arrays.asList(args.get(0).as(Object[].class)));
            put("toString([Ljava/lang/Object;)Ljava/lang/String;", (targetObject, args, context) -> Arrays.toString(args.get(0).as(Object[].class)));
            put("copyOf([BI)[B", (targetObject, args, context) -> Arrays.copyOf(args.get(0).as(byte[].class), args.get(1).intValue()));
            put("equals([Ljava/lang/Object;[Ljava/lang/Object;)Z", (targetObject, args, context) -> Arrays.equals(args.get(0).as(Object[].class), args.get(1).as(Object[].class)));
        }});
        put("java/util/ArrayList", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>()V", (targetObject, args, context) -> {
                expect(targetObject, "java/util/ArrayList");
                targetObject.initialize(new ArrayList<>());
                return null;
            });
            put("<init>(Ljava/util/Collection;)V", (targetObject, args, context) -> {
                expect(targetObject, "java/util/ArrayList");
                targetObject.initialize(new ArrayList<>(args.get(0).as(Collection.class)));
                return null;
            });
            put("add(Ljava/lang/Object;)Z", (targetObject, args, context) -> targetObject.as(ArrayList.class).add(args.get(0).as(Object.class)));
            put("size()I", (targetObject, args, context) -> targetObject.as(ArrayList.class).size());
            put("get(I)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(ArrayList.class).get(args.get(0).intValue()));
            put("set(ILjava/lang/Object;)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(ArrayList.class).set(args.get(0).intValue(), args.get(1).as(Object.class)));
            put("toArray()[Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(ArrayList.class).toArray());
            put("iterator()Ljava/util/Iterator;", (targetObject, args, context) -> targetObject.as(ArrayList.class).iterator());
        }});
        put("java/lang/String", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("<init>(Ljava/lang/StringBuffer;)V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/String");
                targetObject.initialize(new String(args.get(0).as(StringBuffer.class)));
                return null;
            });
            put("<init>([CII)V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/String");
                targetObject.initialize(new String(args.get(0).as(char[].class), args.get(1).intValue(), args.get(2).intValue()));
                return null;
            });
            put("<init>([C)V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/String");
                targetObject.initialize(new String(args.get(0).as(char[].class)));
                return null;
            });
            put("<init>([B)V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/String");
                targetObject.initialize(new String(args.get(0).as(byte[].class)));
                return null;
            });
            put("<init>([BI)V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/String");
                targetObject.initialize(new String(args.get(0).as(byte[].class), args.get(1).intValue()));
                return null;
            });
            put("<init>([BLjava/lang/String;)V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/String");
                targetObject.initialize(new String(args.get(0).as(byte[].class), args.get(1).as(String.class)));
                return null;
            });
            put("getBytes(Ljava/nio/charset/Charset;)[B", (targetObject, args, context) -> targetObject.as(String.class).getBytes(args.get(0).as(Charset.class)));
            put("toString()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(String.class).toString());
            put("intern()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(String.class).intern());
            put("valueOf(I)Ljava/lang/String;", (targetObject, args, context) -> String.valueOf(args.get(0).intValue()));
            put("equals(Ljava/lang/Object;)Z", (targetObject, args, context) -> targetObject.as(String.class).equals(args.get(0).value()));
            put("trim()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(String.class).trim());
            put("toCharArray()[C", (targetObject, args, context) -> targetObject.as(String.class).toCharArray());
            put("length()I", (targetObject, args, context) -> targetObject.as(String.class).length());
            put("hashCode()I", (targetObject, args, context) -> targetObject.as(String.class).hashCode());
            put("charAt(I)C", (targetObject, args, context) -> targetObject.as(String.class).charAt(args.get(0).intValue()));
            put("indexOf(I)I", (targetObject, args, context) -> targetObject.as(String.class).indexOf(args.get(0).intValue()));
            put("endsWith(Ljava/lang/String;)Z", (targetObject, args, context) -> targetObject.as(String.class).endsWith(args.get(0).as(String.class)));
            put("startsWith(Ljava/lang/String;)Z", (targetObject, args, context) -> targetObject.as(String.class).startsWith(args.get(0).as(String.class)));
            put("substring(I)Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(String.class).substring(args.get(0).intValue()));
            put("substring(II)Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(String.class).substring(args.get(0).intValue(), args.get(1).intValue()));
            put("indexOf(Ljava/lang/String;)I", (targetObject, args, context) -> targetObject.as(String.class).indexOf(args.get(0).as(String.class)));
            put("indexOf(II)I", (targetObject, args, context) -> targetObject.as(String.class).indexOf(args.get(0).intValue(), args.get(1).intValue()));
            put("lastIndexOf(Ljava/lang/String;)I", (targetObject, args, context) -> targetObject.as(String.class).lastIndexOf(args.get(0).as(String.class)));
            put("lastIndexOf(I)I", (targetObject, args, context) -> targetObject.as(String.class).lastIndexOf(args.get(0).intValue()));
            put("isEmpty()Z", (targetObject, args, context) -> targetObject.as(String.class).isEmpty());
            put("format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", (targetObject, args, context) -> String.format(args.get(0).as(String.class), args.get(1).as(Object[].class)));
            put("split(Ljava/lang/String;)[Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(String.class).split(args.get(0).as(String.class)));
            put("valueOf(Ljava/lang/Object;)Ljava/lang/String;", (targetObject, args, context) -> String.valueOf(args.get(0).value()));
            put("valueOf([C)Ljava/lang/String;", (targetObject, args, context) -> String.valueOf(args.get(0).as(char[].class)));
            put("replaceAll(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(String.class).replaceAll(args.get(0).as(String.class), args.get(1).as(String.class)));
            put("getBytes(Ljava/lang/String;)[B", (targetObject, args, context) -> targetObject.as(String.class).getBytes(args.get(0).as(String.class)));
            put("valueOf([CII)Ljava/lang/String;", (targetObject, args, context) -> String.valueOf(args.get(0).as(char[].class), args.get(1).intValue(), args.get(2).intValue()));
            put("replace(CC)Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(String.class).replace(args.get(0).as(char.class), args.get(1).as(char.class)));
        }});
        put("java/lang/StringBuilder", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>()V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/StringBuilder");
                targetObject.initialize(new StringBuilder());
                return null;
            });
            put("<init>(I)V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/StringBuilder");
                targetObject.initialize(new StringBuilder(args.get(0).intValue()));
                return null;
            });
            put("<init>(Ljava/lang/String;)V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/StringBuilder");
                targetObject.initialize(new StringBuilder(args.get(0).as(String.class)));
                return null;
            });
            put("append(I)Ljava/lang/StringBuilder;", (targetObject, args, context) -> targetObject.as(StringBuilder.class).append(args.get(0).intValue()));
            put("append(C)Ljava/lang/StringBuilder;", (targetObject, args, context) -> targetObject.as(StringBuilder.class).append(args.get(0).as(char.class)));
            put("append(Ljava/lang/String;)Ljava/lang/StringBuilder;", (targetObject, args, context) -> targetObject.as(StringBuilder.class).append(args.get(0).as(String.class)));
            put("append(Ljava/lang/Object;)Ljava/lang/StringBuilder;", (targetObject, args, context) -> targetObject.as(StringBuilder.class).append(args.get(0).as(Object.class)));
            put("reverse()Ljava/lang/StringBuilder;", (targetObject, args, context) -> targetObject.as(StringBuilder.class).reverse());
            put("toString()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(StringBuilder.class).toString());
            put("length()I", (targetObject, args, context) -> targetObject.as(StringBuilder.class).length());
            put("charAt(I)C", (targetObject, args, context) -> targetObject.as(StringBuilder.class).charAt(args.get(0).intValue()));
            put("setCharAt(IC)V", (targetObject, args, context) -> {
                targetObject.as(StringBuilder.class).setCharAt(args.get(0).intValue(), (char) ((JavaCharacter) args.get(1)).charValue());
                return null;
            });
        }});
        put("java/lang/StringBuffer", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>(Ljava/lang/String;)V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/StringBuffer");
                targetObject.initialize(new StringBuffer(args.get(0).as(String.class)));
                return null;
            });
            put("<init>(I)V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/StringBuffer");
                targetObject.initialize(new StringBuffer(args.get(0).intValue()));
                return null;
            });
            put("<init>()V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/StringBuffer");
                targetObject.initialize(new StringBuffer());
                return null;
            });
            put("insert(ILjava/lang/String;)Ljava/lang/StringBuffer;", (targetObject, args, context) -> targetObject.as(StringBuffer.class).insert(args.get(0).intValue(), args.get(1).as(String.class)));
            put("append(Ljava/lang/String;)Ljava/lang/StringBuffer;", (targetObject, args, context) -> targetObject.as(StringBuffer.class).append(args.get(0).as(String.class)));
            put("append(C)Ljava/lang/StringBuffer;", (targetObject, args, context) -> targetObject.as(StringBuffer.class).append(args.get(0).as(char.class)));
            put("toString()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(StringBuffer.class).toString());
        }});
        put("java/lang/CharSequence", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("toString()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(CharSequence.class).toString());
        	put("length()I", (targetObject, args, context) -> targetObject.as(CharSequence.class).length());
        	put("charAt(I)C", (targetObject, args, context) -> targetObject.as(CharSequence.class).charAt(args.get(0).intValue()));
        }});
        put("java/lang/Exception", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>()V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/Exception");
                targetObject.initialize(null);
                return null;
            });
            put("getStackTrace()[Ljava/lang/StackTraceElement;", (targetObject, args, context) -> context.getStackTrace());
            put("toString()Ljava/lang/String;", (targetObject, args, context) -> targetObject.toString());
        }});
        put("java/lang/Throwable", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>()V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/Throwable");
                targetObject.initialize(null);
                return null;
            });
            put("getStackTrace()[Ljava/lang/StackTraceElement;", (targetObject, args, context) -> context.getStackTrace());
            put("toString()Ljava/lang/String;", (targetObject, args, context) -> targetObject.toString());
        }});
        put("java/lang/NullPointerException", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>()V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/NullPointerException");
                targetObject.initialize(null);
                return null;
            });
            put("getStackTrace()[Ljava/lang/StackTraceElement;", (targetObject, args, context) -> context.getStackTrace());
            put("toString()Ljava/lang/String;", (targetObject, args, context) -> targetObject.toString());
        }});
        put("java/lang/RuntimeException", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>(Ljava/lang/String;)V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/RuntimeException");
                targetObject.initialize(new RuntimeException(args.get(0).as(String.class)));
                return null;
            });
            put("<init>()V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/RuntimeException");
                targetObject.initialize(new RuntimeException());
                return null;
            });
            put("getStackTrace()[Ljava/lang/StackTraceElement;", (targetObject, args, context) -> context.getStackTrace());
        }});
        put("java/lang/Class", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("forName(Ljava/lang/String;)Ljava/lang/Class;", (targetObject, args, context) -> new JavaClass(args.get(0).as(String.class), context));
            put("getDeclaredConstructor([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getDeclaredConstructor(toJavaClass(args.get(0).as(Object[].class))));
            put("getDeclaredConstructors()[Ljava/lang/reflect/Constructor;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getDeclaredConstructors());
            put("getConstructor([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getConstructor(toJavaClass(args.get(0).as(Object[].class))));
            put("getConstructors()[Ljava/lang/reflect/Constructor;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getConstructors());
            put("getDeclaredMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getDeclaredMethod(args.get(0).as(String.class), toJavaClass(args.get(1).as(Object[].class))));
            put("getDeclaredMethods()[Ljava/lang/reflect/Method;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getDeclaredMethods());
            put("getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getMethod(args.get(0).as(String.class), toJavaClass(args.get(1).as(Object[].class))));
            put("getMethods()[Ljava/lang/reflect/Method;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getMethods());
            put("getDeclaredField(Ljava/lang/String;)Ljava/lang/reflect/Field;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getDeclaredField(args.get(0).as(String.class)));
            put("getDeclaredFields()[Ljava/lang/reflect/Field;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getDeclaredFields());
            put("getField(Ljava/lang/String;)Ljava/lang/reflect/Field;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getField(args.get(0).as(String.class)));
            put("getFields()[Ljava/lang/reflect/Field;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getFields());
            put("getClassLoader()Ljava/lang/ClassLoader;", (targetObject, args, context) -> null);
            put("getName()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getName());
            put("getSimpleName()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getSimpleName());
            put("getSuperclass()Ljava/lang/Class;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getSuperclass());
            put("getInterfaces()[Ljava/lang/Class;", (targetObject, args, context) -> targetObject.as(JavaClass.class).getInterfaces());
            put("getProtectionDomain()Ljava/security/ProtectionDomain;", (targetObject, args, context) -> new ProtectionDomain(new CodeSource(context.file.toURI().toURL(), new Certificate[0]), null));
            put("isInterface()Z", (targetObject, args, context) -> targetObject.as(JavaClass.class).isInterface());
            put("isArray()Z", (targetObject, args, context) -> targetObject.as(JavaClass.class).getType().getSort() == Type.ARRAY);
            put("equals(Ljava/lang/Object;)Z", (targetObject, args, context) -> targetObject.as(JavaClass.class).equals(args.get(0).as(JavaClass.class)));
        }});
        put("java/security/ProtectionDomain", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("getCodeSource()Ljava/security/CodeSource;", (targetObject, args, context) -> targetObject.as(ProtectionDomain.class).getCodeSource());
        }});
        put("java/security/CodeSource", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("getLocation()Ljava/net/URL;", (targetObject, args, context) -> targetObject.as(CodeSource.class).getLocation());
        }});
        put("java/security/MessageDigest", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;", (targetObject, args, context) -> MessageDigest.getInstance(args.get(0).as(String.class)));
            put("digest([B)[B", (targetObject, args, context) -> targetObject.as(MessageDigest.class).digest(args.get(0).as(byte[].class)));
        }});
        put("java/net/URL", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("toURI()Ljava/net/URI;", (targetObject, args, context) -> targetObject.as(URL.class).toURI());
            // Probably not an issue because you can't construct URLs yet
            put("openStream()Ljava/io/InputStream;", (targetObject, args, context) -> {
                URL url = targetObject.as(URL.class);
                if (url.getProtocol().equals("file")) {
                    return url.openStream();
                }
                throw new ExecutionException("Disallowed opening URL for now");
            });
        }});
        put("java/net/URI", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("getPath()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(URI.class).getPath());
        }});
        put("java/io/File", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("<init>(Ljava/lang/String;)V", (targetObject, args, context) -> {
                expect(targetObject, "java/io/File");
                targetObject.initialize(new File(args.get(0).as(String.class)));
                return null;
            });
        }});
        put("java/util/zip/ZipFile", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("<init>(Ljava/lang/String;)V", (targetObject, args, context) -> {
                expect(targetObject, "java/util/zip/ZipFile");
                targetObject.initialize(new ZipFile(args.get(0).as(String.class)));
                return null;
            });
        	put("<init>(Ljava/io/File;)V", (targetObject, args, context) -> {
                expect(targetObject, "java/util/zip/ZipFile");
                targetObject.initialize(new ZipFile(args.get(0).as(File.class)));
                return null;
            });
        	put("entries()Ljava/util/Enumeration;", (targetObject, args, context) -> targetObject.as(ZipFile.class).entries());
        	put("close()V", (targetObject, args, context) -> {
        		targetObject.as(ZipFile.class).close();
        		return null;
        	});
        }});
        put("java/util/Enumeration", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("hasMoreElements()Z", (targetObject, args, context) -> targetObject.as(Enumeration.class).hasMoreElements());
        	put("nextElement()Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(Enumeration.class).nextElement());
        }});
        put("java/util/zip/ZipInputStream", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>(Ljava/io/InputStream;)V", (targetObject, args, context) -> {
                expect(targetObject, "java/util/zip/ZipInputStream");
                targetObject.initialize(new ZipInputStream(args.get(0).as(InputStream.class)));
                return null;
            });
            put("getNextEntry()Ljava/util/zip/ZipEntry;", (targetObject, args, context) -> targetObject.as(ZipInputStream.class).getNextEntry());
            put("closeEntry()V", (targetObject, args, context) -> {
                targetObject.as(ZipInputStream.class).closeEntry();
                return null;
            });
        }});
        put("java/util/zip/ZipEntry", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("getName()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(ZipEntry.class).getName());
            put("getExtra()[B", (targetObject, args, context) -> targetObject.as(ZipEntry.class).getExtra());
            put("getLastAccessTime()Ljava/nio/file/attribute/FileTime;", (targetObject, args, context) -> targetObject.as(ZipEntry.class).getLastAccessTime());
            put("getCreationTime()Ljava/nio/file/attribute/FileTime;", (targetObject, args, context) -> targetObject.as(ZipEntry.class).getCreationTime());
        }});
        put("java/lang/reflect/Constructor", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("getParameterTypes()[Ljava/lang/Class;", (targetObject, args, context) -> targetObject.as(JavaConstructor.class).getParameterTypes());
            put("setAccessible(Z)V", (targetObject, args, context) -> {
                targetObject.as(JavaConstructor.class).setAccessible(args.get(0).as(boolean.class));
                return null;
            });

            put("newInstance([Ljava/lang/Object;)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(JavaConstructor.class).newInstance(context, args.get(0)));
        }});
        put("java/lang/reflect/Method", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("getName()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(JavaMethod.class).getName());
            put("getReturnType()Ljava/lang/Class;", (targetObject, args, context) -> targetObject.as(JavaMethod.class).getReturnType());
            put("getParameterTypes()[Ljava/lang/Class;", (targetObject, args, context) -> targetObject.as(JavaMethod.class).getParameterTypes());
            put("setAccessible(Z)V", (targetObject, args, context) -> {
                targetObject.as(JavaMethod.class).setAccessible(args.get(0).as(boolean.class));
                return null;
            });
            put("hashCode()I", (targetObject, args, context) -> targetObject.as(JavaMethod.class).hashCode());
            put("invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", (targetObject, args, context) -> {
            	context.push("java.lang.reflect.Method", "invoke", 0);
            	context.push("sun.reflect.DelegatingMethodAccessorImpl", "invoke", 0);
            	context.push("sun.reflect.NativeMethodAccessorImpl", "invoke", 0);
            	context.push("sun.reflect.NativeMethodAccessorImpl", "invoke0", 0);
            	return targetObject.as(JavaMethod.class).invoke(args.get(0), args.get(1), context);
            });
            put("getDeclaringClass()Ljava/lang/Class;", (targetObject, args, context) -> targetObject.as(JavaMethod.class).getDeclaringClass());
        }});
        put("java/lang/reflect/Field", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("getName()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(JavaField.class).getName());
            put("getType()Ljava/lang/Class;", (targetObject, args, context) -> targetObject.as(JavaField.class).getType());
            put("setAccessible(Z)V", (targetObject, args, context) -> {
                targetObject.as(JavaField.class).setAccessible(args.get(0).as(boolean.class));
                return null;
            });
            put("getModifiers()I", (targetObject, args, context) -> targetObject.as(JavaField.class).getModifiers());
            put("setInt(Ljava/lang/Object;I)V", (targetObject, args, context) -> {
                targetObject.as(JavaField.class).setInt(args.get(0).as(Object.class), args.get(1).intValue());
                return null;
            });
            put("set(Ljava/lang/Object;Ljava/lang/Object;)V", (targetObject, args, context) -> {
                targetObject.as(JavaField.class).set(args.get(0).as(Object.class), args.get(0).as(Object.class));
                return null;
            });
            put("get(Ljava/lang/Object;)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(JavaField.class).get(args.get(0).as(Object.class)));
            put("getDeclaringClass()Ljava/lang/Class;", (targetObject, args, context) -> targetObject.as(JavaField.class).getDeclaringClass());
        }});
        put("java/lang/reflect/Modifier", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("isStatic(I)Z", (targetObject, args, context) -> Modifier.isStatic(args.get(0).intValue()));
            put("isFinal(I)Z", (targetObject, args, context) -> Modifier.isFinal(args.get(0).intValue()));
        }});
        put("java/lang/invoke/MethodType", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("fromMethodDescriptorString(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/invoke/MethodType;", (targetObject, args, context) -> args.get(0).value());
            put("methodType(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/invoke/MethodType;", (targetObject, args, context) -> {
            	JavaClass[] arguments = toJavaClass(args.get(1).as(Object[].class));
            	Type[] types = new Type[arguments.length];
            	for(int i = 0; i < arguments.length; i++)
            		types[i] = arguments[i].getType();
            	return Type.getMethodDescriptor(args.get(0).as(JavaClass.class).getType(), types);
            });
            put("methodType(Ljava/lang/Class;Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/invoke/MethodType;", (targetObject, args, context) -> {
            	JavaClass[] array = new JavaClass[args.get(2).as(Object[].class).length + 1];
            	array[0] = args.get(1).as(JavaClass.class);
            	System.arraycopy(toJavaClass(args.get(2).as(Object[].class)), 0, array, 1, args.get(2).as(Object[].class).length);
            	Type[] types = new Type[array.length];
            	for(int i = 0; i < array.length; i++)
            		types[i] = array[i].getType();
            	return Type.getMethodDescriptor(args.get(0).as(JavaClass.class).getType(), types);
            });
            put("parameterCount()I", (targetObject, args, context) -> {
            	Type[] type = Type.getArgumentTypes(targetObject.as(String.class));
            	return type.length;
            });
            put("dropParameterTypes(II)Ljava/lang/invoke/MethodType;", (targetObject, args, context) -> {
            	Type[] type = Type.getArgumentTypes(targetObject.as(String.class));
            	int start = args.get(0).intValue();
            	int end = args.get(1).intValue();
            	int len = type.length;
            	if(!(0 <= start && start <= end && end <= len))
            		throw new IndexOutOfBoundsException("start=" + start +" end=" + end);
            	Type[] copy;
            	if(start == 0)
            	{
            		if(end == len)
            			copy = new Type[0];
            		else
            			copy = Arrays.copyOfRange(type, end, len);
            	 }else
            	 {
            		 if(end == len)
            			 copy = Arrays.copyOfRange(type, 0, start);
            		 else 
            		 {
            			 int tail = len - end;
            			 copy = Arrays.copyOfRange(type, 0, start + tail);
            			 System.arraycopy(type, end, copy, start, tail);
            		 }
            	 }
            	return Type.getMethodDescriptor(Type.getReturnType(targetObject.as(String.class)), copy);
            });
        }});
        put("java/lang/invoke/MethodHandles$Lookup", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("findStatic(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", (targetObject, args, context) -> new JavaMethodHandle(args.get(0).as(JavaClass.class).getType().getInternalName(), args.get(1).as(String.class), args.get(2).as(String.class), "static"));
            put("findVirtual(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", (targetObject, args, context) -> new JavaMethodHandle(args.get(0).as(JavaClass.class).getType().getInternalName(), args.get(1).as(String.class), args.get(2).as(String.class), "virtual"));
            put("findSpecial(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/Class;)Ljava/lang/invoke/MethodHandle;", (targetObject, args, context) -> new JavaMethodHandle(args.get(0).as(JavaClass.class).getType().getInternalName(), args.get(1).as(String.class), args.get(2).as(String.class), "special"));
            put("unreflect(Ljava/lang/reflect/Method;)Ljava/lang/invoke/MethodHandle;", (targetObject, args, context) -> new JavaMethodHandle(args.get(0).as(JavaMethod.class).getDeclaringClass().getName().replace(".", "/"), args.get(0).as(JavaMethod.class).getName(), args.get(0).as(JavaMethod.class).getDesc(), args.get(0).as(JavaMethod.class).isStatic() ? "static" : "virtual"));
            put("findStaticGetter(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/invoke/MethodHandle;", (targetObject, args, context) -> new JavaFieldHandle(args.get(0).as(JavaClass.class).getType().getInternalName(), args.get(1).as(String.class), args.get(2).as(JavaClass.class).getType().getDescriptor(), "static", false));
            put("findGetter(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/invoke/MethodHandle;", (targetObject, args, context) -> new JavaFieldHandle(args.get(0).as(JavaClass.class).getType().getInternalName(), args.get(1).as(String.class), args.get(2).as(JavaClass.class).getType().getDescriptor(), "virtual", false));
            put("findStaticSetter(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/invoke/MethodHandle;", (targetObject, args, context) -> new JavaFieldHandle(args.get(0).as(JavaClass.class).getType().getInternalName(), args.get(1).as(String.class), args.get(2).as(JavaClass.class).getType().getDescriptor(), "static", true));
            put("findSetter(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/invoke/MethodHandle;", (targetObject, args, context) -> new JavaFieldHandle(args.get(0).as(JavaClass.class).getType().getInternalName(), args.get(1).as(String.class), args.get(2).as(JavaClass.class).getType().getDescriptor(), "virtual", true));
            put("unreflectGetter(Ljava/lang/reflect/Field;)Ljava/lang/invoke/MethodHandle;", (targetObject, args, context) -> new JavaFieldHandle(args.get(0).as(JavaField.class).getDeclaringClass().getName().replace(".", "/"), args.get(0).as(JavaField.class).getName(), args.get(0).as(JavaField.class).getDesc(), Modifier.isStatic(args.get(0).as(JavaField.class).getModifiers()) ? "static" : "virtual", false));
            put("unreflectSetter(Ljava/lang/reflect/Field;)Ljava/lang/invoke/MethodHandle;", (targetObject, args, context) -> new JavaFieldHandle(args.get(0).as(JavaField.class).getDeclaringClass().getName().replace(".", "/"), args.get(0).as(JavaField.class).getName(), args.get(0).as(JavaField.class).getDesc(), Modifier.isStatic(args.get(0).as(JavaField.class).getModifiers()) ? "static" : "virtual", true));
        }});
        put("java/lang/invoke/MethodHandle", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("asType(Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", (targetObject, args, context) -> targetObject.value());
        }});
        put("java/lang/invoke/MethodHandles", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("dropArguments(Ljava/lang/invoke/MethodHandle;I[Ljava/lang/Class;)Ljava/lang/invoke/MethodHandle;", (targetObject, args, context) -> args.get(0).value());
        }});
        put("java/lang/invoke/ConstantCallSite", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>(Ljava/lang/invoke/MethodHandle;)V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/invoke/ConstantCallSite");
                targetObject.initialize(args.get(0).value());
                return null;
            });
        }});
        put("java/lang/invoke/MutableCallSite", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>(Ljava/lang/invoke/MethodHandle;)V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/invoke/MutableCallSite");
                targetObject.initialize(args.get(0).value());
                return null;
            });
            put("setTarget(Ljava/lang/invoke/MethodHandle;)V", (targetObject, args, context) -> null);
            put("getTarget()Ljava/lang/invoke/MethodHandle;", (targetObject, args, context) -> targetObject.value());
        }});
        put("java/lang/System", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("currentTimeMillis()J", (targetObject, args, context) -> System.currentTimeMillis());
            put("arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V", (targetObject, args, context) -> {
                System.arraycopy(args.get(0).value(), args.get(1).intValue(), args.get(2).value(), args.get(3).intValue(), args.get(4).intValue());
                return null;
            });
        }});
        put("java/lang/Thread", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("start()V", (targetObject, args, context) -> {
                targetObject.as(JavaThread.class).start();
                return null;
            });
            put("currentThread()Ljava/lang/Thread;", (targetObject, args, context) -> ThreadStore.retrieveThread(Thread.currentThread().getId()));
            put("getId()J", (targetObject, args, context) -> targetObject.as(JavaThread.class).getThread().getId());
            put("getStackTrace()[Ljava/lang/StackTraceElement;", (targetObject, args, context) -> {
                context.push("java.lang.Thread", "getStackTrace", 0);
                StackTraceElement[] elems = context.getStackTrace();
                context.pop();
                return elems;
            });
            put("join()V", (targetObject, args, context) -> {
                targetObject.as(JavaThread.class).getThread().join();
                return null;
            });
            put("yield()V", (targetObject, args, context) -> {
                Thread.yield();
                return null;
            });
            put("<init>()V", (targetObject, args, context) -> {
                targetObject.initialize(new JavaThread(context, (JavaObject) targetObject));
                return null;
            });
        }});
        put("java/lang/StackTraceElement", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("getClassName()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(StackTraceElement.class).getClassName());
            put("getMethodName()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(StackTraceElement.class).getMethodName());
            put("getFileName()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(StackTraceElement.class).getFileName());
        }});
        put("java/lang/Long", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("<init>(J)V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/Long");
                targetObject.initialize(new Long(args.get(0).longValue()));
                return null;
            });
            put("parseLong(Ljava/lang/String;)J", (targetObject, args, context) -> Long.parseLong(args.get(0).as(String.class)));
            put("parseLong(Ljava/lang/String;I)J", (targetObject, args, context) -> Long.parseLong(args.get(0).as(String.class), args.get(1).intValue()));
            put("valueOf(J)Ljava/lang/Long;", (targetObject, args, context) -> Long.valueOf(args.get(0).longValue()));
            put("valueOf(Ljava/lang/String;)Ljava/lang/Long;", (targetObject, args, context) -> Long.valueOf(args.get(0).as(String.class)));
            put("longValue()J", (targetObject, args, context) -> ((Long)targetObject.value()).longValue());
            put("intValue()I", (targetObject, args, context) -> ((Long)targetObject.value()).intValue());
        }});
        put("java/lang/Integer", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("<init>(I)V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/Integer");
                targetObject.initialize(new Integer(args.get(0).intValue()));
                return null;
            });
        	put("parseInt(Ljava/lang/String;)I", (targetObject, args, context) -> Integer.parseInt(args.get(0).as(String.class)));
            put("valueOf(Ljava/lang/String;)Ljava/lang/Integer;", (targetObject, args, context) -> Integer.valueOf(args.get(0).as(String.class)));
            put("valueOf(Ljava/lang/String;I)Ljava/lang/Integer;", (targetObject, args, context) -> Integer.valueOf(args.get(0).as(String.class), args.get(1).intValue()));
            put("valueOf(I)Ljava/lang/Integer;", (targetObject, args, context) -> Integer.valueOf(args.get(0).intValue()));
            put("intValue()I", (targetObject, args, context) -> ((Integer)targetObject.value()).intValue());
        }});
        put("java/lang/Character", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("valueOf(C)Ljava/lang/Character;", (targetObject, args, context) -> Character.valueOf(args.get(0).as(char.class)));
            put("charValue()C", (targetObject, args, context) -> ((Character)targetObject.value()).charValue());
        }});
        put("java/lang/Boolean", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("valueOf(Z)Ljava/lang/Boolean;", (targetObject, args, context) -> Boolean.valueOf(args.get(0).as(boolean.class)));
            put("booleanValue()Z", (targetObject, args, context) -> ((Boolean)targetObject.value()).booleanValue());
        }});
        put("java/util/regex/Pattern", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("compile(Ljava/lang/String;)Ljava/util/regex/Pattern;", (targetObject, args, context) -> Pattern.compile(args.get(0).as(String.class)));
        }});
        put("java/lang/BootstrapMethodError", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>()V", (targetObject, args, context) -> {
                expect(targetObject, "java/lang/BootstrapMethodError");
                targetObject.initialize(new BootstrapMethodError());
                return null;
            });
        }});
        put("java/util/TreeMap", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>()V", (targetObject, args, context) -> {
                expect(targetObject, "java/util/TreeMap");
                targetObject.initialize(new TreeMap<>());
                return null;
            });
        }});
        put("java/util/HashMap", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>()V", (targetObject, args, context) -> {
                expect(targetObject, "java/util/HashMap");
                targetObject.initialize(new HashMap<>());
                return null;
            });
            put("<init>(I)V", (targetObject, args, context) -> {
                expect(targetObject, "java/util/HashMap");
                targetObject.initialize(new HashMap<>(args.get(0).intValue()));
                return null;
            });
            put("put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(HashMap.class).put(args.get(0), args.get(1)));
            put("get(Ljava/lang/Object;)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(HashMap.class).get(args.get(0).value()));
            put("containsKey(Ljava/lang/Object;)Z", (targetObject, args, context) -> targetObject.as(HashMap.class).containsKey(args.get(0).value()));
            put("isEmpty()Z", (targetObject, args, context) -> targetObject.as(HashMap.class).isEmpty()); 
        }});
        put("java/util/HashSet", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>()V", (targetObject, args, context) -> {
                expect(targetObject, "java/util/HashSet");
                targetObject.initialize(new HashSet<>());
                return null;
            });
        }});
        put("java/util/LinkedList", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>()V", (targetObject, args, context) -> {
                expect(targetObject, "java/util/LinkedList");
                targetObject.initialize(new LinkedList<>());
                return null;
            });
        }});
        put("java/util/Map", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{ 
        	put("containsKey(Ljava/lang/Object;)Z", (targetObject, args, context) -> targetObject.as(Map.class).containsKey(args.get(0).value()));
        	put("put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(Map.class).put(args.get(0).value(), args.get(1).value()));
        	put("get(Ljava/lang/Object;)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(Map.class).get(args.get(0).value()));
        }});
        put("java/util/LinkedHashMap", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{ 
            put("<init>()V", (targetObject, args, context) -> { 
                expect(targetObject, "java/util/LinkedHashMap"); 
                targetObject.initialize(new LinkedHashMap<>()); 
                return null; 
            }); 
            put("<init>(Ljava/util/Map;)V", (targetObject, args, context) -> { 
                expect(targetObject, "java/util/LinkedHashMap"); 
                targetObject.initialize(new LinkedHashMap<>(args.get(0).as(Map.class))); 
                return null; 
            }); 
            put("<init>(IFZ)V", (targetObject, args, context) -> { 
                expect(targetObject, "java/util/LinkedHashMap"); 
                JavaValue accessOrder = args.get(2); 
                targetObject.initialize(new LinkedHashMap<>(args.get(0).intValue(), args.get(1).floatValue(), accessOrder instanceof JavaBoolean ? accessOrder.booleanValue() : accessOrder.intValue() == 1)); 
                return null; 
            }); 
            put("get(Ljava/lang/Object;)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(LinkedHashMap.class).get(args.get(0).value())); 
            put("put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(LinkedHashMap.class).put(args.get(0).value(), args.get(1).value())); 
            put("isEmpty()Z", (targetObject, args, context) -> targetObject.as(LinkedHashMap.class).isEmpty()); 
            put("entrySet()Ljava/util/Set;", (targetObject, args, context) -> targetObject.as(LinkedHashMap.class).entrySet()); 
            put("remove(Ljava/lang/Object;)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(LinkedHashMap.class).remove(args.get(0).value())); 
        }}); 
        put("java/lang/Math", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("abs(J)J", (targetObject, args, context) -> Math.abs(args.get(0).longValue()));
        }});
        put("java/math/BigInteger", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("<init>(Ljava/lang/String;I)V", (targetObject, args, context) -> {
                expect(targetObject, "java/math/BigInteger");
                targetObject.initialize(new BigInteger(args.get(0).as(String.class), args.get(1).intValue()));
                return null;
            });
            put("add(Ljava/math/BigInteger;)Ljava/math/BigInteger;", (targetObject, args, context) -> targetObject.as(BigInteger.class).add(args.get(0).as(BigInteger.class)));
            put("xor(Ljava/math/BigInteger;)Ljava/math/BigInteger;", (targetObject, args, context) -> targetObject.as(BigInteger.class).xor(args.get(0).as(BigInteger.class)));
            put("modPow(Ljava/math/BigInteger;Ljava/math/BigInteger;)Ljava/math/BigInteger;", (targetObject, args, context) -> targetObject.as(BigInteger.class).modPow(args.get(0).as(BigInteger.class), args.get(1).as(BigInteger.class)));
            put("intValue()I", (targetObject, args, context) -> new JavaInteger(targetObject.as(BigInteger.class).intValue()).intValue());
            put("valueOf(J)Ljava/math/BigInteger;", (targetObject, args, context) -> BigInteger.valueOf(args.get(0).longValue()));
        }});
        put("java/util/Base64", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("getDecoder()Ljava/util/Base64$Decoder;", (targetObject, args, context) -> Base64.getDecoder());
        }});
        put("java/util/Base64$Decoder", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("decode(Ljava/lang/String;)[B", (targetObject, args, context) -> targetObject.as(Base64.Decoder.class).decode(args.get(0).as(String.class)));
        	put("decode([B)[B", (targetObject, args, context) -> targetObject.as(Base64.Decoder.class).decode(args.get(0).as(byte[].class)));
        }});

        // Javax
        put("javax/xml/bind/DatatypeConverter", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("parseBase64Binary(Ljava/lang/String;)[B", (targetObject, args, context) -> DatatypeConverter.parseBase64Binary(args.get(0).as(String.class)));
            put("parseHexBinary(Ljava/lang/String;)[B", (targetObject, args, context) -> DatatypeConverter.parseHexBinary(args.get(0).as(String.class)));
        }});
        put("javax/crypto/spec/SecretKeySpec", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("<init>([BLjava/lang/String;)V", (targetObject, args, context) -> {
                expect(targetObject, "javax/crypto/spec/SecretKeySpec");
                targetObject.initialize(new SecretKeySpec(args.get(0).as(byte[].class), args.get(1).as(String.class)));
                return null;
            });
        	put("<init>([BIILjava/lang/String;)V", (targetObject, args, context) -> {
                expect(targetObject, "javax/crypto/spec/SecretKeySpec");
                targetObject.initialize(new SecretKeySpec(args.get(0).as(byte[].class), args.get(1).intValue(), args.get(2).intValue(), args.get(3).as(String.class)));
                return null;
            });
        }});
        put("javax/crypto/spec/DESKeySpec", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("<init>([B)V", (targetObject, args, context) -> {
                expect(targetObject, "javax/crypto/spec/DESKeySpec");
                targetObject.initialize(new DESKeySpec(args.get(0).as(byte[].class)));
                return null;
            });
        }});
        put("javax/crypto/spec/IvParameterSpec", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("<init>([B)V", (targetObject, args, context) -> {
                expect(targetObject, "javax/crypto/spec/IvParameterSpec");
                targetObject.initialize(new IvParameterSpec(args.get(0).as(byte[].class)));
                return null;
            });
        }});
        put("javax/crypto/Cipher", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("getInstance(Ljava/lang/String;)Ljavax/crypto/Cipher;", (targetObject, args, context) -> Cipher.getInstance(args.get(0).as(String.class)));
        	put("init(ILjava/security/Key;)V", (targetObject, args, context) -> {
        		targetObject.as(Cipher.class).init(args.get(0).intValue(), args.get(1).as(Key.class));
        		return null;
        	});
        	put("init(ILjava/security/Key;Ljava/security/spec/AlgorithmParameterSpec;)V", (targetObject, args, context) -> {
        		targetObject.as(Cipher.class).init(args.get(0).intValue(), args.get(1).as(Key.class), args.get(2).as(AlgorithmParameterSpec.class));
        		return null;
        	});
        	put("doFinal([B)[B", (targetObject, args, context) -> targetObject.as(Cipher.class).doFinal(args.get(0).as(byte[].class)));
        }});
        put("javax/crypto/SecretKeyFactory", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("getInstance(Ljava/lang/String;)Ljavax/crypto/SecretKeyFactory;", (targetObject, args, context) -> SecretKeyFactory.getInstance(args.get(0).as(String.class)));
        	put("generateSecret(Ljava/security/spec/KeySpec;)Ljavax/crypto/SecretKey;", (targetObject, args, context) -> targetObject.as(SecretKeyFactory.class).generateSecret(args.get(0).as(KeySpec.class)));
        }});

        // Sun
        put("sun/misc/SharedSecrets", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("getJavaLangAccess()Lsun/misc/JavaLangAccess;", (targetObject, args, context) -> null);
        }});
        put("sun/misc/JavaLangAccess", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
        	put("getConstantPool(Ljava/lang/Class;)Lsun/reflect/ConstantPool;", (targetObject, args, context) -> new JavaConstantPool(context.constantPools.get(args.get(0).as(JavaClass.class).getClassNode())));
        }});
        put("sun/reflect/ConstantPool", new HashMap<String, Function3<JavaValue, List<JavaValue>, Context, Object>>() {{
            put("getSize()I", (targetObject, args, context) -> targetObject.as(JavaConstantPool.class).getSize());
        }});

    }};
    //@formatter:on

    @Override
    public boolean instanceOf(JavaValue target, Type type, Context context) {
        return false;
    }

    @Override
    public Object invokeMethod(String className, String methodName, String methodDesc, JavaValue targetObject, List<JavaValue> args, Context context) {
        Map<String, Function3<JavaValue, List<JavaValue>, Context, Object>> map = functions.get(className);
        return map.get(methodName + methodDesc).applyUnchecked(targetObject, args, context);
    }

    @Override
    public boolean canInvokeMethod(String className, String methodName, String methodDesc, JavaValue targetObject, List<JavaValue> args, Context context) {
        Map<String, Function3<JavaValue, List<JavaValue>, Context, Object>> map = functions.get(className);
        return map != null && map.containsKey(methodName + methodDesc);
    }

    @Override
    public boolean canCheckInstanceOf(JavaValue target, Type type, Context context) {
        return false;
    }

    private static void initObject(Context context, String className, JavaValue object) { 
        ClassNode classNode = context.dictionary.get(className);
        if (classNode != null) {
            for (FieldNode field : classNode.fields) {
                switch (field.desc) { 
                    case "B": 
                        context.provider.setField(classNode.name, field.name, field.desc, object, (byte) 0, context);
                        break; 
                    case "S": 
                        context.provider.setField(classNode.name, field.name, field.desc, object, (short) 0, context);
                        break; 
                    case "I": 
                        context.provider.setField(classNode.name, field.name, field.desc, object, 0, context);
                        break; 
                    case "J": 
                        context.provider.setField(classNode.name, field.name, field.desc, object, 0L, context);
                        break; 
                    case "F": 
                        context.provider.setField(classNode.name, field.name, field.desc, object, 0.0, context);
                        break; 
                    case "D": 
                        context.provider.setField(classNode.name, field.name, field.desc, object, 0.0D, context);
                        break; 
                    case "C": 
                        context.provider.setField(classNode.name, field.name, field.desc, object, (char) 0, context);
                        break; 
                    case "Z": 
                        context.provider.setField(classNode.name, field.name, field.desc, object, false, context);
                        break; 
                } 
            } 
        } else { 
            throw new RuntimeException("Could not initialize class " + className); 
        } 
    } 
 

    private static void expect(JavaValue object, String type) {
        if (!object.type().equals(type)) {
            throw new IllegalArgumentException("Expected UninitializedObject[" + type + "] but got " + object.type());
        }
    }

    @Override
    public boolean checkEquality(JavaValue first, JavaValue second, Context context) {
        if (first.value() instanceof JavaClass && second.value() instanceof JavaClass) {
            return first.as(JavaClass.class).equals(second.value());
        }
        return first == second;
    }

    @Override
    public boolean canCheckEquality(JavaValue first, JavaValue second, Context context) {
        return true;
    }

    private static JavaClass[] toJavaClass(Object[] arr) {
    	if(arr == null)
    		return new JavaClass[0];
        JavaClass[] clazz = new JavaClass[arr.length];
        for (int i = 0; i < arr.length; i++) {
            clazz[i] = (JavaClass) arr[i];
        }
        return clazz;
    }

    @FunctionalInterface
    public interface Function3<T1, T2, T3, R> {

        default R applyUnchecked(T1 var1, T2 var2, T3 var3) {
            try {
                return this.apply(var1, var2, var3);
            } catch (Throwable t) {
                Utils.sneakyThrow(t);
            }
            return null;
        }

        R apply(T1 var1, T2 var2, T3 var3) throws Throwable;
    }
}