content: /*
   * 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 io.airlift.bytecode.expression;
  
  import com.google.common.collect.ImmutableList;
  import io.airlift.bytecode.ClassDefinition;
  import io.airlift.bytecode.DynamicClassLoader;
  import io.airlift.bytecode.MethodDefinition;
  import io.airlift.bytecode.Parameter;
  import org.junit.jupiter.api.Test;
  
  import java.util.HashMap;
  import java.util.Map;
  
  import static io.airlift.bytecode.Access.FINAL;
  import static io.airlift.bytecode.Access.PUBLIC;
  import static io.airlift.bytecode.Access.STATIC;
  import static io.airlift.bytecode.Access.a;
  import static io.airlift.bytecode.ClassGenerator.classGenerator;
  import static io.airlift.bytecode.Parameter.arg;
  import static io.airlift.bytecode.ParameterizedType.type;
  import static io.airlift.bytecode.expression.BytecodeExpressionAssertions.assertBytecodeExpression;
  import static io.airlift.bytecode.expression.BytecodeExpressionAssertions.assertBytecodeExpressionType;
  import static io.airlift.bytecode.expression.BytecodeExpressions.constantDouble;
  import static io.airlift.bytecode.expression.BytecodeExpressions.constantFalse;
  import static io.airlift.bytecode.expression.BytecodeExpressions.constantFloat;
  import static io.airlift.bytecode.expression.BytecodeExpressions.constantInt;
  import static io.airlift.bytecode.expression.BytecodeExpressions.constantLong;
  import static io.airlift.bytecode.expression.BytecodeExpressions.constantNull;
  import static io.airlift.bytecode.expression.BytecodeExpressions.constantString;
  import static io.airlift.bytecode.expression.BytecodeExpressions.constantTrue;
  import static io.airlift.bytecode.expression.BytecodeExpressions.invokeStatic;
  import static io.airlift.bytecode.expression.BytecodeExpressions.newArray;
  
  public class TestArrayBytecodeExpressions
  {
      private final DynamicClassLoader classLoader = new DynamicClassLoader(TestArrayBytecodeExpressions.class.getClassLoader());
      private final ClassDefinition classDefinition = new ClassDefinition(a(PUBLIC, FINAL), "DummyClass", type(Object.class));
      private final Map<Class<?>, MethodDefinition> typeMethodMap = new HashMap<>();
  
      TestArrayBytecodeExpressions()
      {
          for (Class<?> aClass : ImmutableList.of(boolean[].class, char[].class, float[].class, double[].class, byte[].class, short[].class, int[].class, long[].class, String[].class)) {
              MethodDefinition methodDefinition = defineSetAndGetMethod(aClass);
              typeMethodMap.put(aClass, methodDefinition);
          }
  
          classGenerator(classLoader).defineClass(classDefinition, Object.class);
      }
  
      @Test
      void testNewArray()
              throws Exception
      {
          assertBytecodeExpressionType(newArray(type(boolean[].class), 5), type(boolean[].class));
          assertBytecodeExpression(newArray(type(boolean[].class), 5).length(), 5, "new boolean[5].length");
  
          assertBytecodeExpressionType(newArray(type(char[].class), 5), type(char[].class));
          assertBytecodeExpression(newArray(type(char[].class), 5).length(), 5, "new char[5].length");
  
          assertBytecodeExpressionType(newArray(type(float[].class), 5), type(float[].class));
          assertBytecodeExpression(newArray(type(float[].class), 5).length(), 5, "new float[5].length");
  
          assertBytecodeExpressionType(newArray(type(double[].class), 5), type(double[].class));
          assertBytecodeExpression(newArray(type(double[].class), 5).length(), 5, "new double[5].length");
  
          assertBytecodeExpressionType(newArray(type(byte[].class), 5), type(byte[].class));
          assertBytecodeExpression(newArray(type(byte[].class), 5).length(), 5, "new byte[5].length");
  
          assertBytecodeExpressionType(newArray(type(short[].class), 5), type(short[].class));
          assertBytecodeExpression(newArray(type(short[].class), 5).length(), 5, "new short[5].length");
  
          assertBytecodeExpressionType(newArray(type(int[].class), 5), type(int[].class));
          assertBytecodeExpression(newArray(type(int[].class), 5).length(), 5, "new int[5].length");
  
          assertBytecodeExpressionType(newArray(type(long[].class), 5), type(long[].class));
          assertBytecodeExpression(newArray(type(long[].class), 5).length(), 5, "new long[5].length");
  
          assertBytecodeExpressionType(constantString("foo bar baz").invoke("split", String[].class, constantString(" ")), type(String[].class));
          assertBytecodeExpression(constantString("foo bar baz").invoke("split", String[].class, constantString(" ")).length(), 3, "\"foo bar baz\".split(\" \").length");
      }
  
      @Test
      void testNewArrayPrefilled()
              throws Exception
      {
          assertBytecodeExpressionType(newArray(type(boolean[].class), ImmutableList.of(constantTrue(), constantFalse(), constantTrue())), type(boolean[].class));
          assertBytecodeExpression(
                  newArray(type(boolean[].class), ImmutableList.of(constantTrue(), constantFalse(), constantTrue())),
                  new boolean[] {true, false, true},
                  "new boolean[] {true, false, true}");
  
          assertBytecodeExpressionType(newArray(type(int[].class), ImmutableList.of(constantInt(65), constantInt(66), constantInt(99))), type(int[].class));
          assertBytecodeExpression(
                  newArray(type(int[].class), ImmutableList.of(constantInt(65), constantInt(66), constantInt(99))),
                  new int[] {65, 66, 99},
                  "new int[] {65, 66, 99}");
  
          assertBytecodeExpressionType(newArray(type(long[].class), ImmutableList.of(constantLong(1234L), constantLong(12345L), constantLong(9876543210L))), type(long[].class));
          assertBytecodeExpression(
                  newArray(type(long[].class), ImmutableList.of(constantLong(1234L), constantLong(12345L), constantLong(9876543210L))),
                  new long[] {1234L, 12345L, 9876543210L},
                  "new long[] {1234L, 12345L, 9876543210L}");
  
          assertBytecodeExpressionType(newArray(type(String[].class), ImmutableList.of(constantString("grape"), constantNull(String.class), constantString("new"), constantString("array"))), type(String[].class));
          assertBytecodeExpression(
                  newArray(type(String[].class), ImmutableList.of(constantString("grape"), constantNull(String.class), constantString("new"), constantString("array"))),
                  new String[] {"grape", null, "new", "array"},
                  "new String[] {\"grape\", null, \"new\", \"array\"}");
      }
  
      @Test
      void testSetElement()
              throws Exception
      {
          BytecodeExpression stringArray = constantString("foo bar baz").invoke("split", String[].class, constantString(" "));
  
          assertBytecodeExpressionType(stringArray, type(String[].class));
          assertBytecodeExpression(stringArray.length(), 3, "\"foo bar baz\".split(\" \").length");
          assertBytecodeExpression(stringArray.getElement(0), "foo", "\"foo bar baz\".split(\" \")[0]");
          assertBytecodeExpression(stringArray.getElement(1), "bar", "\"foo bar baz\".split(\" \")[1]");
          assertBytecodeExpression(stringArray.getElement(2), "baz", "\"foo bar baz\".split(\" \")[2]");
  
          assertBytecodeExpression(invokeStatic(typeMethodMap.get(boolean[].class), newArray(type(boolean[].class), 5), constantInt(0), constantTrue()), true, classLoader);
          assertBytecodeExpression(invokeStatic(typeMethodMap.get(int[].class), newArray(type(int[].class), 5), constantInt(0), constantInt(999)), 999, classLoader);
          assertBytecodeExpression(invokeStatic(typeMethodMap.get(float[].class), newArray(type(float[].class), 5), constantInt(0), constantFloat(0.777f)), 0.777f, classLoader);
          assertBytecodeExpression(invokeStatic(typeMethodMap.get(double[].class), newArray(type(double[].class), 5), constantInt(0), constantDouble(0.888d)), 0.888d, classLoader);
          assertBytecodeExpression(invokeStatic(typeMethodMap.get(String[].class), stringArray, constantInt(0), constantString("hello")), "hello", classLoader);
      }
  
      @Test
      void testGetElement()
              throws Exception
      {
          assertBytecodeExpression(constantString("abc").invoke("getBytes", byte[].class).getElement(1), "abc".getBytes()[1], "\"abc\".getBytes()[1]");
          assertBytecodeExpression(constantString("abc").invoke("getBytes", byte[].class).getElement(constantInt(1)), "abc".getBytes()[1], "\"abc\".getBytes()[1]");
      }
  
      private MethodDefinition defineSetAndGetMethod(Class<?> aClass)
      {
          Parameter arr = arg("arr", type(aClass));
          Parameter index = arg("index", type(int.class));
          Class<?> componentType = aClass.getComponentType();
          Parameter value = arg("value", type(componentType));
          MethodDefinition methodDefinition = classDefinition.declareMethod(a(PUBLIC, STATIC), "setAndGetMethod_" + componentType.getSimpleName(), type(componentType), arr, index, value);
          methodDefinition.getBody()
                  .append(arr.setElement(index, value))
                  .append(arr.getElement(index).ret());
          return methodDefinition;
      }
  }
  