All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.appland.appmap.output.v1.Parameters Maven / Gradle / Ivy

package com.appland.appmap.output.v1;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.tinylog.TaggedLogger;

import com.appland.appmap.config.AppMapConfig;
import com.appland.appmap.config.Properties;
import com.appland.appmap.util.Logger;

import javassist.CtBehavior;
import javassist.CtClass;
import javassist.NotFoundException;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;

/**
 * A serializable list of named and typed objects.
 *
 * @see Event
 * @see Value
 * @see GitHub: AppMap - Parameter object format
 */
public class Parameters implements Iterable {
  private static final TaggedLogger logger = AppMapConfig.getLogger(null);

  private final ArrayList values = new ArrayList();

  public Parameters() { }

  /**
   * Constructs a Parameters object from an existing CtBehavior. Values will automatically be added,
   * named and typed after the behaviors parameters.
   *
   * @param behavior The behavior to construct Parameters from
   * @throws NoSourceAvailableException If parameter names cannot be read from the behavior
   * @see GitHub: AppMap - Function call attributes
   */
  public Parameters(CtBehavior behavior) {
    MethodInfo methodInfo = behavior.getMethodInfo();
    String fqn = behavior.getDeclaringClass().getName() +
      "." + behavior.getName() +
      methodInfo.getDescriptor();

    CtClass[] paramTypes = null;
    try {
      paramTypes = behavior.getParameterTypes();
    } catch (NotFoundException e) {
      //@formatter:off
      // getParameterTypes throws NotFoundException when it can't find a class
      // definition for the type of one of the parameters. Given that:
      //
      //   * the method represented by this behavior was loaded from one of the
      //     app's classes (and so originally compiled successfully)
      //
      //   * the JVM resolves the classes for a method's parameters before
      //     loading the method itself
      //
      // this failure can only happen if we're trying to instrument a method
      // that can never be called.
      //
      // Log a message and bail.
      //@formatter:on
      logger.debug(e, "Failed to get parameter types for {}", fqn);
      return;
    }

    CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
    LocalVariableAttribute locals = null;
    if (codeAttribute != null) {
      locals = (LocalVariableAttribute) codeAttribute.getAttribute(javassist.bytecode.LocalVariableAttribute.tag);
    } else {
      logger.debug("No code attribute for {}", fqn);
    }

    String[] paramNames = null;
    int numParams = paramTypes.length;
    if (locals != null && numParams > 0) {
      int numLocals = locals.tableLength();
      
      // This is handy when debugging this code, but produces too much
      // noise for general use.
      if (Properties.DebugLocals) {
        Logger.println("local variables for " + fqn);
        for (int idx = 0; idx < numLocals; idx++) {
          Logger.printf("  %d %s %d\n", idx, locals.variableName(idx), locals.index(idx));
        }
      }

      paramNames = new String[numParams];
      Boolean isStatic = (behavior.getModifiers() & Modifier.STATIC) != 0;
      int firstParamIdx = isStatic ? 0 : 1; // ignore `this`
      int localVarIdx = 0;
      
      // Scan the local variables until we find the one with an index
      // that matches the first parameter index.
      //
      // In some cases, though, there aren't local variables for the
      // parameters. For example, the class file for
      // org.springframework.samples.petclinic.owner.PetTypeFormatter,
      // has the method
      // print(Ljava/lang/Object;Ljava/util/Locale;)Ljava/lang/String
      // in it, which only has a local variable for `this`. This
      // method isn't in the source file, so I'm not sure where it's
      // coming from.
      for (; localVarIdx < numLocals; localVarIdx++) {
        if (locals.index(localVarIdx) == firstParamIdx) {
          break;
        }
      }

      if (localVarIdx < numLocals) {
        // Assume the rest of the parameters follow the first.
        paramNames[0] = locals.variableName(localVarIdx);
        for (int idx = 1; idx < numParams; idx++) {
          paramNames[idx] = locals.variableName(localVarIdx + idx);
        }
      }
    }

    Value[] paramValues = new Value[numParams];
    for (int i = 0; i < paramTypes.length; ++i) {
      // Use a real parameter name if we have it, a fake one if we
      // don't.
      String paramName = paramNames != null ? paramNames[i] : "p" + i;
      Value param = new Value()
          .setClassType(paramTypes[i].getName())
          .setName(paramName)
          .setKind("req");

      paramValues[i] = param;
    }

    for (int i = 0; i < paramValues.length; ++i) {
      this.add(paramValues[i]);
    }
  }

  /**
   * Get an iterator for each {@link Value}.
   * @return A {@link Value} iterator
   */
  @Override
  public Iterator iterator() {
    return this.values.iterator();
  }

  /**
   * Add a new {@link Value} object to the end of the list.
   * @param param The {@link Value} to be added
   */
  public boolean add(Value param) {
    if (param == null) {
      return false;
    }

    return this.values.add(param);
  }

  /**
   * Gets a stream of Values.
   * @return A {@link Value} Stream
   */
  public Stream stream() {
    return this.values.stream();
  }

  /**
   * Gets the number of values stored.
   * @return The size of the internal value array
   */
  public int size() {
    return this.values.size();
  }


  /**
   * Clears the internal value array.
   */
  public void clear() {
    this.values.clear();
  }

  /**
   * Gets a {@Value} object stored by this Parameters object by name/identifier.
   * @param name The name or identifier of the @{link Value} to be returned
   * @return The {@link Value} object found
   * @throws NoSuchElementException If no @{link Value} object is found
   */
  public Value get(String name) throws NoSuchElementException {
    if (this.values != null) {
      for (Value param : this.values) {
        if (param.name.equals(name)) {
          return param;
        }
      }
    }

    throw new NoSuchElementException();
  }

  /**
   * Gets a {@Value} object stored by this Parameters object by index.
   * @param index The index of the @{link Value} to be returned
   * @return The {@link Value} object at the given index
   * @throws NoSuchElementException if no @{link Value} object is found at the given index
   */
  public Value get(Integer index) throws NoSuchElementException {
    if (this.values == null) {
      throw new NoSuchElementException();
    }

    try {
      return this.values.get(index);
    } catch (NullPointerException | IndexOutOfBoundsException e) {
      throw new NoSuchElementException();
    }
  }

  /**
   * Tests if the {@link Value} object at the given index is of a given type.
   * @param index The index to validate
   * @param type The name of the type to check for
   * @return {@code true} if the @{link Value} at the given index matches the type given. Otherwise,
   *         {@code false}.
   */
  public Boolean validate(Integer index, String type) {
    try {
      Value param = this.get(index);
      return param.classType.equals(type);
    } catch (NoSuchElementException e) {
      return false;
    }
  }

  /**
   * Performs a deep copy of the Parameters object and all of its values.
   * @return A new Parameters object
   */
  public Parameters clone() {
    Parameters clonedParams = new Parameters();
    for (Value param : this.values) {
      clonedParams.add(new Value(param));
    }
    return clonedParams;
  }

  @Override
  public String toString() {
    return this.values
        .stream()
        .map(value -> value.classType)
        .collect(Collectors.joining(", "));
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy