content: /*
   * Copyright 2010-2025 Hyland Software, Inc. and its affiliates.
   *
   * 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 org.activiti.core.el.juel.tree.impl.ast;
  
  import jakarta.el.ELContext;
  import jakarta.el.ELException;
  import java.lang.reflect.Array;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import org.activiti.core.el.juel.misc.LocalMessages;
  import org.activiti.core.el.juel.tree.Bindings;
  import org.activiti.core.el.juel.tree.FunctionNode;
  
  public class AstFunction extends AstRightValue implements FunctionNode {
  
      private final int index;
      private final String name;
      private final AstParameters params;
      private final boolean varargs;
  
      public AstFunction(String name, int index, AstParameters params) {
          this(name, index, params, false);
      }
  
      public AstFunction(String name, int index, AstParameters params, boolean varargs) {
          this.name = name;
          this.index = index;
          this.params = params;
          this.varargs = varargs;
      }
  
      /**
       * Invoke method.
       * @param bindings
       * @param context
       * @param base
       * @param method
       * @return method result
       * @throws InvocationTargetException
       * @throws IllegalAccessException
       */
      protected Object invoke(Bindings bindings, ELContext context, Object base, Method method)
          throws InvocationTargetException, IllegalAccessException {
          Class<?>[] types = method.getParameterTypes();
          Object[] params = null;
          if (types.length > 0) {
              params = new Object[types.length];
              if (varargs && method.isVarArgs()) {
                  for (int i = 0; i < params.length - 1; i++) {
                      Object param = getParam(i).eval(bindings, context);
                      if (param != null || types[i].isPrimitive()) {
                          params[i] = bindings.convert(param, types[i]);
                      }
                  }
                  int varargIndex = types.length - 1;
                  Class<?> varargType = types[varargIndex].getComponentType();
                  int length = getParamCount() - varargIndex;
                  Object array = null;
                  if (length == 1) {
                      // special: eventually use argument as is
                      Object param = getParam(varargIndex).eval(bindings, context);
                      if (param != null && param.getClass().isArray()) {
                          if (types[varargIndex].isInstance(param)) {
                              array = param;
                          } else {
                              // coerce array elements
                              length = Array.getLength(param);
                              array = Array.newInstance(varargType, length);
                              for (int i = 0; i < length; i++) {
                                  Object elem = Array.get(param, i);
                                  if (elem != null || varargType.isPrimitive()) {
                                      Array.set(array, i, bindings.convert(elem, varargType));
                                  }
                              }
                          }
                      } else {
                          // single element array
                          array = Array.newInstance(varargType, 1);
                          if (param != null || varargType.isPrimitive()) {
                              Array.set(array, 0, bindings.convert(param, varargType));
                          }
                      }
                  } else {
                      array = Array.newInstance(varargType, length);
                      for (int i = 0; i < length; i++) {
                          Object param = getParam(varargIndex + i).eval(bindings, context);
                          if (param != null || varargType.isPrimitive()) {
                              Array.set(array, i, bindings.convert(param, varargType));
                          }
                      }
                  }
                  params[varargIndex] = array;
              } else {
                  for (int i = 0; i < params.length; i++) {
                      Object param = getParam(i).eval(bindings, context);
                      if (param != null || types[i].isPrimitive()) {
                          params[i] = bindings.convert(param, types[i]);
                      }
                  }
              }
          }
          return method.invoke(base, params);
      }
  
      @Override
      public Object eval(Bindings bindings, ELContext context) {
          Method method = bindings.getFunction(index);
          try {
              return invoke(bindings, context, null, method);
          } catch (IllegalAccessException e) {
              throw new ELException(LocalMessages.get("error.function.access", name), e);
          } catch (InvocationTargetException e) {
              throw new ELException(LocalMessages.get("error.function.invocation", name), e.getCause());
          }
      }
  
      @Override
      public String toString() {
          return name;
      }
  
      @Override
      public void appendStructure(StringBuilder b, Bindings bindings) {
          b.append(bindings != null && bindings.isFunctionBound(index) ? "<fn>" : name);
          params.appendStructure(b, bindings);
      }
  
      public int getIndex() {
          return index;
      }
  
      public String getName() {
          return name;
      }
  
      public boolean isVarArgs() {
          return varargs;
      }
  
      public int getParamCount() {
          return params.getCardinality();
      }
  
      protected AstNode getParam(int i) {
          return params.getChild(i);
      }
  
      public int getCardinality() {
          return 1;
      }
  
      public AstNode getChild(int i) {
          return i == 0 ? params : null;
      }
  }
  