/*
 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
 * 
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The contents of this file are subject to the terms of either the Universal Permissive License
 * v 1.0 as shown at http://oss.oracle.com/licenses/upl
 *
 * or the following license:
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other materials provided with
 * the distribution.
 * 
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
 * endorse or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package examples;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;

/**
 * A simple piece of code that generates various problematic data structures recognized by
 * JOverflow, and then goes into sleep for long enough for the user to take a heap dump. The
 * resulting heap dump is stored alongside the test source files, and is analyzed in
 * org.openjdk.jmc.joverflow.stats.VariousIssueTest.
 * <p>
 * IMPORTANT: a heap dump for this class should be generated by the JVM running in 32-bit mode.
 * Numbers in the test are based on pointer size etc. in this mode, where we know their exact values
 * and don't have to care about different object header size on different VMs, compressed
 * references, etc.
 */
@SuppressWarnings({"rawtypes", "unchecked", "unused"})
public class VariousIssues {

	public static final int SPARSE_MAP1_CAPACITY = 20000;
	public static final int BAD_WEAK_MAP_CAPACITY = 22000;
	public static final int SPARSE_MAP1_LOAD_FACTOR = 10; // We fill 1/10 of table slots
	public static final int SPARSE_MAP1_ELEMENTS = SPARSE_MAP1_CAPACITY / SPARSE_MAP1_LOAD_FACTOR;

	public static final int EMPTY_UNUSED_CHM_INSTANCES = 10;
	public static final int EMPTY_USED_CHM_INSTANCES = 15;
	public static final int EMPTY_CHM_CAPACITY = 1000;

	public static final int BAR_ARR_OUTER_DIM = 200;
	public static final int BAR_ARR_INNER_DIM = 20;

	public static final int LZT_CHAR_ARRAY_SIZE = 1000;
	public static final int LZT_CHAR_ARRAY_WORK_SIZE = 100;
	public static final int LZT_INT_ARRAY_SIZE = 500;
	public static final int LZT_INT_ARRAY_WORK_SIZE = 100;
	public static final int LZT_LONG_ARRAY_SIZE = 400;
	public static final int LZT_LONG_ARRAY_WORK_SIZE = 199;
	public static final int UNUSED_HI_BYTES_CHAR_ARRAY_SIZE = 2000;
	public static final int UNUSED_HI_BYTES_INT_ARRAY_SIZE = 1000;
	public static final int UNUSED_HI_BYTES_LONG_ARRAY_SIZE = 400;

	public static final int DATA_CLASS_1_INSTANCES = 2000;
	public static final int NULL_CLASS_INSTANCES = 900;
	public static final int NOT_NULL_CLASS_INSTANCES = 100;

	public static final int BAD_WEAK_MAP_ELEMENTS = BAD_WEAK_MAP_CAPACITY / SPARSE_MAP1_LOAD_FACTOR;
	public static final int GOOD_WEAK_MAP_ELEMENTS = 1000;

	public final HashMap<StableHashCodeClass, EmptyInstanceClass> sparseMap1;

	public final ConcurrentHashMap<String, String>[] emptyUnusedConcHashMaps;
	public final ConcurrentHashMap<String, String>[] emptyUsedConcHashMaps;

	public final ObjectSubClass[][] barArray1;
	public final ArrayList<ArrayList<ObjectSubClass>> barArrayList;

	public final char[] lztCharArray;
	public final int[] lztIntArray;
	public final long[] lztLongArray;

	public final char[] uhbCharArray;
	public final int[] uhbIntArray;
	public final long[] uhbLongArray;

	public final DataClass1 dataClass1Instances[];
	public final AlmostAlwaysAllNullClass nullClassInstances[];

	// WeakHashMap with references back from values to keys - bad.
	// This map is also sparse.
	public final WeakHashMap<StableHashCodeClass, RefBackToKey> weakHashMapWithBackRefs;
	// Same as above, but not bad, because values are WeakReferences themselves.
	// So stale entries are treated properly.
	public final WeakHashMap goodBackRefWeakHashMap;

	public static void main(String args[]) {
		// Generate all the problematic data
		VariousIssues v = new VariousIssues();

		ExampleUtils.printPidAndSleep("various-issues.hprof");
	}

	public VariousIssues() {
		sparseMap1 = new HashMap<>(SPARSE_MAP1_CAPACITY);
		for (int i = 0; i < SPARSE_MAP1_ELEMENTS; i++) {
			EmptyInstanceClass ei = new EmptyInstanceClass();
			sparseMap1.put(new StableHashCodeClass(i), ei);
		}

		emptyUnusedConcHashMaps = new ConcurrentHashMap[EMPTY_UNUSED_CHM_INSTANCES];
		for (int i = 0; i < EMPTY_UNUSED_CHM_INSTANCES; i++) {
			emptyUnusedConcHashMaps[i] = new ConcurrentHashMap<>(EMPTY_CHM_CAPACITY);
		}

		emptyUsedConcHashMaps = new ConcurrentHashMap[EMPTY_USED_CHM_INSTANCES];
		for (int i = 0; i < EMPTY_USED_CHM_INSTANCES; i++) {
			ConcurrentHashMap<String, String> chm = new ConcurrentHashMap<>(EMPTY_CHM_CAPACITY);
			emptyUsedConcHashMaps[i] = chm;
			chm.put("Foo", "Bar");
			chm.remove("Foo");
		}

		barArray1 = new ObjectSubClass[BAR_ARR_OUTER_DIM][];
		for (int i = 0; i < BAR_ARR_OUTER_DIM; i++) {
			barArray1[i] = new ObjectSubClass[BAR_ARR_INNER_DIM];
			for (int j = 0; j < BAR_ARR_INNER_DIM; j++) {
				barArray1[i][j] = new ObjectSubClass();
			}
		}
		barArrayList = new ArrayList<>(BAR_ARR_OUTER_DIM);
		for (int i = 0; i < BAR_ARR_OUTER_DIM; i++) {
			ArrayList<ObjectSubClass> innerList = new ArrayList<>(BAR_ARR_INNER_DIM);
			barArrayList.add(innerList);
			for (int j = 0; j < BAR_ARR_INNER_DIM; j++) {
				innerList.add(new ObjectSubClass());
			}
		}

		lztCharArray = new char[LZT_CHAR_ARRAY_SIZE];
		for (int i = 0; i < LZT_CHAR_ARRAY_WORK_SIZE; i++) {
			lztCharArray[i] = 'A';
		}
		lztIntArray = new int[LZT_INT_ARRAY_SIZE];
		lztIntArray[0] = 12356789;
		lztIntArray[LZT_INT_ARRAY_WORK_SIZE - 1] = 1234567;
		lztLongArray = new long[LZT_LONG_ARRAY_SIZE];
		for (int i = 0; i < LZT_LONG_ARRAY_WORK_SIZE; i++) {
			lztLongArray[i] = ((long) Integer.MAX_VALUE) + i; // To avoid additional "unused high bytes"
		}

		uhbCharArray = new char[UNUSED_HI_BYTES_CHAR_ARRAY_SIZE];
		for (int i = 0; i < UNUSED_HI_BYTES_CHAR_ARRAY_SIZE; i++) {
			uhbCharArray[i] = (char) (i % 128);
		}
		uhbIntArray = new int[UNUSED_HI_BYTES_INT_ARRAY_SIZE];
		for (int i = 0; i < UNUSED_HI_BYTES_INT_ARRAY_SIZE; i++) {
			uhbIntArray[i] = Short.MAX_VALUE - i; // Make sure we underutilize 2 bytes
		}
		uhbLongArray = new long[UNUSED_HI_BYTES_LONG_ARRAY_SIZE];
		for (int i = 0; i < UNUSED_HI_BYTES_LONG_ARRAY_SIZE; i++) {
			uhbLongArray[i] = Short.MAX_VALUE - i; // Here we underutilize 6 bytes
		}

		dataClass1Instances = new DataClass1[DATA_CLASS_1_INSTANCES];
		for (int i = 0; i < DATA_CLASS_1_INSTANCES; i++) {
			dataClass1Instances[i] = new DataClass1(i);
		}

		nullClassInstances = new AlmostAlwaysAllNullClass[NULL_CLASS_INSTANCES + NOT_NULL_CLASS_INSTANCES];
		for (int i = 0; i < NULL_CLASS_INSTANCES + NOT_NULL_CLASS_INSTANCES; i++) {
			if (i < NULL_CLASS_INSTANCES) {
				nullClassInstances[i] = new AlmostAlwaysAllNullClass(null, 0);
			} else {
				nullClassInstances[i] = new AlmostAlwaysAllNullClass(Integer.toString(i), i + 1);
			}
		}

		weakHashMapWithBackRefs = new WeakHashMap<>(BAD_WEAK_MAP_CAPACITY);
		for (int i = 0; i < BAD_WEAK_MAP_ELEMENTS; i++) {
			StableHashCodeClass sc = new StableHashCodeClass(i);
			RefBackToKey rb = new RefBackToKey(sc, i, Integer.toString(i));
			weakHashMapWithBackRefs.put(sc, rb);
		}
		// Let's put an additional null key into this map, to make sure our code can handle it
		weakHashMapWithBackRefs.put(null, new RefBackToKey(null, 0, null));

		goodBackRefWeakHashMap = new WeakHashMap();
		for (int i = 0; i < GOOD_WEAK_MAP_ELEMENTS; i++) {
			String s = Integer.toString(i);
			goodBackRefWeakHashMap.put(s, new WeakReference<>(s));
		}
		// Let's put an additional entry into this map - the only "bad" one
		String s1 = Integer.toString(1111);
		goodBackRefWeakHashMap.put(s1, s1);
		System.gc(); // This should purge all stale entries, i.e. all entries but one
		ExampleUtils.sleep(1);
		System.gc();

		if (goodBackRefWeakHashMap.size() > 1) {
			System.err.println("Unexpected size for goodBackRefWeakHashMap: " + goodBackRefWeakHashMap.size());
			System.exit(-1);
		}

		// Now put elements into this map again. This will be used to verify that our
		// detector correctly recognizes such WeakHashMaps as harmless
		for (int i = 0; i < GOOD_WEAK_MAP_ELEMENTS; i++) {
			goodBackRefWeakHashMap.put(dataClass1Instances[i], new WeakReference<>(dataClass1Instances[i]));
		}
	}

	/**
	 * Instances of this class are used only for checking object histogram accuracy. Data structures
	 * may use pointers to existing instances of this class for their own purposes.
	 */
	public static class DataClass1 {
		private Object alwaysNull; // We use this to check accuracy of always-null stats
		private int i;
		private String s;

		DataClass1(int i) {
			this.i = i;
			s = "Foo";
		}
	}

	/**
	 * Instances of this class have no fields. We use it to check accuracy of instance size
	 * calculation, and also of our all-null instance checking.
	 */
	public static class EmptyInstanceClass {
	}

	/**
	 * We use instances of this class where we would otherwise use Object for simplicity, but where
	 * we need to easily distinguish between Object[] and other types that can be allocated by JDK,
	 * and our own classes.
	 */
	public static class ObjectSubClass {
	}

	/**
	 * Most instances of this class have all fields equal to null. Used to check how our all-null
	 * instance checking works.
	 */
	public static class AlmostAlwaysAllNullClass {
		private final String s;
		private final int i;

		AlmostAlwaysAllNullClass(String s, int i) {
			this.s = s;
			this.i = i;
		}
	}

	/**
	 * Class with our own stable hashCode() implementation, that won't change in different JVMs or
	 * due to other random factors. Guarantees that a HashMap instance that uses it for keys always
	 * has the same layout.
	 */
	public static class StableHashCodeClass {
		private final int i;

		StableHashCodeClass(int i) {
			this.i = i;
		}

		@Override
		public int hashCode() {
			return i;
		}

		@Override
		public boolean equals(Object other) {
			if (!(other instanceof StableHashCodeClass)) {
				return false;
			}

			StableHashCodeClass so = (StableHashCodeClass) other;
			return so.i == i;
		}
	}

	/**
	 * We put instances of this class into a WeakHashMap, where keys are the same sc instances as
	 * assigned to the data field of this class. Used to check our detector of this kind of
	 * pathology.
	 */
	public static class RefBackToKey {
		private final StableHashCodeClass sc;
		private final int i;
		private final String s;

		RefBackToKey(StableHashCodeClass sc, int i, String s) {
			this.sc = sc;
			this.i = i;
			this.s = s;
		}
	}
}
