<commit_before>/*
 * Copyright 2010-2012 VMware and contributors
 *
 * 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.springsource.loaded;

import java.io.BufferedInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.FieldNode;
import org.springsource.loaded.Utils.ReturnType.Kind;


// TODO debugging tests - how is the experience?  rewriting of field accesses will really 
// affect field navigation in the debugger

/**
 * Utility functions for use throughout SpringLoaded
 * 
 * @author <NAME>
 * @since 0.5.0
 */
public class Utils implements Opcodes, Constants {

	// public final static boolean assertsOn = true;
	public final static String[] NO_STRINGS = new String[0];

	private final static String encoding = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
	private final static char[] encodingChars;

	static {
		encodingChars = new char[62];
		for (int c = 0; c < 62; c++) {
			encodingChars[c] = encoding.charAt(c);
		}
	}

	/**
	 * Convert a number (base10) to base62 encoded string
	 * 
	 * @param number the number to convert
	 * @return the base 62 encoded string
	 */
	public static String encode(long number) {
		char[] output = new char[32];
		int p = 31;
		long n = number;
		while (n > 61) {
			output[p--] = encodingChars[(int) (n % 62L)];
			n = n / 62;
		}
		output[p] = encodingChars[(int) (n % 62L)];
		return new String(output, p, 32 - p);
	}

	/**
	 * Decode a base62 encoded string into a number (base10). (More expensive than encoding)
	 * 
	 * @param s the string to decode
	 * @return the number
	 */
	public static long decode(String s) {
		long n = 0;
		for (int i = 0, max = s.length(); i < max; i++) {
			n = (n * 62) + encoding.indexOf(s.charAt(i));
		}
		return n;
	}

	/**
	 * Depending on the signature of the return type, add the appropriate instructions to the method visitor.
	 * 
	 * @param mv where to visit to append the instructions
	 * @param returnType return type descriptor
	 * @param createCast whether to include CHECKCAST instructions for return type values
	 */
	public static void addCorrectReturnInstruction(MethodVisitor mv, ReturnType returnType, boolean createCast) {
		if (returnType.isPrimitive()) {
			char ch = returnType.descriptor.charAt(0);
			switch (ch) {
			case 'V': // void is treated as a special primitive
				mv.visitInsn(RETURN);
				break;
			case 'I':
			case 'Z':
			case 'S':
			case 'B':
			case 'C':
				mv.visitInsn(IRETURN);
				break;
			case 'F':
				mv.visitInsn(FRETURN);
				break;
			case 'D':
				mv.visitInsn(DRETURN);
				break;
			case 'J':
				mv.visitInsn(LRETURN);
				break;
			default:
				throw new IllegalArgumentException("Not supported for '" + ch + "'");
			}
		} else {
			// either array or reference type
			if (GlobalConfiguration.assertsMode) {
				// Must not end with a ';' unless it starts with a '['
				if (returnType.descriptor.endsWith(";") && !returnType.descriptor.startsWith("[")) {
					throw new IllegalArgumentException("Invalid signature of '" + returnType.descriptor + "'");
				}
			}
			if (createCast) {
				mv.visitTypeInsn(CHECKCAST, returnType.descriptor);
			}
			mv.visitInsn(ARETURN);
		}
	}

	/**
	 * Return the number of parameters in the descriptor. Copes with primitives and arrays and reference types.
	 * 
	 * @param methodDescriptor a method descriptor of the form (Ljava/lang/String;I[[Z)I
	 * @return number of parameters in the descriptor
	 */
	public static int getParameterCount(String methodDescriptor) {
		int pos = 1; // after the '('
		int count = 0;
		char ch;
		while ((ch = methodDescriptor.charAt(pos)) != ')') {
			// Either 'L' or '[' or primitive
			if (ch == 'L') {
				// skip to ';'
				pos = methodDescriptor.indexOf(';', pos + 1);
			} else if (ch == '[') {
				while (methodDescriptor.charAt(++pos) == '[') {
				}
				if (methodDescriptor.charAt(pos) == 'L') {
					// reference array like [[Ljava/lang/String;
					pos = methodDescriptor.indexOf(';', pos + 1);
				}
			}
			count++;
			pos++;
		}
		return count;
	}

	/**
	 * Create the set of LOAD instructions to load the method parameters. Take into account the size and type.
	 * 
	 * @param mv the method visitor to recieve the load instructions
	 * @param descriptor the complete method descriptor (eg. "(ILjava/lang/String;)V") - params and return type are skipped
	 * @param startindex the initial index in which to assume the first parameter is stored
	 */
	public static void createLoadsBasedOnDescriptor(MethodVisitor mv, String descriptor, int startindex) {
		int slot = startindex;
		int descriptorpos = 1; // start after the '('
		char ch;
		while ((ch = descriptor.charAt(descriptorpos)) != ')') {
			switch (ch) {
			case '[':
				mv.visitVarInsn(ALOAD, slot);
				slot++;
				// jump to end of array, could be [[[[I
				while (descriptor.charAt(++descriptorpos) == '[') {
				}
				if (descriptor.charAt(descriptorpos) == 'L') {
					descriptorpos = descriptor.indexOf(';', descriptorpos) + 1;
				} else {
					// Just a primitive array
					descriptorpos++;
				}
				break;
			case 'L':
				mv.visitVarInsn(ALOAD, slot);
				slot++;
				// jump to end of 'L' signature
				descriptorpos = descriptor.indexOf(';', descriptorpos) + 1;
				break;
			case 'J':
				mv.visitVarInsn(LLOAD, slot);
				slot += 2; // double slotter
				descriptorpos++;
				break;
			case 'D':
				mv.visitVarInsn(DLOAD, slot);
				slot += 2; // double slotter
				descriptorpos++;
				break;
			case 'F':
				mv.visitVarInsn(FLOAD, slot);
				descriptorpos++;
				slot++;
				break;
			case 'I':
			case 'Z':
			case 'B':
			case 'C':
			case 'S':
				mv.visitVarInsn(ILOAD, slot);
				descriptorpos++;
				slot++;
				break;
			default:
				throw new IllegalStateException("Unexpected type in descriptor: " + ch);
			}
		}
	}

	public static void insertUnboxInsnsIfNecessary(MethodVisitor mv, String type, boolean isObject) {
		if (type.length() != 1) {
			return; // unnecessary
		}
		insertUnboxInsns(mv, type.charAt(0), isObject);
	}

	public static void insertUnboxInsns(MethodVisitor mv, char ch, boolean isObject) {
		switch (ch) {
		case 'I':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
			break;
		case 'Z':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z");
			break;
		case 'B':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B");
			break;
		case 'C':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C");
			break;
		case 'D':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D");
			break;
		case 'S':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S");
			break;
		case 'F':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F");
			break;
		case 'J':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J");
			break;
		default:
			throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'");
		}
	}

	/**
	 * Return a simple sequence for the descriptor where type references are collapsed to 'O', so (IILjava/lang/String;Z) will
	 * return IIOZ.
	 * 
	 * @param descriptor method descriptor, for example (IILjava/lang/String;Z)V
	 * @return sequence where all parameters are represented by a single character - or null if no parameters
	 */
	public static String getParamSequence(String descriptor) {
		if (descriptor.charAt(1) == ')') {
			// no parameters!
			return null;
		}
		StringBuilder seq = new StringBuilder();
		int pos = 1;
		char ch;
		while ((ch = descriptor.charAt(pos)) != ')') {
			switch (ch) {
			case 'L':
				seq.append("O"); // O for Object
				pos = descriptor.indexOf(';', pos + 1);
				break;
			case '[':
				seq.append("O"); // O for Object
				while (descriptor.charAt(++pos) == '[') {
				}
				if (descriptor.charAt(pos) == 'L') {
					pos = descriptor.indexOf(';', pos + 1);
				}
				break;
			default:
				seq.append(ch);
			}
			pos++;
		}
		return seq.toString();
	}

	public static void insertBoxInsns(MethodVisitor mv, String type) {
		if (type.length() != 1) {
			return; // not necessary
		}
		insertBoxInsns(mv, type.charAt(0));
	}

	public static void insertBoxInsns(MethodVisitor mv, char ch) {
		switch (ch) {
		case 'I':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");
			break;
		case 'F':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;");
			break;
		case 'S':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;");
			break;
		case 'Z':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;");
			break;
		case 'J':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;");
			break;
		case 'D':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;");
			break;
		case 'C':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;");
			break;
		case 'B':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;");
			break;
		case 'L':
		case '[':
			// no box needed
			break;
		default:
			throw new IllegalArgumentException("Boxing should not be attempted for descriptor '" + ch + "'");
		}
	}

	// public static void dumpit(String slashedName, byte[] bytes) {
	// try {
	// File f = new File("n:/temp/sl/" +
	// slashedName.substring(slashedName.lastIndexOf("/") + 1) + ""
	// + System.currentTimeMillis() + ".class");
	// FileOutputStream fos = new FileOutputStream(f);
	// fos.write(bytes);
	// fos.close();
	// println("Written " + f.getName());
	// } catch (Exception e) {
	// e.printStackTrace();
	// }
	// }

	public static String getInterfaceName(String owner) {
		return owner + "__I";
	}

	public static void assertSlashed(String name) {
		if (name.indexOf(".") != -1) {
			throw new IllegalStateException("Expected a slashed name but was passed " + name);
		}
	}

	public static void assertDotted(String name) {
		if (name.indexOf("/") != -1) {
			throw new IllegalStateException("Expected a dotted name but was passed " + name);
		}
	}

	public static String toOpcodeString(int opcode) {
		switch (opcode) {
		case NOP: // 0
			return "NOP";
		case ACONST_NULL: // 1
			return "ACONST_NULL";
		case ICONST_M1: // 2
			return "ICONST_M1";
		case ICONST_0: // 3
			return "ICONST_0";
		case ICONST_1: // 4
			return "ICONST_1";
		case ICONST_2: // 5
			return "ICONST_2";
		case ICONST_3: // 6
			return "ICONST_3";
		case ICONST_4: // 7
			return "ICONST_4";
		case ICONST_5: // 8
			return "ICONST_5";
		case LCONST_0: // 9
			return "LCONST_0";
		case LCONST_1: // 10
			return "LCONST_1";
		case FCONST_0: // 11
			return "FCONST_0";
		case FCONST_1: // 12
			return "FCONST_1";
		case FCONST_2: // 13
			return "FCONST_2";
		case DCONST_0: // 14
			return "DCONST_0";
		case DCONST_1: // 15
			return "DCONST_1";
		case BIPUSH: // 16
			return "BIPUSH";
		case SIPUSH: // 17
			return "SIPUSH";
		case ILOAD: // 21
			return "ILOAD";
		case LLOAD: // 22
			return "LLOAD";
		case FLOAD: // 23
			return "FLOAD";
		case DLOAD: // 24
			return "DLOAD";
		case ALOAD: // 25
			return "ALOAD";
		case IALOAD: // 46
			return "IALOAD";
		case LALOAD: // 47
			return "LALOAD";
		case FALOAD: // 48
			return "FALOAD";
		case AALOAD: // 50
			return "AALOAD";
		case IASTORE: // 79
			return "IASTORE";
		case AASTORE: // 83
			return "AASTORE";
		case BASTORE: // 84
			return "BASTORE";
		case POP: // 87
			return "POP";
		case POP2: // 88
			return "POP2";
		case DUP: // 89
			return "DUP";
		case DUP_X1: // 90
			return "DUP_X1";
		case DUP_X2: // 91
			return "DUP_X2";
		case DUP2: // 92
			return "DUP2";
		case DUP2_X1: // 93
			return "DUP2_X1";
		case DUP2_X2: // 94
			return "DUP2_X2";
		case SWAP: // 95
			return "SWAP";
		case IADD: // 96
			return "IADD";
		case LSUB: // 101
			return "LSUB";
		case IMUL: // 104
			return "IMUL";
		case LMUL: // 105
			return "LMUL";
		case FMUL: // 106
			return "FMUL";
		case DMUL: // 107
			return "DMUL";
		case ISHR: // 122
			return "ISHR";
		case IAND: // 126
			return "IAND";
		case I2D: // 135
			return "I2D";
		case L2F: // 137
			return "L2F";
		case L2D: // 138
			return "L2D";
		case I2B: // 145
			return "I2B";
		case I2C: // 146
			return "I2C";
		case I2S: // 147
			return "I2S";
		case IFEQ: // 153
			return "IFEQ";
		case IFNE: // 154
			return "IFNE";
		case IFLT: // 155
			return "IFLT";
		case IFGE: // 156
			return "IFGE";
		case IFGT: // 157
			return "IFGT";
		case IFLE: // 158
			return "IFLE";
		case IF_ICMPEQ: // 159
			return "IF_ICMPEQ";
		case IF_ICMPNE: // 160
			return "IF_ICMPNE";
		case IF_ICMPLT: // 161
			return "IF_ICMPLT";
		case IF_ICMPGE: // 162
			return "IF_ICMPGE";
		case IF_ICMPGT: // 163
			return "IF_ICMPGT";
		case IF_ICMPLE: // 164
			return "IF_ICMPLE";
		case IF_ACMPEQ: // 165
			return "IF_ACMPEQ";
		case IF_ACMPNE: // 166
			return "IF_ACMPNE";
		case GOTO: // 167
			return "GOTO";
		case IRETURN: // 172
			return "IRETURN";
		case LRETURN: // 173
			return "LRETURN";
		case FRETURN: // 174
			return "FRETURN";
		case DRETURN: // 175
			return "DRETURN";
		case ARETURN: // 176
			return "ARETURN";
		case RETURN: // 177
			return "RETURN";
		case INVOKEVIRTUAL: // 182
			return "INVOKEVIRTUAL";
		case INVOKESPECIAL: // 183
			return "INVOKESPECIAL";
		case INVOKESTATIC: // 184
			return "INVOKESTATIC";
		case INVOKEINTERFACE: // 185
			return "INVOKEINTERFACE";
		case NEWARRAY: // 188
			return "NEWARRAY";
		case ANEWARRAY: // 189
			return "ANEWARRAY";
		case ARRAYLENGTH: // 190
			return "ARRAYLENGTH";
		case ATHROW: // 191
			return "ATHROW";
		case CHECKCAST: // 192
			return "CHECKCAST";
		case IFNULL: // 198
			return "IFNULL";
		case IFNONNULL: // 199
			return "IFNONNULL";
		default:
			throw new IllegalArgumentException("NYI " + opcode);
		}
	}

	/**
	 * Create a descriptor for some set of parameter types. The descriptor will be of the form "([Ljava/lang/String;)"
	 * 
	 * @param params the (possibly null) list of parameters for which to create the descriptor
	 * @return a descriptor or "()" for no parameters
	 */
	public static String toParamDescriptor(Class<?>... params) {
		if (params == null) {
			return "()";
		}
		StringBuilder s = new StringBuilder("(");
		for (Class<?> param : params) {
			appendDescriptor(param, s);
		}
		s.append(")");
		return s.toString();
	}

	/**
	 * Given a method descriptor, extract the parameter descriptor and convert into corresponding Class objects. This requires a
	 * reference to a class loader to convert type names into Class objects.
	 * 
	 * @param methodDescriptor a method descriptor (e.g (Ljava/lang/String;)I)
	 * @param classLoader a class loader that can be used to lookup types
	 * @return an array for classes representing the types in the method descriptor
	 * @throws ClassNotFoundException if there is a problem finding the Class for a particular name in the descriptor
	 */
	public static Class<?>[] toParamClasses(String methodDescriptor, ClassLoader classLoader) throws ClassNotFoundException {
		Type[] paramTypes = Type.getArgumentTypes(methodDescriptor);
		Class<?>[] paramClasses = new Class<?>[paramTypes.length];
		for (int i = 0; i < paramClasses.length; i++) {
			paramClasses[i] = toClass(paramTypes[i], classLoader);
		}
		return paramClasses;
	}

	/**
	 * Convert an asm Type into a corresponding Class object, requires a reference to a ClassLoader to be able to convert classnames
	 * to class objects.
	 * 
	 * @param type the asm Type
	 * @param classLoader a class loader that can be used to find types
	 * @return the JVM Class for the type
	 * @throws ClassNotFoundException if there is a problem finding the Class for the type
	 */
	public static Class<?> toClass(Type type, ClassLoader classLoader) throws ClassNotFoundException {
		switch (type.getSort()) {
		case Type.VOID:
			return void.class;
		case Type.BOOLEAN:
			return boolean.class;
		case Type.CHAR:
			return char.class;
		case Type.BYTE:
			return byte.class;
		case Type.SHORT:
			return short.class;
		case Type.INT:
			return int.class;
		case Type.FLOAT:
			return float.class;
		case Type.LONG:
			return long.class;
		case Type.DOUBLE:
			return double.class;
		case Type.ARRAY:
			Class<?> clazz = toClass(type.getElementType(), classLoader);
			return Array.newInstance(clazz, 0).getClass();
		default:
			// case OBJECT:
			return Class.forName(type.getClassName(), false, classLoader);
		}
	}

	/**
	 * Construct the method descriptor for a method. For example 'String bar(int)' would return '(I)Ljava/lang/String;'. If the
	 * first parameter is skipped, the leading '(' is also skipped (the caller is expect to build the right prefix).
	 * 
	 * @param method method for which the descriptor should be created
	 * @param ignoreFirstParameter whether to include the first parameter in the output descriptor
	 * @return a method descriptor
	 */
	public static String toMethodDescriptor(Method method, boolean ignoreFirstParameter) {
		Class<?>[] params = method.getParameterTypes();
		if (ignoreFirstParameter && params.length < 1) {
			throw new IllegalStateException("Cannot ignore the first parameter when there are none.  method=" + method);
		}
		StringBuilder s = new StringBuilder();
		if (!ignoreFirstParameter) {
			s.append("(");
		}
		for (int i = (ignoreFirstParameter ? 1 : 0), max = params.length; i < max; i++) {
			appendDescriptor(params[i], s);
		}
		s.append(")");
		appendDescriptor(method.getReturnType(), s);
		return s.toString();
	}

	public static void appendDescriptor(Class<?> p, StringBuilder s) {
		if (p.isArray()) {
			while (p.isArray()) {
				s.append("[");
				p = p.getComponentType();
			}
		}
		if (p.isPrimitive()) {
			if (p == Void.TYPE) {
				s.append('V');
			} else if (p == Integer.TYPE) {
				s.append('I');
			} else if (p == Boolean.TYPE) {
				s.append('Z');
			} else if (p == Character.TYPE) {
				s.append('C');
			} else if (p == Long.TYPE) {
				s.append('J');
			} else if (p == Double.TYPE) {
				s.append('D');
			} else if (p == Float.TYPE) {
				s.append('F');
			} else if (p == Byte.TYPE) {
				s.append('B');
			} else if (p == Short.TYPE) {
				s.append('S');
			}
		} else {
			s.append("L");
			s.append(p.getName().replace('.', '/'));
			s.append(";");
		}<commit_msg>guard for github issue 52<commit_after>/*
 * Copyright 2010-2012 VMware and contributors
 *
 * 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.springsource.loaded;

import java.io.BufferedInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.FieldNode;
import org.springsource.loaded.Utils.ReturnType.Kind;


// TODO debugging tests - how is the experience?  rewriting of field accesses will really 
// affect field navigation in the debugger

/**
 * Utility functions for use throughout SpringLoaded
 * 
 * @author <NAME>
 * @since 0.5.0
 */
public class Utils implements Opcodes, Constants {

	// public final static boolean assertsOn = true;
	public final static String[] NO_STRINGS = new String[0];

	private final static String encoding = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
	private final static char[] encodingChars;

	static {
		encodingChars = new char[62];
		for (int c = 0; c < 62; c++) {
			encodingChars[c] = encoding.charAt(c);
		}
	}

	/**
	 * Convert a number (base10) to base62 encoded string
	 * 
	 * @param number the number to convert
	 * @return the base 62 encoded string
	 */
	public static String encode(long number) {
		char[] output = new char[32];
		int p = 31;
		long n = number;
		while (n > 61) {
			output[p--] = encodingChars[(int) (n % 62L)];
			n = n / 62;
		}
		output[p] = encodingChars[(int) (n % 62L)];
		return new String(output, p, 32 - p);
	}

	/**
	 * Decode a base62 encoded string into a number (base10). (More expensive than encoding)
	 * 
	 * @param s the string to decode
	 * @return the number
	 */
	public static long decode(String s) {
		long n = 0;
		for (int i = 0, max = s.length(); i < max; i++) {
			n = (n * 62) + encoding.indexOf(s.charAt(i));
		}
		return n;
	}

	/**
	 * Depending on the signature of the return type, add the appropriate instructions to the method visitor.
	 * 
	 * @param mv where to visit to append the instructions
	 * @param returnType return type descriptor
	 * @param createCast whether to include CHECKCAST instructions for return type values
	 */
	public static void addCorrectReturnInstruction(MethodVisitor mv, ReturnType returnType, boolean createCast) {
		if (returnType.isPrimitive()) {
			char ch = returnType.descriptor.charAt(0);
			switch (ch) {
			case 'V': // void is treated as a special primitive
				mv.visitInsn(RETURN);
				break;
			case 'I':
			case 'Z':
			case 'S':
			case 'B':
			case 'C':
				mv.visitInsn(IRETURN);
				break;
			case 'F':
				mv.visitInsn(FRETURN);
				break;
			case 'D':
				mv.visitInsn(DRETURN);
				break;
			case 'J':
				mv.visitInsn(LRETURN);
				break;
			default:
				throw new IllegalArgumentException("Not supported for '" + ch + "'");
			}
		} else {
			// either array or reference type
			if (GlobalConfiguration.assertsMode) {
				// Must not end with a ';' unless it starts with a '['
				if (returnType.descriptor.endsWith(";") && !returnType.descriptor.startsWith("[")) {
					throw new IllegalArgumentException("Invalid signature of '" + returnType.descriptor + "'");
				}
			}
			if (createCast) {
				mv.visitTypeInsn(CHECKCAST, returnType.descriptor);
			}
			mv.visitInsn(ARETURN);
		}
	}

	/**
	 * Return the number of parameters in the descriptor. Copes with primitives and arrays and reference types.
	 * 
	 * @param methodDescriptor a method descriptor of the form (Ljava/lang/String;I[[Z)I
	 * @return number of parameters in the descriptor
	 */
	public static int getParameterCount(String methodDescriptor) {
		int pos = 1; // after the '('
		int count = 0;
		char ch;
		while ((ch = methodDescriptor.charAt(pos)) != ')') {
			// Either 'L' or '[' or primitive
			if (ch == 'L') {
				// skip to ';'
				pos = methodDescriptor.indexOf(';', pos + 1);
			} else if (ch == '[') {
				while (methodDescriptor.charAt(++pos) == '[') {
				}
				if (methodDescriptor.charAt(pos) == 'L') {
					// reference array like [[Ljava/lang/String;
					pos = methodDescriptor.indexOf(';', pos + 1);
				}
			}
			count++;
			pos++;
		}
		return count;
	}

	/**
	 * Create the set of LOAD instructions to load the method parameters. Take into account the size and type.
	 * 
	 * @param mv the method visitor to recieve the load instructions
	 * @param descriptor the complete method descriptor (eg. "(ILjava/lang/String;)V") - params and return type are skipped
	 * @param startindex the initial index in which to assume the first parameter is stored
	 */
	public static void createLoadsBasedOnDescriptor(MethodVisitor mv, String descriptor, int startindex) {
		int slot = startindex;
		int descriptorpos = 1; // start after the '('
		char ch;
		while ((ch = descriptor.charAt(descriptorpos)) != ')') {
			switch (ch) {
			case '[':
				mv.visitVarInsn(ALOAD, slot);
				slot++;
				// jump to end of array, could be [[[[I
				while (descriptor.charAt(++descriptorpos) == '[') {
				}
				if (descriptor.charAt(descriptorpos) == 'L') {
					descriptorpos = descriptor.indexOf(';', descriptorpos) + 1;
				} else {
					// Just a primitive array
					descriptorpos++;
				}
				break;
			case 'L':
				mv.visitVarInsn(ALOAD, slot);
				slot++;
				// jump to end of 'L' signature
				descriptorpos = descriptor.indexOf(';', descriptorpos) + 1;
				break;
			case 'J':
				mv.visitVarInsn(LLOAD, slot);
				slot += 2; // double slotter
				descriptorpos++;
				break;
			case 'D':
				mv.visitVarInsn(DLOAD, slot);
				slot += 2; // double slotter
				descriptorpos++;
				break;
			case 'F':
				mv.visitVarInsn(FLOAD, slot);
				descriptorpos++;
				slot++;
				break;
			case 'I':
			case 'Z':
			case 'B':
			case 'C':
			case 'S':
				mv.visitVarInsn(ILOAD, slot);
				descriptorpos++;
				slot++;
				break;
			default:
				throw new IllegalStateException("Unexpected type in descriptor: " + ch);
			}
		}
	}

	public static void insertUnboxInsnsIfNecessary(MethodVisitor mv, String type, boolean isObject) {
		if (type.length() != 1) {
			return; // unnecessary
		}
		insertUnboxInsns(mv, type.charAt(0), isObject);
	}

	public static void insertUnboxInsns(MethodVisitor mv, char ch, boolean isObject) {
		switch (ch) {
		case 'I':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
			break;
		case 'Z':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z");
			break;
		case 'B':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B");
			break;
		case 'C':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C");
			break;
		case 'D':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D");
			break;
		case 'S':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S");
			break;
		case 'F':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F");
			break;
		case 'J':
			if (isObject) {
				mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J");
			break;
		default:
			throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'");
		}
	}

	/**
	 * Return a simple sequence for the descriptor where type references are collapsed to 'O', so (IILjava/lang/String;Z) will
	 * return IIOZ.
	 * 
	 * @param descriptor method descriptor, for example (IILjava/lang/String;Z)V
	 * @return sequence where all parameters are represented by a single character - or null if no parameters
	 */
	public static String getParamSequence(String descriptor) {
		if (descriptor.charAt(1) == ')') {
			// no parameters!
			return null;
		}
		StringBuilder seq = new StringBuilder();
		int pos = 1;
		char ch;
		while ((ch = descriptor.charAt(pos)) != ')') {
			switch (ch) {
			case 'L':
				seq.append("O"); // O for Object
				pos = descriptor.indexOf(';', pos + 1);
				break;
			case '[':
				seq.append("O"); // O for Object
				while (descriptor.charAt(++pos) == '[') {
				}
				if (descriptor.charAt(pos) == 'L') {
					pos = descriptor.indexOf(';', pos + 1);
				}
				break;
			default:
				seq.append(ch);
			}
			pos++;
		}
		return seq.toString();
	}

	public static void insertBoxInsns(MethodVisitor mv, String type) {
		if (type.length() != 1) {
			return; // not necessary
		}
		insertBoxInsns(mv, type.charAt(0));
	}

	public static void insertBoxInsns(MethodVisitor mv, char ch) {
		switch (ch) {
		case 'I':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");
			break;
		case 'F':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;");
			break;
		case 'S':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;");
			break;
		case 'Z':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;");
			break;
		case 'J':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;");
			break;
		case 'D':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;");
			break;
		case 'C':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;");
			break;
		case 'B':
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;");
			break;
		case 'L':
		case '[':
			// no box needed
			break;
		default:
			throw new IllegalArgumentException("Boxing should not be attempted for descriptor '" + ch + "'");
		}
	}

	// public static void dumpit(String slashedName, byte[] bytes) {
	// try {
	// File f = new File("n:/temp/sl/" +
	// slashedName.substring(slashedName.lastIndexOf("/") + 1) + ""
	// + System.currentTimeMillis() + ".class");
	// FileOutputStream fos = new FileOutputStream(f);
	// fos.write(bytes);
	// fos.close();
	// println("Written " + f.getName());
	// } catch (Exception e) {
	// e.printStackTrace();
	// }
	// }

	public static String getInterfaceName(String owner) {
		return owner + "__I";
	}

	public static void assertSlashed(String name) {
		if (name.indexOf(".") != -1) {
			throw new IllegalStateException("Expected a slashed name but was passed " + name);
		}
	}

	public static void assertDotted(String name) {
		if (name.indexOf("/") != -1) {
			throw new IllegalStateException("Expected a dotted name but was passed " + name);
		}
	}

	public static String toOpcodeString(int opcode) {
		switch (opcode) {
		case NOP: // 0
			return "NOP";
		case ACONST_NULL: // 1
			return "ACONST_NULL";
		case ICONST_M1: // 2
			return "ICONST_M1";
		case ICONST_0: // 3
			return "ICONST_0";
		case ICONST_1: // 4
			return "ICONST_1";
		case ICONST_2: // 5
			return "ICONST_2";
		case ICONST_3: // 6
			return "ICONST_3";
		case ICONST_4: // 7
			return "ICONST_4";
		case ICONST_5: // 8
			return "ICONST_5";
		case LCONST_0: // 9
			return "LCONST_0";
		case LCONST_1: // 10
			return "LCONST_1";
		case FCONST_0: // 11
			return "FCONST_0";
		case FCONST_1: // 12
			return "FCONST_1";
		case FCONST_2: // 13
			return "FCONST_2";
		case DCONST_0: // 14
			return "DCONST_0";
		case DCONST_1: // 15
			return "DCONST_1";
		case BIPUSH: // 16
			return "BIPUSH";
		case SIPUSH: // 17
			return "SIPUSH";
		case ILOAD: // 21
			return "ILOAD";
		case LLOAD: // 22
			return "LLOAD";
		case FLOAD: // 23
			return "FLOAD";
		case DLOAD: // 24
			return "DLOAD";
		case ALOAD: // 25
			return "ALOAD";
		case IALOAD: // 46
			return "IALOAD";
		case LALOAD: // 47
			return "LALOAD";
		case FALOAD: // 48
			return "FALOAD";
		case AALOAD: // 50
			return "AALOAD";
		case IASTORE: // 79
			return "IASTORE";
		case AASTORE: // 83
			return "AASTORE";
		case BASTORE: // 84
			return "BASTORE";
		case POP: // 87
			return "POP";
		case POP2: // 88
			return "POP2";
		case DUP: // 89
			return "DUP";
		case DUP_X1: // 90
			return "DUP_X1";
		case DUP_X2: // 91
			return "DUP_X2";
		case DUP2: // 92
			return "DUP2";
		case DUP2_X1: // 93
			return "DUP2_X1";
		case DUP2_X2: // 94
			return "DUP2_X2";
		case SWAP: // 95
			return "SWAP";
		case IADD: // 96
			return "IADD";
		case LSUB: // 101
			return "LSUB";
		case IMUL: // 104
			return "IMUL";
		case LMUL: // 105
			return "LMUL";
		case FMUL: // 106
			return "FMUL";
		case DMUL: // 107
			return "DMUL";
		case ISHR: // 122
			return "ISHR";
		case IAND: // 126
			return "IAND";
		case I2D: // 135
			return "I2D";
		case L2F: // 137
			return "L2F";
		case L2D: // 138
			return "L2D";
		case I2B: // 145
			return "I2B";
		case I2C: // 146
			return "I2C";
		case I2S: // 147
			return "I2S";
		case IFEQ: // 153
			return "IFEQ";
		case IFNE: // 154
			return "IFNE";
		case IFLT: // 155
			return "IFLT";
		case IFGE: // 156
			return "IFGE";
		case IFGT: // 157
			return "IFGT";
		case IFLE: // 158
			return "IFLE";
		case IF_ICMPEQ: // 159
			return "IF_ICMPEQ";
		case IF_ICMPNE: // 160
			return "IF_ICMPNE";
		case IF_ICMPLT: // 161
			return "IF_ICMPLT";
		case IF_ICMPGE: // 162
			return "IF_ICMPGE";
		case IF_ICMPGT: // 163
			return "IF_ICMPGT";
		case IF_ICMPLE: // 164
			return "IF_ICMPLE";
		case IF_ACMPEQ: // 165
			return "IF_ACMPEQ";
		case IF_ACMPNE: // 166
			return "IF_ACMPNE";
		case GOTO: // 167
			return "GOTO";
		case IRETURN: // 172
			return "IRETURN";
		case LRETURN: // 173
			return "LRETURN";
		case FRETURN: // 174
			return "FRETURN";
		case DRETURN: // 175
			return "DRETURN";
		case ARETURN: // 176
			return "ARETURN";
		case RETURN: // 177
			return "RETURN";
		case INVOKEVIRTUAL: // 182
			return "INVOKEVIRTUAL";
		case INVOKESPECIAL: // 183
			return "INVOKESPECIAL";
		case INVOKESTATIC: // 184
			return "INVOKESTATIC";
		case INVOKEINTERFACE: // 185
			return "INVOKEINTERFACE";
		case NEWARRAY: // 188
			return "NEWARRAY";
		case ANEWARRAY: // 189
			return "ANEWARRAY";
		case ARRAYLENGTH: // 190
			return "ARRAYLENGTH";
		case ATHROW: // 191
			return "ATHROW";
		case CHECKCAST: // 192
			return "CHECKCAST";
		case IFNULL: // 198
			return "IFNULL";
		case IFNONNULL: // 199
			return "IFNONNULL";
		default:
			throw new IllegalArgumentException("NYI " + opcode);
		}
	}

	/**
	 * Create a descriptor for some set of parameter types. The descriptor will be of the form "([Ljava/lang/String;)"
	 * 
	 * @param params the (possibly null) list of parameters for which to create the descriptor
	 * @return a descriptor or "()" for no parameters
	 */
	public static String toParamDescriptor(Class<?>... params) {
		if (params == null) {
			return "()";
		}
		StringBuilder s = new StringBuilder("(");
		for (Class<?> param : params) {
			appendDescriptor(param, s);
		}
		s.append(")");
		return s.toString();
	}

	/**
	 * Given a method descriptor, extract the parameter descriptor and convert into corresponding Class objects. This requires a
	 * reference to a class loader to convert type names into Class objects.
	 * 
	 * @param methodDescriptor a method descriptor (e.g (Ljava/lang/String;)I)
	 * @param classLoader a class loader that can be used to lookup types
	 * @return an array for classes representing the types in the method descriptor
	 * @throws ClassNotFoundException if there is a problem finding the Class for a particular name in the descriptor
	 */
	public static Class<?>[] toParamClasses(String methodDescriptor, ClassLoader classLoader) throws ClassNotFoundException {
		Type[] paramTypes = Type.getArgumentTypes(methodDescriptor);
		Class<?>[] paramClasses = new Class<?>[paramTypes.length];
		for (int i = 0; i < paramClasses.length; i++) {
			paramClasses[i] = toClass(paramTypes[i], classLoader);
		}
		return paramClasses;
	}

	/**
	 * Convert an asm Type into a corresponding Class object, requires a reference to a ClassLoader to be able to convert classnames
	 * to class objects.
	 * 
	 * @param type the asm Type
	 * @param classLoader a class loader that can be used to find types
	 * @return the JVM Class for the type
	 * @throws ClassNotFoundException if there is a problem finding the Class for the type
	 */
	public static Class<?> toClass(Type type, ClassLoader classLoader) throws ClassNotFoundException {
		switch (type.getSort()) {
		case Type.VOID:
			return void.class;
		case Type.BOOLEAN:
			return boolean.class;
		case Type.CHAR:
			return char.class;
		case Type.BYTE:
			return byte.class;
		case Type.SHORT:
			return short.class;
		case Type.INT:
			return int.class;
		case Type.FLOAT:
			return float.class;
		case Type.LONG:
			return long.class;
		case Type.DOUBLE:
			return double.class;
		case Type.ARRAY:
			Class<?> clazz = toClass(type.getElementType(), classLoader);
			return Array.newInstance(clazz, 0).getClass();
		default:
			// case OBJECT:
			return Class.forName(type.getClassName(), false, classLoader);
		}
	}

	/**
	 * Construct the method descriptor for a method. For example 'String bar(int)' would return '(I)Ljava/lang/String;'. If the
	 * first parameter is skipped, the leading '(' is also skipped (the caller is expect to build the right prefix).
	 * 
	 * @param method method for which the descriptor should be created
	 * @param ignoreFirstParameter whether to include the first parameter in the output descriptor
	 * @return a method descriptor
	 */
	public static String toMethodDescriptor(Method method, boolean ignoreFirstParameter) {
		Class<?>[] params = method.getParameterTypes();
		if (ignoreFirstParameter && params.length < 1) {
			throw new IllegalStateException("Cannot ignore the first parameter when there are none.  method=" + method);
		}
		StringBuilder s = new StringBuilder();
		if (!ignoreFirstParameter) {
			s.append("(");
		}
		for (int i = (ignoreFirstParameter ? 1 : 0), max = params.length; i < max; i++) {
			appendDescriptor(params[i], s);
		}
		s.append(")");
		appendDescriptor(method.getReturnType(), s);
		return s.toString();
	}

	public static void appendDescriptor(Class<?> p, StringBuilder s) {
		if (p == null) {
			// Could do with a real working scenario that leads to this problem - see https://github.com/spring-projects/spring-loaded/issues/52
			s.append("null");
			return;
		}
		if (p.isArray()) {
			while (p.isArray()) {
				s.append("[");
				p = p.getComponentType();
			}
		}
		if (p.isPrimitive()) {
			if (p == Void.TYPE) {
				s.append('V');
			} else if (p == Integer.TYPE) {
				s.append('I');
			} else if (p == Boolean.TYPE) {
				s.append('Z');
			} else if (p == Character.TYPE) {
				s.append('C');
			} else if (p == Long.TYPE) {
				s.append('J');
			} else if (p == Double.TYPE) {
				s.append('D');
			} else if (p == Float.TYPE) {
				s.append('F');
			} else if (p == Byte.TYPE) {
				s.append('B');
			} else if (p == Short.TYPE) {
				s.append('S');
			}
		} else {
			s.append("L");
			s.append(p.getName().replace('.', '/'));
			s.append(";");
		}
{'fraction_non_alphanumeric': 0.13935812881555398, 'fraction_numerical': 0.017064260671292325, 'mean_word_length': 3.190691462036371, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 25, 'https://': 1, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6077985', 'n_tokens_mistral': 16285, 'n_tokens_neox': 13657, 'n_words': 4017}