/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.debug.jdi.tests;

import static org.junit.Assert.assertNotEquals;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import com.sun.jdi.Field;
import com.sun.jdi.IntegerValue;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;

/**
 * Tests for JDI com.sun.jdi.ReferenceType
 * and JDWP Type command set.
 */
public class ReferenceTypeTest extends AbstractJDITest {

	private final List<ReferenceType> fTypes = new LinkedList<>();

	// These must match what is done in localSetUp
	private final boolean[] fSystemClassLoader = { true, true, false, false };
	private final boolean[] fHasMethods = { true, false, true, true };
	private final boolean[] fIsAbstract = { false, false, false, true };
	private final boolean[] fIsFinal = { false, true, false, false };
	private final boolean[] fIsStatic = { false, false, false, false };
	private final String[] fTypeName =
		{
			"java.lang.Object",
			"java.lang.String[]",
			"org.eclipse.debug.jdi.tests.program.MainClass",
			"org.eclipse.debug.jdi.tests.program.Printable" };
	private final int fObjectIndex = 0;
	private final int fMainClassIndex = 2;

	/**
	 * Creates a new test.
	 */
	public ReferenceTypeTest() {
		super();
	}
	/**
	 * Init the fields that are used by this test only.
	 */
	@Override
	public void localSetUp() {
		// Get all kinds of reference type
		fTypes.add(getSystemType());
		fTypes.add(getArrayType());
		fTypes.add(getMainClass());
		fTypes.add(getInterfaceType());
	}
	/**
	 * Run all tests and output to standard output.
	 * @param args
	 */
	public static void main(java.lang.String[] args) {
		new ReferenceTypeTest().runSuite(args);
	}
	/**
	 * Gets the name of the test case.
	 * @see junit.framework.TestCase#getName()
	 */
	@Override
	public String getName() {
		return "com.sun.jdi.ReferenceType";
	}
	/**
	 * Test JDI allFields().
	 */
	public void testJDIAllFields() {
		Iterator<ReferenceType> iterator = fTypes.listIterator();
		while (iterator.hasNext()) {
			ReferenceType type = iterator.next();
			Iterator<?> all = type.allFields().iterator();
			int i = 0;
			while (all.hasNext()) {
				assertTrue("1." + type.name() + "." + i++, all.next() instanceof Field);
			}
		}
	}
	/**
	 * Test JDI allMethods().
	 */
	public void testJDIAllMethods() {
		Iterator<ReferenceType> iterator = fTypes.listIterator();
		while (iterator.hasNext()) {
			ReferenceType type = iterator.next();
			Iterator<?> all = type.allMethods().iterator();
			int i = 0;
			while (all.hasNext()) {
				assertTrue("1." + type.name() + "." + i++, all.next() instanceof Method);
			}
		}
	}
	/**
	 * Test JDI classLoader() and JDWP 'Type - Get class loader'.
	 */
	public void testJDIClassLoader() {
		for (int i = 0; i < fTypes.size(); ++i) {
			ReferenceType type = fTypes.get(i);
			ObjectReference classLoader = type.classLoader();
			assertEquals("1." + i, (classLoader == null), fSystemClassLoader[i]);
		}
	}
	/**
	 * Test JDI classObject().
	 */
	public void testJDIClassObject() {
		ListIterator<ReferenceType> iterator = fTypes.listIterator();
		while (iterator.hasNext()) {
			ReferenceType type = iterator.next();
			assertNotNull(type.name(), type.classObject());
		}
	}
	/**
	 * Test JDI equals() and hashCode().
	 */
	public void testJDIEquality() {
		ReferenceType other =
			fVM.classesByName("java.lang.String").get(0);
		ListIterator<ReferenceType> iterator = fTypes.listIterator();
		while (iterator.hasNext()) {
			ReferenceType type = iterator.next();
			assertTrue("1." + type.name() + ".1", type.equals(type));
			assertFalse("1." + type.name() + ".2", type.equals(other));
			@SuppressWarnings("unlikely-arg-type")
			boolean wrongClass = type.equals(fVM);
			assertFalse("1." + type.name() + ".3", wrongClass);
			assertFalse("1." + type.name() + ".4", type.equals(new Object()));
			assertFalse("1." + type.name() + ".5", type.equals(null));
			assertNotEquals("1." + type.name() + ".6", type.hashCode(), other.hashCode());
		}
	}
	/**
	 * Test JDI failedToInitialize().
	 */
	public void testJDIFailedToInitialize() {
		ListIterator<ReferenceType> iterator = fTypes.listIterator();
		while (iterator.hasNext()) {
			ReferenceType type = iterator.next();
			assertFalse("1." + type.name(), type.failedToInitialize());
		}
	}
	/**
	 * Test JDI fieldByName(String).
	 */
	public void testJDIFieldByName() {
		// NB: This tests the class type only, it should test the others too
		ReferenceType type = fTypes.get(fMainClassIndex);
		Field field = type.fieldByName("fObject");
		assertNotNull("1." + type.name(), field);
	}
	/**
	 * Test JDI fields() and JDWP 'Type - Get Fields'.
	 */
	public void testJDIFields() {
		Iterator<ReferenceType> iterator = fTypes.listIterator();
		while (iterator.hasNext()) {
			ReferenceType type = iterator.next();
			Iterator<?> fields = type.fields().iterator();
			int i = 0;
			while (fields.hasNext()) {
				assertTrue("1." + i++ +"." + type.name(), fields.next() instanceof Field);
			}
		}
	}
	/**
	 * Test JDI getValue(Field) and JDWP 'Type - Get Fields Values'.
	 */
	public void testJDIGetValue() {
		// NB: This tests the class type only, it should test the others too
		ReferenceType type = fTypes.get(fMainClassIndex);
		Field field = type.fieldByName("fInt");
		assertNotNull("1." + type.name(), field);
		assertTrue("2." + type.name(), type.getValue(field) instanceof IntegerValue);
	}
	/**
	 * Test JDI getValues(List) and JDWP 'Type - Get Fields Values'.
	 */
	public void testJDIGetValues() {
		// NB: This tests the class type only, it should test the others too
		ReferenceType type = fTypes.get(fMainClassIndex);

		// Get field values
		List<?> fields = type.fields();
		ListIterator<?> iterator = fields.listIterator();
		List<Field> staticFields = new LinkedList<>();
		while (iterator.hasNext()) {
			Field field = (Field) iterator.next();
			if (field.isStatic()) {
				staticFields.add(field);
			}
		}
		Map<?, ?> values = type.getValues(staticFields);
		assertEquals("1." + type.name(), 24, values.size());

		// Get value of field fInt in MainClass
		Field field = staticFields.get(0);
		int i = 0;
		while (!field.name().equals("fInt")) {
			field = staticFields.get(++i);
		}

		// Ensure it is an integer value
		assertTrue("2." + type.name(), values.get(field) instanceof IntegerValue);
	}
	/**
	 * Test JDI isAbstract().
	 */
	public void testJDIIsAbstract() {
		ListIterator<ReferenceType> iterator = fTypes.listIterator();
		while (iterator.hasNext()) {
			ReferenceType type = iterator.next();
			if (type.name().equals("org.eclipse.debug.jdi.tests.program.Printable")) {
				assertTrue("1." + type.name(), type.isAbstract());
			} else {
				assertFalse("2." + type.name(), type.isAbstract());
			}
		}
	}
	/**
	 * Test JDI isFinal().
	 */
	public void testJDIIsFinal() {
		for (int i = 0; i < fTypes.size(); ++i) {
			ReferenceType type = fTypes.get(i);
			assertEquals("1." + i, type.isFinal(), fIsFinal[i]);
		}
	}
	/**
	 * Test JDI isInitialized().
	 */
	public void testJDIIsInitialized() {
		ListIterator<ReferenceType> iterator = fTypes.listIterator();
		while (iterator.hasNext()) {
			ReferenceType type = iterator.next();
			assertTrue("1." + type.name(), type.isInitialized());
		}
	}
	/**
	 * Test JDI isPrepared().
	 */
	public void testJDIIsPrepared() {
		ListIterator<ReferenceType> iterator = fTypes.listIterator();
		while (iterator.hasNext()) {
			ReferenceType type = iterator.next();
			assertTrue("1." + type.name(), type.isPrepared());
		}
	}
	/**
	 * Test JDI isStatic().
	 */
	public void testJDIIsStatic() {
		ListIterator<ReferenceType> iterator = fTypes.listIterator();
		while (iterator.hasNext()) {
			ReferenceType type = iterator.next();
			assertFalse("1." + type.name(), type.isStatic());
		}
	}
	/**
	 * Test JDI isVerified().
	 */
	public void testJDIIsVerified() {
		for (int i = 0; i < fTypes.size(); ++i) {
			if (i != fObjectIndex) {
				ReferenceType type = fTypes.get(i);
				assertTrue("1." + type.name(), type.isVerified());
			}
		}
	}
	/**
	 * Test JDI methods() and JDWP 'Type - Get Methods'.
	 */
	public void testJDIMethods() {
		for (int i = 0; i < fTypes.size(); ++i) {
			ReferenceType type = fTypes.get(i);
			List<?> methods = type.methods();
			assertNotEquals("" + i, methods.isEmpty(), fHasMethods[i]);
		}
	}
	/**
	 * Test JDI methodsByName(String) and methodsByName(String, String).
	 */
	public void testJDIMethodsByName() {
		Iterator<ReferenceType> iterator = fTypes.listIterator();
		while (iterator.hasNext()) {
			ReferenceType type = iterator.next();

			// methodsByName(String)
			Iterator<?> methods = type.methodsByName("run").iterator();
			while (methods.hasNext()) {
				assertTrue("1." + type.name(), methods.next() instanceof Method);
			}
			assertEquals("2", 0, type.methodsByName("fraz").size());

			// methodsByName(String, String)
			methods = type.methodsByName("run", "()V").iterator();
			while (methods.hasNext()) {
				assertTrue("3." + type.name(), methods.next() instanceof Method);
			}
			assertEquals("4", 0, type.methodsByName("fraz", "()Z").size());
		}
	}
	/**
	 * Test JDI isAbstract(), isFinal() and isStatic()
	 * and JDWP 'Type - Get modifiers'.
	 */
	public void testJDIModifiers() {
		for (int i = 0; i < fTypes.size(); ++i) {
			ReferenceType type = fTypes.get(i);
			if (i != 2) {
				// i == 2 corresponds to an ArrayType, isAbstract() is undefined
				assertEquals("1." + i, type.isAbstract(), fIsAbstract[i]);
			}
			assertEquals("2." + i, type.isFinal(), fIsFinal[i]);
			assertEquals("3." + i, type.isStatic(), fIsStatic[i]);
		}
	}
	/**
	 * Test JDI name() and JDWP 'Type - Get signature'.
	 */
	public void testJDIName() {
		for (int i = 0; i < fTypes.size(); ++i) {
			ReferenceType type = fTypes.get(i);
			assertEquals("" + i, type.name(), fTypeName[i]);
		}
	}
	/**
	 * Test JDI nestedTypes().
	 */
	public void testJDINestedTypes() {
		// NB: This tests the class type only, it should test the others too
		ReferenceType type = getClass("org.eclipse.debug.jdi.tests.program.OtherClass");
		assertNotNull("1." + type.name(), type);
		List<?> nestedTypes = type.nestedTypes();
		assertEquals("2." + type.name(), 1, nestedTypes.size());
		assertTrue("3." + type.name(), nestedTypes.get(0) instanceof ReferenceType);
	}
	/**
	 * Test JDI visibleFields().
	 */
	public void testJDIVisibleFields() {
		Iterator<ReferenceType> iterator = fTypes.listIterator();
		while (iterator.hasNext()) {
			ReferenceType type = iterator.next();
			List<?> all = type.allFields();
			Iterator<?> visible = type.visibleFields().iterator();
			while (visible.hasNext()) {
				Field next = (Field) visible.next();
				assertTrue("1." + type.name() + "." + next.name(), all.contains(next));
			}
		}
	}
	/**
	 * Test JDI visibleMethods().
	 */
	public void testJDIVisibleMethods() {
		Iterator<ReferenceType> iterator = fTypes.listIterator();
		while (iterator.hasNext()) {
			ReferenceType type = iterator.next();
			List<?> all = type.allMethods();
			Iterator<?> visible = type.visibleMethods().iterator();
			while (visible.hasNext()) {
				Method next = (Method) visible.next();
				assertTrue("1." + type.name() + "." + next.name(), all.contains(next));
			}
		}
	}
}
