/*******************************************************************************
 * Copyright (c) 2006, 2010 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
 *******************************************************************************/
/*
 * Here we focus on various aspects of the runtime behavior of the generated
 * code.
 */
package org.eclipse.jdt.core.tests.compiler.regression;

import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;

import junit.framework.Test;

@SuppressWarnings({ "rawtypes" })
public class RuntimeTests extends AbstractRegressionTest {

public RuntimeTests(String name) {
	super(name);
}

// Static initializer to specify tests subset using TESTS_* static variables
// All specified tests which does not belong to the class are skipped...
// Only the highest compliance level is run; add the VM argument
// -Dcompliance=1.4 (for example) to lower it if needed
static {
//		TESTS_NAMES = new String[] { "test0001" };
//	 	TESTS_NUMBERS = new int[] { 1 };
//		TESTS_RANGE = new int[] { 1, -1 };
}

public static Test suite() {
	return buildAllCompliancesTestSuite(testClass());
}

public static Class testClass() {
	return RuntimeTests.class;
}

// decided not to keep this active because of negative effects on the test
// series (the OOME potentially causing grief to others)
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=217078
// memory exhaustion - try to allocate too big an instance
public void _test0001_memory_exhaustion() {
	runTest(
		new String[] { /* testFiles */
			"X.java",
			"public class X {\n" +
			"  public static void main(String args[]) {\n" +
			"    try {" +
			"      Y y = new Y(Integer.MAX_VALUE);\n" +
			"    }" +
			"    catch (OutOfMemoryError e) {\n" +
			"      System.out.println(\"SUCCESS\");\n" +
			"      return;\n" +
			"    }\n" +
			"    System.out.println(\"FAILURE\");\n" +
			"  }\n" +
			"}\n" +
			"class Y {\n" +
			"  long storage[];\n" +
			"  Y(int itemsNb) {\n" +
			"    storage = new long[itemsNb];\n" +
			"  }\n" +
			"}\n"},
		false /* expectingCompilerErrors */,
		"" /* expectedCompilerLog */,
		"SUCCESS" /* expectedOutputString */,
		null /* expectedErrorString - skip this because some JREs emit additional info to stderr in case of exception */,
		false /* forceExecution */,
		null /* classLib */,
		true /* shouldFlushOutputDirectory */,
		null /* vmArguments */,
		null /* customOptions */,
		null /* clientRequestor */,
		true /* skipJavac */);
}

// synchronization - concurrent access to a resource with explicit and
// implicit locks
public void test0500_synchronization() {
	this.runConformTest(
		new String[] {
			"X.java",
			"public class X {\n" +
			"static public void main (String args[]) {\n" +
			"  new Lock().implicitLock();\n" +
			"}\n" +
			"}\n" +
			"class Lock extends Thread {\n" +
			"  byte step = 0;\n" +
			"  void logStep(String start) {\n" +
			"    System.out.println(start + \" \" + this.step); //$NON-NLS-1$\n" +
			"  }\n" +
			"  public void run() {\n" +
			"    for (int i = 1; i < 3; i++) {\n" +
			"      logStep(\"explicit lock\"); //$NON-NLS-1$\n" +
			"      synchronized (this) {\n" +
			"        this.step++;\n" +
			"        notify();\n" +
			"        while(this.step < 2 * i) {\n" +
			"          try {\n" +
			"            wait();\n" +
			"          } catch (InterruptedException e) {\n" +
			"            System.out.println(\"EXCEPTION\"); //$NON-NLS-1$\n" +
			"          }\n" +
			"        }\n" +
			"      }\n" +
			"    }\n" +
			"  }\n" +
			"  synchronized void implicitLock() {\n" +
			"      this.start();\n" +
			"      for (int i = 0; i < 2; i++) {\n" +
			"        while (this.step < 1 + i * 2) {\n" +
			"          try {\n" +
			"            wait();\n" +
			"          } catch (InterruptedException e) {\n" +
			"            System.out.println(\"EXCEPTION\"); //$NON-NLS-1$\n" +
			"          }\n" +
			"        }\n" +
			"        logStep(\"implicit lock\"); //$NON-NLS-1$\n" +
			"        this.step++;\n" +
			"        notify();\n" +
			"      }\n" +
			"      return;\n" +
			"  }\n" +
			"}\n"},
		"explicit lock 0\n" +
		"implicit lock 1\n" +
		"explicit lock 2\n" +
		"implicit lock 3"
	);
}

// https://bugs.eclipse.org/bugs/show_bug.cgi?id=126712
// reflection - access to a public method of a package visible
// class through a public extending class
public void test0600_reflection() {
	runConformTest(
		true,
		new String[] {
			"X.java",
			"import java.lang.reflect.*;\n" +
			"import p.*;\n" +
			"public class X {\n" +
			"static public void main (String args[]) {\n" +
			"  Y y = new Y();\n" +
			"  try {\n" +
			"    Method foo = Y.class.getMethod(\"foo\", (Class []) null);\n" +
			"    y.foo();\n" +
			"    foo.invoke(y, (Object []) null);\n" +
			"  } catch (NoSuchMethodException e) {\n" +
			"      //ignore\n" +
			"  } catch (InvocationTargetException e) {\n" +
			"      //ignore\n" +
			"  } catch (IllegalAccessException e) {\n" +
			"    System.out.print(\"FAILURE: IllegalAccessException\");\n" +
			"  }\n" +
			"}\n" +
			"}",
			"p/Y.java",
			"package p;\n" +
			"public class Y extends Z {\n" +
			"  /* empty */\n" +
			"}\n",
			"p/Z.java",
			"package p;\n" +
			"class Z {\n" +
			"  public void foo() {\n" +
			"  System.out.println(\"SUCCESS\"); //$NON-NLS-1$\n" +
			"  }\n" +
			"}\n"},
		"",
		this.complianceLevel <= ClassFileConstants.JDK1_5 ? "SUCCESS\n" + "FAILURE: IllegalAccessException" : "SUCCESS\n" + "SUCCESS",
		"",
		JavacTestOptions.EclipseJustification.EclipseBug126712
	);
}

// https://bugs.eclipse.org/bugs/show_bug.cgi?id=126712
// reflection - access to a public field of a package visible
// class through a public extending class
public void test0601_reflection() {
	this.runConformTest(
		new String[] {
			"X.java",
			"import java.lang.reflect.*;\n" +
			"import p.*;\n" +
			"public class X {\n" +
			"static public void main (String args[]) {\n" +
			"  Y y = new Y();\n" +
			"  try {\n" +
			"    Field f = Y.class.getField(\"m\");\n" +
			"    System.out.println(y.m);\n" +
			"    System.out.println(f.get(y));\n" +
			"  } catch (NoSuchFieldException e) {\n" +
			"      //ignore\n" +
			"  } catch (IllegalAccessException e) {\n" +
			"    System.out.print(\"FAILURE: IllegalAccessException\");\n" +
			"  }\n" +
			"}\n" +
			"}",
			"p/Y.java",
			"package p;\n" +
			"public class Y extends Z {\n" +
			"  /* empty */\n" +
			"}\n",
			"p/Z.java",
			"package p;\n" +
			"class Z {\n" +
			"  public String m = \"SUCCESS\";\n" +
			"}\n"},
		"SUCCESS\n" +
		"FAILURE: IllegalAccessException"
	);
}

// partial rebuild - method signature changed (return type)
public void test1000_partial_rebuild() {
	this.runConformTest(
		new String[] {
			"X.java",
			"public class X {\n" +
			"static public void main(String args[]) {\n" +
			"  Z.go();\n" +
			"}\n" +
			"}\n",
			"Z.java",
			"public class Z {\n" +
			"static public void go() {\n" +
			"  int flag = 0;\n" +
			"  try {\n" +
			"    new Y().random();\n" +
			"    flag = 1;\n" +
			"  }\n" +
			"  catch (NoSuchMethodError e) {\n" +
			"    flag = 2;\n" +
			"  }\n" +
			"  catch (Throwable t) {\n" +
			"    flag = 3;\n" +
			"  }\n" +
			"  System.out.println(flag);\n" +
			"}\n" +
			"}\n",
			"Y.java",
			"public class Y {\n" +
			"java.util.Random generator = new java.util.Random();" +
			"public byte random() {\n" +
			"  return (byte) (generator.nextInt() % Byte.MAX_VALUE);\n" +
			"}\n" +
			"}\n",
			},
		"1");
	this.runConformTest(
		new String[] {
			"X.java",
			"public class X {\n" +
			"static public void main(String args[]) {\n" +
			"  Z.go();\n" +
			"}\n" +
			"}\n",
			"Y.java",
			"public class Y {\n" +
			"java.util.Random generator = new java.util.Random();" +
			"public int random() {\n" + // random now returns an int
			"  return generator.nextInt();\n" +
			"}\n" +
			"}\n",
			},
		"2",
		null,
		false, // do not purge output directory - pick old version of Z.class
		null);
}

// partial rebuild - method signature changed (parameter type)
public void test1001_partial_rebuild() {
	this.runConformTest(
		new String[] {
			"X.java",
			"public class X {\n" +
			"static public void main(String args[]) {\n" +
			"  Z.go();\n" +
			"}\n" +
			"}\n",
			"Z.java",
			"public class Z {\n" +
			"static public void go() {\n" +
			"  byte flag = 0;\n" +
			"  try {\n" +
			"    new Y().random(flag);\n" +
			"    flag = 1;\n" +
			"  }\n" +
			"  catch (NoSuchMethodError e) {\n" +
			"    flag = 2;\n" +
			"  }\n" +
			"  catch (Throwable t) {\n" +
			"    flag = 3;\n" +
			"  }\n" +
			"  System.out.println(flag);\n" +
			"}\n" +
			"}\n",
			"Y.java",
			"public class Y {\n" +
			"public int random(byte seed) {\n" +
			"  return seed++;\n" +
			"}\n" +
			"}\n",
			},
		"1");
	this.runConformTest(
		new String[] {
			"X.java",
			"public class X {\n" +
			"static public void main(String args[]) {\n" +
			"  Z.go();\n" +
			"}\n" +
			"}\n",
			"Y.java",
			"public class Y {\n" +
			"public int random(int seed) {\n" + // seed now of type int
			"  return seed++;\n" +
			"}\n" +
			"}\n",
			},
		"2",
		null,
		false, // do not purge output directory - pick old version of Z.class
		null);
}

// partial rebuild - method signature changed (visibility)
public void test1002_partial_rebuild() {
	this.runConformTest(
		new String[] {
			"X.java",
			"public class X {\n" +
			"static public void main(String args[]) {\n" +
			"  new Z().go();\n" +
			"}\n" +
			"}\n",
			"Z.java",
			"public class Z extends p.Y {\n" +
			"  class ZInner extends YInner {\n" +
			"    // empty\n" +
			"  }\n" +
			"public void go() {\n" +
			"  byte flag = 0;\n" +
			"  try {\n" +
			"    new ZInner().foo();\n" +
			"    flag = 1;\n" +
			"  }\n" +
			"  catch (IllegalAccessError e) {\n" +
			"    flag = 2;\n" +
			"  }\n" +
			"  catch (Throwable t) {\n" +
			"    flag = 3;\n" +
			"  }\n" +
			"  System.out.println(flag);\n" +
			"}\n" +
			"}\n",
			"p/Y.java",
			"package p;\n" +
			"public class Y {\n" +
			"  public class YInner {\n" +
			"    public void foo() {\n" +
			"      return;\n" +
			"    }\n" +
			"  }\n" +
			"}\n",
			},
		"1");
	this.runConformTest(
		false,
		new String[] {
			"X.java",
			"public class X {\n" +
			"static public void main(String args[]) {\n" +
			"  new Z().go();\n" +
			"}\n" +
			"}\n",
			"p/Y.java",
			"package p;\n" +
			"public class Y {\n" +
			"  public class YInner {\n" +
			"    void foo() {\n" + // now foo no more visible (package only)
			"      return;\n" +
			"    }\n" +
			"  }\n" +
			"}\n",
			},
		"",
		"2",
		"",
		JavacTestOptions.JavacHasABug.JavacBugFixed_6_10);
}

// partial rebuild - method signature changed (visibility)
public void test1003_partial_rebuild() {
	this.runConformTest(
		new String[] {
			"X.java",
			"public class X {\n" +
			"static public void main(String args[]) {\n" +
			"  new Z().go();\n" +
			"}\n" +
			"}\n",
			"Z.java",
			"public class Z extends p.Y {\n" +
			"  class ZInner extends YInner {\n" +
			"    // empty\n" +
			"  }\n" +
			"public void go() {\n" +
			"  byte flag = 0;\n" +
			"  try {\n" +
			"    new ZInner().foo();\n" +
			"    flag = 1;\n" +
			"  }\n" +
			"  catch (IllegalAccessError e) {\n" +
			"    flag = 2;\n" +
			"  }\n" +
			"  catch (Throwable t) {\n" +
			"    flag = 3;\n" +
			"  }\n" +
			"  System.out.println(flag);\n" +
			"}\n" +
			"}\n",
			"p/Y.java",
			"package p;\n" +
			"public class Y {\n" +
			"  public class YInner {\n" +
			"    public void foo() {\n" +
			"      return;\n" +
			"    }\n" +
			"  }\n" +
			"}\n",
			},
		"1");
	this.runConformTest(
		false, // do not purge output directory - pick old version of Z.class
		new String[] {
			"X.java",
			"public class X {\n" +
			"static public void main(String args[]) {\n" +
			"  new Z().go();\n" +
			"}\n" +
			"}\n",
			"p/Y.java",
			"package p;\n" +
			"public class Y {\n" +
			"  public class YInner {\n" +
			"    protected void foo() {\n" +
			  // now foo no more visible (package + inheriting classes only)
			"      return;\n" +
			"    }\n" +
			"  }\n" +
			"}\n",
			},
		"",
		"2",
		"",
		JavacTestOptions.JavacHasABug.JavacBugFixed_6_10);
}

// partial rebuild - extending class now redefines extended class fields and
//                   methods
// was Compliance_1_x#test009
public void test1004_partial_rebuild() {
	this.runConformTest(
		new String[] {
			"p1/Z.java",
			"package p1; \n"+
			"public class Z {	\n" +
			"	public static void main(String[] arguments) { \n"+
			"		Y y = new Y();	\n" +
			"		System.out.print(y.field);	\n"	+
			"		System.out.print(y.staticField);	\n"	+
			"		System.out.print(y.method());	\n"	+
			"		System.out.println(y.staticMethod());	\n"	+
			"	} \n"+
			"} \n",
			"p1/X.java",
			"package p1; \n"+
			"public class X { \n"+
			"	public String field = \"X.field-\";	\n" +
			"	public static String staticField = \"X.staticField-\";	\n" +
			"	public String method(){ return \"X.method()-\";	}	\n" +
			"	public static String staticMethod(){ return \"X.staticMethod()-\";	}	\n" +
			"} \n",
			"p1/Y.java",
			"package p1; \n"+
			"public class Y extends X { \n"+
			"} \n"
		},
		"X.field-X.staticField-X.method()-X.staticMethod()-");
	String expectedOutput =
		this.complianceLevel == ClassFileConstants.JDK1_3 ?
			"X.field-X.staticField-Y.method()-X.staticMethod()-" :
			"Y.field-Y.staticField-Y.method()-Y.staticMethod()-";
	this.runConformTest(
		new String[] {
			"p1/Y.java",
			"package p1; \n"+
			"public class Y extends X { \n"+
			"	public static void main(String[] arguments) { \n"+
			"		Z.main(arguments);	\n" +
			"	}	\n" +
			"	public String field = \"Y.field-\";	\n" +
			"	public static String staticField = \"Y.staticField-\";	\n" +
			"	public String method(){ return \"Y.method()-\";	}	\n" +
			"	public static String staticMethod(){ return \"Y.staticMethod()-\";	}	\n" +
			"} \n"
		},
		expectedOutput, // expected output
		null, // use default class-path
		false, // do not flush previous output dir content
		null); // no special vm args
}

}
