/*
 * Copyright (c) 2013-2020 Chris Newland.
 * Licensed under https://github.com/AdoptOpenJDK/jitwatch/blob/master/LICENSE-BSD
 * Instructions: https://github.com/AdoptOpenJDK/jitwatch/wiki
 */
package org.adoptopenjdk.jitwatch.test;

import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_TYPE_NAME_VOID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.adoptopenjdk.jitwatch.demo.MakeHotSpotLog;
import org.adoptopenjdk.jitwatch.model.IMetaMember;
import org.adoptopenjdk.jitwatch.model.JITDataModel;
import org.adoptopenjdk.jitwatch.model.LogParseException;
import org.adoptopenjdk.jitwatch.model.MemberSignatureParts;
import org.adoptopenjdk.jitwatch.model.MetaClass;
import org.adoptopenjdk.jitwatch.model.MetaConstructor;
import org.adoptopenjdk.jitwatch.model.MetaMethod;
import org.adoptopenjdk.jitwatch.model.MetaPackage;
import org.adoptopenjdk.jitwatch.model.bytecode.BytecodeInstruction;
import org.adoptopenjdk.jitwatch.util.ClassUtil;
import org.adoptopenjdk.jitwatch.util.ParseUtil;
import org.adoptopenjdk.jitwatch.util.StringUtil;
import org.junit.Test;

public class TestParseUtil
{
	@Test public void testSourceSignatureRegExMatcher()
	{
		// single primitive param, void return
		Method m = UnitTestUtil.getMethod("java.lang.AbstractStringBuilder", "ensureCapacity", new Class<?>[] { int.class });
		MetaMethod method = new MetaMethod(m, null);
		String sourceSig = "public void ensureCapacity(int foo)";
		Matcher matcher = Pattern.compile(method.getSourceMethodSignatureRegEx()).matcher(sourceSig);
		boolean match = matcher.find();
		assertTrue(match);

		// 2 primitive params,void return
		Method m2 = UnitTestUtil.getMethod("java.lang.AbstractStringBuilder", "setCharAt",
				new Class<?>[] { int.class, char.class });
		MetaMethod method2 = new MetaMethod(m2, null);
		String sourceSig2 = "public void setCharAt(int foo, char bar)";
		Matcher matcher2 = Pattern.compile(method2.getSourceMethodSignatureRegEx()).matcher(sourceSig2);
		boolean match2 = matcher2.find();
		assertTrue(match2);

		// Object param and return type
		Method m3 = UnitTestUtil.getMethod("java.lang.AbstractStringBuilder", "append", new Class<?>[] { java.lang.String.class });
		MetaMethod methodFQ = new MetaMethod(m3, null);
		String sourceSigFQ = "public AbstractStringBuilder append(String foo)";
		Matcher matcherFQ = Pattern.compile(methodFQ.getSourceMethodSignatureRegEx()).matcher(sourceSigFQ);
		boolean matchFQ = matcherFQ.find();
		assertTrue(matchFQ);

		// constructor with primitive params
		Constructor<?> c1 = UnitTestUtil.getConstructor("java.lang.AbstractStringBuilder", new Class<?>[] { int.class });
		MetaConstructor con1 = new MetaConstructor(c1, null);
		String sourceSigC1 = "AbstractStringBuilder(int foo)";
		Matcher matcherC1 = Pattern.compile(con1.getSourceMethodSignatureRegEx()).matcher(sourceSigC1);
		boolean matchC1 = matcherC1.find();
		assertTrue(matchC1);

		// array return type, no params
		Method m4 = UnitTestUtil.getMethod("java.lang.String", "getBytes", new Class<?>[0]);
		MetaMethod method4 = new MetaMethod(m4, null);
		String sourceSig4 = "public byte[] getBytes()";
		Matcher matcher4 = Pattern.compile(method4.getSourceMethodSignatureRegEx()).matcher(sourceSig4);
		boolean match4 = matcher4.find();
		assertTrue(match4);

		// array param and object return type
		Method m5 = UnitTestUtil.getMethod("java.lang.AbstractStringBuilder", "append", new Class<?>[] { char[].class });
		MetaMethod method5 = new MetaMethod(m5, null);
		String sourceSig5 = "public AbstractStringBuilder append(char[] foo)";
		Matcher matcher5 = Pattern.compile(method5.getSourceMethodSignatureRegEx()).matcher(sourceSig5);
		boolean match5 = matcher5.find();
		assertTrue(match5);
	}

	private String loadConvert(char[] in, int off, int len, char[] convtBuf)
	{
		return "foo";
	}

	@Test public void testRegressionLoadConvert() // space before
	// parentheses
	{
		Method m = UnitTestUtil.getMethod(getClass().getName(), "loadConvert",
				new Class<?>[] { char[].class, int.class, int.class, char[].class });
		MetaMethod method = new MetaMethod(m, null);

		String sourceSig = "private String loadConvert (char[] in, int off, int len, char[] convtBuf) {";
		Matcher matcher = Pattern.compile(method.getSourceMethodSignatureRegEx()).matcher(sourceSig);
		boolean match = matcher.find();
		assertTrue(match);
	}

	@Test public void testSignatureMatchFailWithGenerics()
	{
		// java.util.Arrays
		// public static <U,T> T[] copyOf(U[] original, int newLength, Class<?
		// extends T[]> newType) {

		Method m = UnitTestUtil.getMethod("java.util.Arrays", "copyOf", new Class<?>[] { Object[].class, int.class, Class.class });
		MetaMethod method = new MetaMethod(m, null);

		// test for failure on matching internal (type erased) representation
		// against generics signature
		String sourceSig = "public static <U,T> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {";
		Matcher matcher = Pattern.compile(method.getSourceMethodSignatureRegEx()).matcher(sourceSig);
		boolean match = matcher.find();
		assertFalse(match);
	}

	@Test public void testFindBestLineMatchForMemberSignature()
	{
		Method m = UnitTestUtil.getMethod("java.util.Arrays", "copyOf", new Class<?>[] { Object[].class, int.class, Class.class });

		MetaClass metaClass = new MetaClass(null, "java.util.arrays");
		IMetaMember member = new MetaMethod(m, metaClass);

		List<String> srcLinesList = new ArrayList<>();

		srcLinesList.add("public static <T> T[] copyOf(T[] original, int newLength) {");
		srcLinesList.add("	return (T[]) copyOf(original, newLength, original.getClass());");
		srcLinesList.add("}");
		srcLinesList.add("public static byte[] copyOf(byte[] original, int newLength) {");
		srcLinesList.add("public static short[] copyOf(short[] original, int newLength) {");
		srcLinesList.add("public static int[] copyOf(int[] original, int newLength) {");
		srcLinesList.add("public static long[] copyOf(long[] original, int newLength) {");
		srcLinesList.add("public static char[] copyOf(char[] original, int newLength) {");
		srcLinesList.add("public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {"); // best
		srcLinesList.add("T[] copy = ((Object)newType == (Object)Object[].class)");
		srcLinesList.add("? (T[]) new Object[newLength]");
		srcLinesList.add(": (T[]) Array.newInstance(newType.getComponentType(), newLength);");
		srcLinesList.add("System.arraycopy(original, 0, copy, 0,");
		srcLinesList.add("Math.min(original.length, newLength));");
		srcLinesList.add("return copy;");
		srcLinesList.add("}");
		srcLinesList.add("public static float[] copyOf(float[] original, int newLength) {");
		srcLinesList.add("public static double[] copyOf(double[] original, int newLength) {");
		srcLinesList.add("public static boolean[] copyOf(boolean[] original, int newLength) {");
		srcLinesList.add("public static <T> T[] copyOfRange(T[] original, int from, int to) {");
		srcLinesList.add("public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {");
		srcLinesList.add("public static byte[] copyOfRange(byte[] original, int from, int to) {");

		int bestMatchPos = ParseUtil.findBestLineMatchForMemberSignature(member, srcLinesList);

		assertEquals(8, bestMatchPos);
	}

	@Test public void testFindBestLineMatchForMemberSignatureBytecode()
	{
		Method m = UnitTestUtil.getMethod("java.util.Arrays", "copyOf", new Class<?>[] { Object[].class, int.class, Class.class });

		MetaClass metaClass = new MetaClass(null, "java.util.arrays");
		IMetaMember member = new MetaMethod(m, metaClass);

		List<String> srcLinesList = new ArrayList<>();

		srcLinesList.add("public static <T extends java/lang/Object> T[] copyOf(T[], int);");
		srcLinesList.add(
				"public static <T extends java/lang/Object, U extends java/lang/Object> T[] copyOf(U[], int, java.lang.Class<? extends T[]>);");
		srcLinesList.add("public static byte[] copyOf(byte[], int);");
		srcLinesList.add(
				"public static <T extends java/lang/Object, U extends java/lang/Object> T[] copyOfRange(U[], int, int, java.lang.Class<? extends T[]>);");

		int bestMatchPos = ParseUtil.findBestLineMatchForMemberSignature(member, srcLinesList);

		assertEquals(1, bestMatchPos);
	}

	@Test public void testFindBestLineMatchForMemberSignatureBytecodeRegression()
	{
		Method m = UnitTestUtil.getMethod("java.util.Arrays", "copyOf", new Class<?>[] { Object[].class, int.class });

		MetaClass metaClass = new MetaClass(null, "java.util.arrays");
		IMetaMember member = new MetaMethod(m, metaClass);

		List<String> srcLinesList = new ArrayList<>();

		srcLinesList.add(
				"public static <T extends java/lang/Object, U extends java/lang/Object> T[] copyOf(U[], int, java.lang.Class<? extends T[]>);");
		srcLinesList.add("public static byte[] copyOf(byte[], int);");
		srcLinesList.add("public static byte[] copyOf(float[], int);");
		srcLinesList.add(
				"public static <T extends java/lang/Object, U extends java/lang/Object> T[] copyOfRange(U[], int, int, java.lang.Class<? extends T[]>);");
		srcLinesList.add("public static <T extends java/lang/Object> T[] copyOf(T[], int);");

		int bestMatchPos = ParseUtil.findBestLineMatchForMemberSignature(member, srcLinesList);

		assertEquals(4, bestMatchPos);
	}

	@Test public void testMemberSignaturePartsPrimitiveParamPrimitiveReturn() throws LogParseException
	{
		MemberSignatureParts msp = MemberSignatureParts.fromLogCompilationSignature("java.lang.String charAt (I)C");

		assertEquals("java.lang.String", msp.getFullyQualifiedClassName());
		assertEquals("charAt", msp.getMemberName());
		assertEquals("char", msp.getReturnType());
		assertEquals(1, msp.getParamTypes().size());
		assertEquals("int", msp.getParamTypes().get(0));
	}

	@Test public void testMemberSignaturePartsConstructor() throws LogParseException
	{
		MemberSignatureParts msp = MemberSignatureParts.fromLogCompilationSignature("java.lang.Object <init> ()V");

		assertEquals("java.lang.Object", msp.getFullyQualifiedClassName());
		assertEquals("Object", msp.getMemberName());
		assertEquals(S_TYPE_NAME_VOID, msp.getReturnType());
		assertEquals(0, msp.getParamTypes().size());
	}

	@Test public void testMemberSignaturePartsPrimitiveParamVoidReturn() throws LogParseException
	{
		MemberSignatureParts msp = MemberSignatureParts.fromLogCompilationSignature(
				"java.lang.AbstractStringBuilder ensureCapacityInternal (I)V");

		assertEquals("java.lang.AbstractStringBuilder", msp.getFullyQualifiedClassName());
		assertEquals("ensureCapacityInternal", msp.getMemberName());
		assertEquals(S_TYPE_NAME_VOID, msp.getReturnType());
		assertEquals(1, msp.getParamTypes().size());
		assertEquals("int", msp.getParamTypes().get(0));
	}

	@Test public void testMemberSignaturePartsPrimitiveParamObjectReturn() throws LogParseException
	{
		MemberSignatureParts msp = MemberSignatureParts.fromLogCompilationSignature(
				"java.lang.AbstractStringBuilder append (Z)Ljava.lang.AbstractStringBuilder;");

		assertEquals("java.lang.AbstractStringBuilder", msp.getFullyQualifiedClassName());
		assertEquals("append", msp.getMemberName());
		assertEquals("java.lang.AbstractStringBuilder", msp.getReturnType());
		assertEquals(1, msp.getParamTypes().size());
		assertEquals("boolean", msp.getParamTypes().get(0));
	}

	@Test public void testMemberSignaturePartsMultiDimensionalArrayParamPrimitiveReturn() throws LogParseException
	{
		MemberSignatureParts msp = MemberSignatureParts.fromLogCompilationSignature(
				"com.sun.org.apache.xerces.internal.dom.DeferredDocumentImpl setChunkIndex ([[IIII)I");

		assertEquals("com.sun.org.apache.xerces.internal.dom.DeferredDocumentImpl", msp.getFullyQualifiedClassName());
		assertEquals("setChunkIndex", msp.getMemberName());
		assertEquals("int", msp.getReturnType());
		assertEquals(4, msp.getParamTypes().size());
		assertEquals("[[I", msp.getParamTypes().get(0));
		assertEquals("int", msp.getParamTypes().get(1));
		assertEquals("int", msp.getParamTypes().get(2));
		assertEquals("int", msp.getParamTypes().get(3));
	}

	@Test public void testMemberSignaturePartsClassIsArrayClone() throws LogParseException
	{
		MemberSignatureParts msp = MemberSignatureParts.fromLogCompilationSignature(
				"[Ljava.lang.String; clone ()Ljava.lang.Object;");

		assertEquals("[Ljava.lang.String;", msp.getFullyQualifiedClassName());
		assertEquals("clone", msp.getMemberName());
		assertEquals("java.lang.Object", msp.getReturnType());
		assertEquals(0, msp.getParamTypes().size());
	}

	@Test public void testMemberSignaturePartsClassHasUnderscores() throws LogParseException
	{
		MemberSignatureParts msp = MemberSignatureParts.fromLogCompilationSignature(
				"org.omg.CORBA_2_3.portable.ObjectImpl <init> ()V");

		assertEquals("org.omg.CORBA_2_3.portable.ObjectImpl", msp.getFullyQualifiedClassName());
		assertEquals("ObjectImpl", msp.getMemberName());
		assertEquals(S_TYPE_NAME_VOID, msp.getReturnType());
		assertEquals(0, msp.getParamTypes().size());
	}

	// test varargs method
	public void doSomethingWithVarArgs(String... args)
	{
		// DO NOT REMOVE, NEEDED BY UNIT TEST
	}

	// test varargs method
	public void doSomethingWithParamAndVarArgs(int foo, String... args)
	{
		// DO NOT REMOVE, NEEDED BY UNIT TEST
	}

	// test varargs method
	public void method_with_underscores()
	{
		// DO NOT REMOVE, NEEDED BY UNIT TEST
	}

	@Test public void testVarArgsInBytecodeSignatureMatches()
	{
		String coreClassWithVarArgs = getClass().getName();

		JITDataModel model = new JITDataModel();

		MetaClass metaClass = null;

		try
		{
			metaClass = model.buildAndGetMetaClass(ClassUtil.loadClassWithoutInitialising(coreClassWithVarArgs));
		}
		catch (ClassNotFoundException cnfe)
		{
			cnfe.printStackTrace();
			fail();
		}

		String bytecodeSig = "public void doSomethingWithVarArgs(java.lang.String...)";

		MemberSignatureParts msp = MemberSignatureParts.fromBytecodeSignature("org.adoptopenjdk.jitwatch.test.TestParseUtil",
				bytecodeSig);

		IMetaMember foundVarArgsMethod = metaClass.getMemberForSignature(msp);

		assertNotNull(foundVarArgsMethod);
	}

	@Test public void testMethodWithUnderscores()
	{
		String thisClass = getClass().getName();

		JITDataModel model = new JITDataModel();

		MetaClass metaClass = null;

		try
		{
			metaClass = model.buildAndGetMetaClass(ClassUtil.loadClassWithoutInitialising(thisClass));
		}
		catch (ClassNotFoundException cnfe)
		{
			cnfe.printStackTrace();
			fail();
		}

		String bytecodeSig = "public void method_with_underscores()";

		MemberSignatureParts msp = MemberSignatureParts.fromBytecodeSignature("org.adoptopenjdk.jitwatch.test.TestParseUtil",
				bytecodeSig);

		IMetaMember foundVarArgsMethod = metaClass.getMemberForSignature(msp);

		assertNotNull(foundVarArgsMethod);
	}

	@Test public void testBadParseThrowsException()
	{
		try
		{
			MemberSignatureParts.fromLogCompilationSignature("hkl sdfkghlkshdfglkh sfkldgh ksh dfg");

			fail();
		}
		catch (LogParseException lpe)
		{

		}

	}

	@Test public void testExpandParameterType()
	{
		Map<String, String> expected = new HashMap<>();

		expected.put("I", "int");
		expected.put("C", "char");
		expected.put("Z", "boolean");
		expected.put("B", "byte");
		expected.put("S", "short");
		expected.put("J", "long");
		expected.put("F", "float");
		expected.put("D", "double");
		expected.put("Ljava.lang.String;", "java.lang.String");

		for (Map.Entry<String, String> entry : expected.entrySet())
		{
			assertEquals(entry.getValue(), ParseUtil.expandParameterType(entry.getKey()));
		}

		// 1D array
		for (Map.Entry<String, String> entry : expected.entrySet())
		{
			assertEquals(entry.getValue() + "[]", ParseUtil.expandParameterType("[" + entry.getKey()));
		}

		// 2D array
		for (Map.Entry<String, String> entry : expected.entrySet())
		{
			assertEquals(entry.getValue() + "[][]", ParseUtil.expandParameterType("[[" + entry.getKey()));
		}
	}

	@Test public void testModifierRegression()
	{
		// bad use of modifier was making volatile appear in method signatures

		String className = "java.lang.StringBuilder";
		String methodName = "charAt";

		Method m = UnitTestUtil.getMethod(className, methodName, new Class<?>[] { int.class });
		MetaMethod method = new MetaMethod(m, null);

		String uqToString = method.toStringUnqualifiedMethodName(true, false);

		assertEquals(-1, uqToString.indexOf("volatile"));
	}

	public void unicodeMethodNameµµµµµ()
	{
	}

	@Test public void testNonASCIIMethod() throws NoSuchMethodException, SecurityException
	{
		String thisClassName = getClass().getName();

		MetaPackage metaPackage = new MetaPackage(StringUtil.getPackageName(thisClassName));

		MetaClass metaClass = new MetaClass(metaPackage, StringUtil.getUnqualifiedClassName(thisClassName));

		String testMethodName = "unicodeMethodNameµµµµµ";

		Method method = getClass().getDeclaredMethod(testMethodName);

		MetaMethod testMethod = new MetaMethod(method, metaClass);

		String sourceSig = "public void unicodeMethodNameµµµµµ()";

		Matcher matcher = Pattern.compile(testMethod.getSourceMethodSignatureRegEx()).matcher(sourceSig);
		boolean match = matcher.find();
		assertTrue(match);
	}

	// tests for the regex used to parse the hotspot.log method signature format
	// format is
	// class method(params)return
	// e.g.
	// java.util.ArrayList elementData (I)Ljava.lang.Object;

	@Test public void testASCIILogSignatures() throws Exception
	{
		String sig = "java.util.ArrayList elementData (I)Ljava.lang.Object;";
		String[] parts = ParseUtil.splitLogSignatureWithRegex(sig);

		assertNotNull(parts);
		assertEquals("java.util.ArrayList", parts[0]);
		assertEquals("elementData", parts[1]);
		assertEquals("I", parts[2]);
		assertEquals("Ljava.lang.Object;", parts[3]);
	}

	@Test public void testNonASCIILogSignatures() throws Exception
	{
		String sig = "org.adoptopenjdk.jitwatch.test.TestParser unicodeMethodNameµµµµµ (V)V";
		String[] parts = ParseUtil.splitLogSignatureWithRegex(sig);

		assertNotNull(parts);
		assertEquals("org.adoptopenjdk.jitwatch.test.TestParser", parts[0]);
		assertEquals("unicodeMethodNameµµµµµ", parts[1]);
		assertEquals("V", parts[2]);
		assertEquals("V", parts[3]);
	}

	@Test public void testNonASCIILogSignaturesRegression() throws Exception
	{
		String sig = "frege.compiler.gen.Util layoutXS (Lfrege.lib.PP$TDoc;)Lfrege.prelude.PreludeBase$TList;";
		String[] parts = ParseUtil.splitLogSignatureWithRegex(sig);

		assertNotNull(parts);
		assertEquals("frege.compiler.gen.Util", parts[0]);
		assertEquals("layoutXS", parts[1]);
		assertEquals("Lfrege.lib.PP$TDoc;", parts[2]);
		assertEquals("Lfrege.prelude.PreludeBase$TList;", parts[3]);
	}

	@Test public void testASCIILogSignaturesArrayWithArrayParam() throws Exception
	{
		String sig = "java.util.ComparableTimSort gallopLeft (Ljava.lang.Comparable;[Ljava.lang.Object;III)I";

		String[] parts = ParseUtil.splitLogSignatureWithRegex(sig);

		assertNotNull(parts);
		assertEquals("java.util.ComparableTimSort", parts[0]);
		assertEquals("gallopLeft", parts[1]);
		assertEquals("Ljava.lang.Comparable;[Ljava.lang.Object;III", parts[2]);
		assertEquals("I", parts[3]);
	}

	@Test public void testEclipseLogParseRegression() throws Exception
	{
		String sig = "org.eclipse.e4.ui.css.swt.engine.AbstractCSSSWTEngineImpl getElement (Ljava.lang.Object;)Lorg.w3c.dom.Element;";

		String[] parts = ParseUtil.splitLogSignatureWithRegex(sig);

		assertNotNull(parts);
		assertEquals("org.eclipse.e4.ui.css.swt.engine.AbstractCSSSWTEngineImpl", parts[0]);
		assertEquals("getElement", parts[1]);
		assertEquals("Ljava.lang.Object;", parts[2]);
		assertEquals("Lorg.w3c.dom.Element;", parts[3]);
	}

	@Test public void testParseMemberFromBytecodeInvokeCommentConstructor() throws Exception
	{
		String comment1 = "java/lang/Object.\"<init>\":()V";

		JITDataModel model = new JITDataModel();

		model.buildAndGetMetaClass(Object.class);

		BytecodeInstruction instruction = new BytecodeInstruction();
		instruction.setComment(comment1);

		IMetaMember member1 = ParseUtil.getMemberFromBytecodeComment(model, null, instruction);

		assertEquals("public java.lang.Object()", member1.toString());
	}

	@Test public void testParseMemberFromBytecodeInvokeCommentObjectReturn() throws Exception
	{
		String comment2 = "java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;";

		JITDataModel model = new JITDataModel();

		model.buildAndGetMetaClass(StringBuilder.class);

		BytecodeInstruction instruction = new BytecodeInstruction();
		instruction.setComment(comment2);

		IMetaMember member2 = ParseUtil.getMemberFromBytecodeComment(model, null, instruction);

		assertEquals("public java.lang.StringBuilder java.lang.StringBuilder.append(int)", member2.toString());
	}

	@Test public void testParseMemberFromBytecodeInvokeCommentPrimitive() throws Exception
	{
		String comment3 = "org/adoptopenjdk/jitwatch/demo/MakeHotSpotLog.chainA1:(J)J";

		JITDataModel model = new JITDataModel();

		model.buildAndGetMetaClass(MakeHotSpotLog.class);

		BytecodeInstruction instruction = new BytecodeInstruction();
		instruction.setComment(comment3);

		IMetaMember member3 = ParseUtil.getMemberFromBytecodeComment(model, null, instruction);

		assertEquals("private long org.adoptopenjdk.jitwatch.demo.MakeHotSpotLog.chainA1(long)", member3.toString());
	}

	@Test public void testLambdaSignatureRegression() throws Exception
	{
		String sig = "uk.co.foo.bar.Anonymised$$Lambda$40 applyAsInt (Ljava.lang.Object;)I";

		String[] parts = ParseUtil.splitLogSignatureWithRegex(sig);

		assertNotNull(parts);
		assertEquals("uk.co.foo.bar.Anonymised$$Lambda$40", parts[0]);
		assertEquals("applyAsInt", parts[1]);
		assertEquals("Ljava.lang.Object;", parts[2]);
		assertEquals("I", parts[3]);

		JITDataModel model = new JITDataModel();

		try
		{
			ParseUtil.findMemberWithSignature(model, sig);
			fail();
		}
		catch (LogParseException lpe)
		{
		}
	}

	@Test public void testFindClassForLogCompilationParameter() throws Exception
	{
		assertEquals(Class.forName("java.lang.String"), ParseUtil.findClassForLogCompilationParameter("java.lang.String"));

		assertEquals(Class.forName("[Ljava.lang.String;"), ParseUtil.findClassForLogCompilationParameter("java.lang.String[]"));

		assertEquals(Class.forName("[[Ljava.lang.String;"), ParseUtil.findClassForLogCompilationParameter("java.lang.String[][]"));

		assertEquals(Class.forName("[Ljava.lang.String;"), ParseUtil.findClassForLogCompilationParameter("java.lang.String..."));

		assertEquals(int.class, ParseUtil.findClassForLogCompilationParameter("int"));

		assertEquals(Class.forName("[I"), ParseUtil.findClassForLogCompilationParameter("int[]"));

		assertEquals(Class.forName("[[I"), ParseUtil.findClassForLogCompilationParameter("int[][]"));
	}

	@Test public void testFindClassForLogCompilationParameterRegressionForGenerics() throws Exception
	{
		assertEquals(Class.forName("java.util.List"), ParseUtil.findClassForLogCompilationParameter("java.util.List<?>"));
		assertEquals(Class.forName("java.util.List"), ParseUtil.findClassForLogCompilationParameter("java.util.List<T>"));
	}

	@Test public void testStripGenerics()
	{
		assertEquals("int", ParseUtil.stripGenerics("int"));
		assertEquals("java.util.List", ParseUtil.stripGenerics("java.util.List"));
		assertEquals("java.util.List", ParseUtil.stripGenerics("java.util.List<T>"));
		assertEquals("java.util.List", ParseUtil.stripGenerics("java.util.List<Class<T>>"));
		assertEquals("java.util.List", ParseUtil.stripGenerics("java.util.List<? super T>"));
		assertEquals("java.util.List[]", ParseUtil.stripGenerics("java.util.List<? super T>[]"));
		assertEquals("java.util.List[]", ParseUtil.stripGenerics("java.util.List<?>[]"));
	}

	@Test public void paramClassesMatchPrimitiveNone()
	{
		List<Class<?>> memberClassList = new ArrayList<>();
		List<Class<?>> sigclassList = new ArrayList<>();

		assertTrue(ParseUtil.paramClassesMatch(false, memberClassList, sigclassList, true));
	}

	@Test public void paramClassesMatchPrimitiveSingle()
	{
		List<Class<?>> memberClassList = new ArrayList<>();
		List<Class<?>> sigclassList = new ArrayList<>();

		memberClassList.add(int.class);
		sigclassList.add(int.class);

		assertTrue(ParseUtil.paramClassesMatch(false, memberClassList, sigclassList, true));
	}

	@Test public void paramClassesMatchPrimitiveMultiple()
	{
		List<Class<?>> memberClassList = new ArrayList<>();
		List<Class<?>> sigclassList = new ArrayList<>();

		memberClassList.add(int.class);
		memberClassList.add(float.class);
		memberClassList.add(double.class);
		memberClassList.add(long.class);

		sigclassList.add(int.class);
		sigclassList.add(float.class);
		sigclassList.add(double.class);
		sigclassList.add(long.class);

		assertTrue(ParseUtil.paramClassesMatch(false, memberClassList, sigclassList, true));
	}

	@Test public void paramClassesMatchObjectSingle()
	{
		List<Class<?>> memberClassList = new ArrayList<>();
		List<Class<?>> sigclassList = new ArrayList<>();

		memberClassList.add(java.lang.String.class);
		sigclassList.add(java.lang.String.class);

		assertTrue(ParseUtil.paramClassesMatch(false, memberClassList, sigclassList, true));
	}

	@Test public void paramClassesMatchObjectMultiple()
	{
		List<Class<?>> memberClassList = new ArrayList<>();
		List<Class<?>> sigclassList = new ArrayList<>();

		memberClassList.add(java.lang.String.class);
		memberClassList.add(java.lang.Integer.class);
		memberClassList.add(java.lang.Double.class);

		sigclassList.add(java.lang.String.class);
		sigclassList.add(java.lang.Integer.class);
		sigclassList.add(java.lang.Double.class);

		assertTrue(ParseUtil.paramClassesMatch(false, memberClassList, sigclassList, true));
	}

	@Test public void paramClassesMatchObjectAssignableFromExactMatch()
	{
		List<Class<?>> memberClassList = new ArrayList<>();
		List<Class<?>> sigclassList = new ArrayList<>();

		memberClassList.add(java.lang.Object.class);

		sigclassList.add(java.lang.String.class);

		assertFalse(ParseUtil.paramClassesMatch(false, memberClassList, sigclassList, true));
	}

	@Test public void paramClassesMatchObjectAssignableFrom()
	{
		List<Class<?>> memberClassList = new ArrayList<>();
		List<Class<?>> sigclassList = new ArrayList<>();

		memberClassList.add(java.lang.Object.class);

		sigclassList.add(java.lang.String.class);

		assertTrue(ParseUtil.paramClassesMatch(false, memberClassList, sigclassList, false));
	}

	@Test public void paramClassesMatchObjectVarArgs() throws Exception
	{
		List<Class<?>> memberClassList = new ArrayList<>();
		List<Class<?>> sigclassList = new ArrayList<>();

		memberClassList.add(Class.forName("[Ljava.lang.Object;"));

		sigclassList.add(java.lang.Object.class);
		sigclassList.add(java.lang.Object.class);
		sigclassList.add(java.lang.Object.class);

		assertTrue(ParseUtil.paramClassesMatch(true, memberClassList, sigclassList, true));
	}

	@Test public void paramClassesMatchObjectVarArgsAssignable() throws Exception
	{
		List<Class<?>> memberClassList = new ArrayList<>();
		List<Class<?>> sigclassList = new ArrayList<>();

		memberClassList.add(Class.forName("[Ljava.lang.Object;"));

		sigclassList.add(java.lang.String.class);
		sigclassList.add(java.lang.Float.class);
		sigclassList.add(java.lang.Integer.class);

		assertTrue(ParseUtil.paramClassesMatch(true, memberClassList, sigclassList, true));
	}

	@Test public void testMethodWithPolymorphicSignature() throws Exception
	{
		JITDataModel model = new JITDataModel();
		model.buildAndGetMetaClass(java.lang.invoke.MethodHandle.class);

		MemberSignatureParts msp = MemberSignatureParts.fromLogCompilationSignature(
				"java.lang.invoke.MethodHandle linkToStatic (Ljava.lang.Object;Ljava.lang.invoke.MemberName;)V");

		IMetaMember member = model.findMetaMember(msp);

		assertNotNull(member);
	}

	@Test public void testRegressionStringIndexOf() throws Exception
	{
		JITDataModel model = new JITDataModel();
		model.buildAndGetMetaClass(java.lang.String.class);

		MemberSignatureParts msp = MemberSignatureParts.fromLogCompilationSignature("java.lang.String indexOf (II)I");

		IMetaMember member = model.findMetaMember(msp);

		assertNotNull(member);
	}

	@Test public void testRegressionStringToUpper() throws Exception
	{
		JITDataModel model = new JITDataModel();
		model.buildAndGetMetaClass(java.lang.String.class);

		MemberSignatureParts mspToUpperCase = MemberSignatureParts.fromBytecodeSignature("java.lang.String",
				"  public java.lang.String toUpperCase();");

		IMetaMember memberToUpperCase = model.findMetaMember(mspToUpperCase);

		assertNotNull(memberToUpperCase);

		assertEquals(0, memberToUpperCase.getParamTypeNames().length);
	}

	@Test public void testRegressionStringToUpperLocale() throws Exception
	{
		JITDataModel model = new JITDataModel();
		model.buildAndGetMetaClass(java.lang.String.class);

		MemberSignatureParts mspToUpperCaseLocale = MemberSignatureParts.fromBytecodeSignature("java.lang.String",
				"  public java.lang.String toUpperCase(java.util.Locale);");

		IMetaMember memberToUpperCaseLocale = model.findMetaMember(mspToUpperCaseLocale);

		assertNotNull(memberToUpperCaseLocale);

		assertEquals(1, memberToUpperCaseLocale.getParamTypeNames().length);
		assertEquals("java.util.Locale", memberToUpperCaseLocale.getParamTypeNames()[0]);
	}

	@Test public void testValhallaRegressionBytecodeNameContainsCharacterNotValidAsJavaName() throws Exception
	{
		String sig = "ArrayList${0=I} ensureCapacity (I)V";

		String[] parts = ParseUtil.splitLogSignatureWithRegex(sig);

		assertNotNull(parts);
		assertEquals("ArrayList${0=I}", parts[0]);
		assertEquals("ensureCapacity", parts[1]);
		assertEquals("I", parts[2]);
		assertEquals("V", parts[3]);
	}

	@Test public void testGetClassTypesPrimitive() throws LogParseException
	{
		Class<?>[] result = ParseUtil.getClassTypes("I");

		assertEquals(1, result.length);
		assertEquals(int.class, result[0]);
	}

	@Test public void testGetClassTypesClass() throws LogParseException
	{
		Class<?>[] result = ParseUtil.getClassTypes("Ljava.lang.String;");

		assertEquals(1, result.length);
		assertEquals(java.lang.String.class, result[0]);
	}

	@Test public void testGetClassTypesPrimitiveArray() throws LogParseException
	{
		Class<?>[] result = ParseUtil.getClassTypes("[C");

		assertEquals(1, result.length);
		assertEquals(char[].class, result[0]);
	}

	@Test public void testGetClassTypesClassArray() throws LogParseException
	{
		Class<?>[] result = ParseUtil.getClassTypes("[Ljava.math.BigInteger;");

		assertEquals(1, result.length);
		assertEquals(java.math.BigInteger[].class, result[0]);
	}

	@Test public void testExpandParseDictionaryTypeNameClass() throws LogParseException
	{
		String result = ParseUtil.expandParseDictionaryTypeName("java/math/BigInteger");

		assertEquals("java.math.BigInteger", result);
	}

	@Test public void testExpandParseDictionaryTypeNamePrimitive() throws LogParseException
	{
		String result = ParseUtil.expandParseDictionaryTypeName("int");

		assertEquals("int", result);
	}

	@Test public void testExpandParseDictionaryTypeNamePrimitiveArray() throws LogParseException
	{
		String result = ParseUtil.expandParseDictionaryTypeName("[C");

		assertEquals("char[]", result);
	}

	@Test public void testMemberSignaturePartsNativeSignature() throws LogParseException
	{
		MemberSignatureParts msp = MemberSignatureParts.fromLogCompilationSignature(
				"java.lang.System arraycopy (Ljava/lang/Object;ILjava/lang/Object;II)V");

		assertEquals("java.lang.System", msp.getFullyQualifiedClassName());
		assertEquals("arraycopy", msp.getMemberName());
		assertEquals("void", msp.getReturnType());
		assertEquals(5, msp.getParamTypes().size());
		assertEquals("java.lang.Object", msp.getParamTypes().get(0));
		assertEquals("int", msp.getParamTypes().get(1));
		assertEquals("java.lang.Object", msp.getParamTypes().get(2));
		assertEquals("int", msp.getParamTypes().get(3));
		assertEquals("int", msp.getParamTypes().get(4));
	}
}
