/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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 art;

import java.util.*;
import java.util.function.*;
import java.lang.reflect.*;
import java.nio.ByteBuffer;
import dalvik.system.InMemoryDexClassLoader;

public class Test1946 {
  // Base64 encoded dex file containing the following classes. Note the class E cannot be loaded.
  // public class A {}
  // public class B {}
  // public class C {}
  // public class D {}
  // public class E extends ClassNotThere {}
  private static final byte[] TEST_CLASSES = Base64.getDecoder().decode(
      "ZGV4CjAzNQDzTO8rVDlKlz80vQF4NLYV5MjMMjHlOtRoAwAAcAAAAHhWNBIAAAAAAAAAAOACAAAO" +
      "AAAAcAAAAAgAAACoAAAAAQAAAMgAAAAAAAAAAAAAAAcAAADUAAAABQAAAAwBAAC8AQAArAEAACQC" +
      "AAAsAgAANAIAADwCAABEAgAATAIAAFQCAABZAgAAXgIAAGMCAAB0AgAAeQIAAH4CAACSAgAABgAA" +
      "AAcAAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAANAAAABwAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA" +
      "AgAAAAAAAAADAAAAAAAAAAQAAAAAAAAABQAAAAAAAAAGAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAB" +
      "AAAAAAAAAK4CAAAAAAAAAQAAAAAAAAAGAAAAAAAAAAIAAAAAAAAAuAIAAAAAAAACAAAAAAAAAAYA" +
      "AAAAAAAAAwAAAAAAAADCAgAAAAAAAAQAAAAAAAAABgAAAAAAAAAEAAAAAAAAAMwCAAAAAAAABQAA" +
      "AAAAAAADAAAAAAAAAAUAAAAAAAAA1gIAAAAAAAABAAEAAQAAAJUCAAAEAAAAcBAGAAAADgABAAEA" +
      "AQAAAJoCAAAEAAAAcBAGAAAADgABAAEAAQAAAJ8CAAAEAAAAcBAGAAAADgABAAEAAQAAAKQCAAAE" +
      "AAAAcBAGAAAADgABAAEAAQAAAKkCAAAEAAAAcBADAAAADgAGPGluaXQ+AAZBLmphdmEABkIuamF2" +
      "YQAGQy5qYXZhAAZELmphdmEABkUuamF2YQADTEE7AANMQjsAA0xDOwAPTENsYXNzTm90VGhlcmU7" +
      "AANMRDsAA0xFOwASTGphdmEvbGFuZy9PYmplY3Q7AAFWAAEABw4AAQAHDgABAAcOAAEABw4AAQAH" +
      "DgAAAAEAAICABKwDAAABAAGAgATEAwAAAQACgIAE3AMAAAEABICABPQDAAABAAWAgASMBAsAAAAA" +
      "AAAAAQAAAAAAAAABAAAADgAAAHAAAAACAAAACAAAAKgAAAADAAAAAQAAAMgAAAAFAAAABwAAANQA" +
      "AAAGAAAABQAAAAwBAAABIAAABQAAAKwBAAACIAAADgAAACQCAAADIAAABQAAAJUCAAAAIAAABQAA" +
      "AK4CAAAAEAAAAQAAAOACAAA=");
  public class TMP1 {}
  public class TMP2 {}
  public class TMP3 extends ArrayList {}

  private static void check(boolean b, Supplier<String> msg) {
    if (!b) {
      throw new Error("Test failed! " + msg.get());
    }
  }

  private static <T> void checkEq(T[] full, T[] sub, final String msg) {
    List<T> f = Arrays.asList(full);
    check(full.length == sub.length, () -> "not equal length");
    Supplier<String> msgGen =
      () -> Arrays.toString(full) + " is not same as " + Arrays.toString(sub) + ": " + msg;
    check(new HashSet<T>(Arrays.asList(full)).containsAll(Arrays.asList(sub)), msgGen);
  }

  private static <T> void checkSubset(T[] full, T[] sub, final String msg) {
    Supplier<String> msgGen =
      () -> Arrays.toString(full) + " does not contain all of " + Arrays.toString(sub) + ": " + msg;
    check(new HashSet<T>(Arrays.asList(full)).containsAll(Arrays.asList(sub)), msgGen);
  }

  public static void run() throws Exception {
    initializeTest();
    // Check a few random classes in BCP.
    checkSubset(getClassloaderDescriptors(null),
        new String[] { "Ljava/lang/String;", "Ljava/util/TreeSet;" },
        "Missing entries for null classloader.");
    // Make sure that null is the same as BootClassLoader
    checkEq(getClassloaderDescriptors(null),
        getClassloaderDescriptors(Object.class.getClassLoader()), "Object not in bcp!");
    // Check the current class loader gets expected classes.
    checkSubset(getClassloaderDescriptors(Test1946.class.getClassLoader()),
        new String[] {
          "Lart/Test1946;",
          "Lart/Test1946$TMP1;",
          "Lart/Test1946$TMP2;",
          "Lart/Test1946$TMP3;"
        },
        "Missing entries for current class classloader.");
    // Check that the result is exactly what we expect and includes classes that fail verification.
    checkEq(getClassloaderDescriptors(makeClassLoaderFrom(TEST_CLASSES,
            ClassLoader.getSystemClassLoader())),
        new String[] { "LA;", "LB;", "LC;", "LD;", "LE;" },
        "Unexpected classes in custom classloader");
    checkEq(getClassloaderDescriptors(makeClassLoaderFrom(TEST_CLASSES,
            Object.class.getClassLoader())),
        new String[] { "LA;", "LB;", "LC;", "LD;", "LE;" },
        "Unexpected classes in custom classloader");
    checkEq(getClassloaderDescriptors(makeClassLoaderFrom(TEST_CLASSES,
            Test1946.class.getClassLoader())),
        new String[] { "LA;", "LB;", "LC;", "LD;", "LE;" },
        "Unexpected classes in custom classloader");
    // Check we only get 1 copy of each descriptor.
    checkEq(getClassloaderDescriptors(makeClassLoaderFrom(Arrays.asList(TEST_CLASSES, TEST_CLASSES),
            Test1946.class.getClassLoader())),
        new String[] { "LA;", "LB;", "LC;", "LD;", "LE;" },
        "Unexpected classes in custom classloader");
    System.out.println("Passed!");
  }

  private static ClassLoader makeClassLoaderFrom(byte[] data, ClassLoader parent) throws Exception {
    return new InMemoryDexClassLoader(ByteBuffer.wrap(data), parent);
  }

  private static ClassLoader makeClassLoaderFrom(List<byte[]> data, ClassLoader parent)
      throws Exception {
    ArrayList<ByteBuffer> bufs = new ArrayList<>();
    for (byte[] d : data) {
      bufs.add(ByteBuffer.wrap(d));
    }
    return new InMemoryDexClassLoader(bufs.toArray(new ByteBuffer[0]), parent);
  }

  private static native void initializeTest();
  private static native String[] getClassloaderDescriptors(ClassLoader loader);
}
