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

org.scaladebugger.api.profiles.RequestHelper.scala Maven / Gradle / Ivy

package org.scaladebugger.api.profiles

import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger

import com.sun.jdi.event.Event
import org.scaladebugger.api.lowlevel.events.EventType.EventType
import org.scaladebugger.api.lowlevel.events.data.JDIEventDataResult
import org.scaladebugger.api.lowlevel.events.filters.UniqueIdPropertyFilter
import org.scaladebugger.api.lowlevel.events.{EventManager, JDIEventArgument}
import org.scaladebugger.api.lowlevel.requests.JDIRequestArgument
import org.scaladebugger.api.lowlevel.requests.properties.UniqueIdProperty
import org.scaladebugger.api.lowlevel.{JDIArgument, RequestInfo}
import org.scaladebugger.api.pipelines.Pipeline
import org.scaladebugger.api.pipelines.Pipeline.IdentityPipeline
import org.scaladebugger.api.profiles.traits.info.events.EventInfo
import org.scaladebugger.api.utils.{Memoization, MultiMap}
import org.scaladebugger.api.virtualmachines.ScalaVirtualMachine

import scala.collection.JavaConverters._
import scala.util.Try

/**
 * Represents the base request that abstracts functionality common
 * among all requests.
 *
 * @param scalaVirtualMachine The destination for the requests
 * @param eventManager The low-level event manager to listen to events
 * @param etInstance The type of event to
 * @param _newRequestId Generates a new request id
 * @param _newRequest Creates a new request using the provided request id,
 *                    request arguments, and collection of JDI arguments
 * @param _hasRequest Determines whether a request exists with the provided
 *                    request arguments
 * @param _removeRequestById Removes a request using its id
 * @param _newEventInfo Creates a new event info pipeline using the provided
 *                      Scala virtual machine, JDI event, and collection of
 *                      JDI arguments
 * @param _retrieveRequestInfo Retrieves the information for a request using its
 *                           request id, returning Some(info) if found
 * @param _includeUniqueId If true, includes a unique id on each new request
 *                         and filters the generated pipelines using the
 *                         unique id property filter (should be set to false
 *                         for events without requests such as VM Start)
 * @tparam E The JDI event
 * @tparam EI The event info type to transform the JDI event into
 * @tparam RequestArgs The arguments used to create a request
 * @tparam CounterKey The key to use when looking up a pipeline counter to
 *                    increment or decrement
 */
class RequestHelper[
  E <: Event,
  EI <: EventInfo,
  RequestArgs,
  CounterKey
](
  protected val scalaVirtualMachine: ScalaVirtualMachine,
  protected val eventManager: EventManager,
  private[profiles] val etInstance: EventType,
  private[profiles] val _newRequestId: () => String,
  private[profiles] val _newRequest: (String, RequestArgs, Seq[JDIRequestArgument]) => Try[String],
  private[profiles] val _hasRequest: (RequestArgs) => Boolean,
  private[profiles] val _removeRequestById: String => Unit,
  private[profiles] val _newEventInfo: (ScalaVirtualMachine, E, Seq[JDIArgument]) => EI,
  private[profiles] val _retrieveRequestInfo: String => Option[RequestInfo],
  private[profiles] val _includeUniqueId: Boolean = true
) {
  // Do not allow any argument to be null (stop at front door)
  require(
    scalaVirtualMachine != null && eventManager != null && etInstance != null &&
    _newRequestId != null && _newRequest != null && _hasRequest != null &&
    _removeRequestById != null && _newEventInfo != null &&
    _removeRequestById != null
  )

  /** Represents the combination of event and data returned. */
  type EventAndData = (EI, Seq[JDIEventDataResult])

  /** Represents the manager of the Scala virtual machine. */
  private lazy val scalaVirtualMachineManager = scalaVirtualMachine.manager

  /**
   * Contains a mapping of request ids to associated event handler ids.
   */
  private val pipelineRequestEventIds = new MultiMap[String, String]

  /**
   * Contains mapping from input to a counter indicating how many pipelines
   * are currently active for the input.
   */
  private val pipelineCounter =
    new ConcurrentHashMap[CounterKey, AtomicInteger]().asScala

  /**
   * Creates a new request using the given arguments. The
   * request is memoized, meaning that the same request will be returned for
   * the same arguments. The memoized result will be thrown out if the
   * underlying request storage indicates that the request has been removed.
   *
   * @param requestArgs The custom request arguments
   * @param jdiRequestArgs The JDI request arguments
   * @return Success containing the event id, otherwise a failure
   */
  def newRequest(
    requestArgs: RequestArgs,
    jdiRequestArgs: Seq[JDIRequestArgument]
  ) = newRequestImpl(requestArgs, jdiRequestArgs)

  /** Represents the internal implementation of newRequest. */
  private lazy val newRequestImpl = {
    type Input = (RequestArgs, Seq[JDIRequestArgument])
    type Key = (RequestArgs, Seq[JDIRequestArgument])
    type Output = Try[String]

    val m = new Memoization[Input, Key, Output](
      memoFunc = (input: Input) => {
        val requestId = _newRequestId()
        val args =
          if (_includeUniqueId) UniqueIdProperty(id = requestId) +: input._2
          else input._2

        _newRequest(requestId, input._1, args)
      },
      cacheInvalidFunc = (key: Key) => !_hasRequest(key._1)
    )

    (requestArgs: RequestArgs, jdiRequestArgs: Seq[JDIRequestArgument]) =>
      Try(m((requestArgs, jdiRequestArgs))).flatten
  }

  /**
   * Creates a new pipeline of events and data using the given
   * arguments. The pipeline is NOT memoized; therefore, each call creates a
   * new pipeline with a new underlying event handler feeding the pipeline.
   * This means that the pipeline needs to be properly closed to remove the
   * event handler.
   *
   * @param requestId The id of the request whose events to stream through the
   *                  new pipeline
   * @param counterKey The key used to increment and decrement the underlying
   *                   pipeline counter
   * @return Success containing new event and data pipeline, otherwise a failure
   */
  def newEventPipeline(
    requestId: String,
    eventArgs: Seq[JDIEventArgument],
    counterKey: CounterKey
  ): Try[IdentityPipeline[EventAndData]] = Try {
    // Lookup final set of request arguments used when creating the request
    val rArgs = _retrieveRequestInfo(requestId)
      .map(_.extraArguments).getOrElse(Nil)

    val eArgsWithFilter =
      if (_includeUniqueId) UniqueIdPropertyFilter(id = requestId) +: eventArgs
      else eventArgs
    val newPipeline = newEventStream(rArgs, eArgsWithFilter)

    // Create a companion pipeline who, when closed, checks to see if there
    // are no more pipelines for the given request and, if so, removes the
    // request as well
    val closePipeline = Pipeline.newPipeline(
      classOf[EventAndData],
      newPipelineCloseFunc(requestId, counterKey)
    )
    val combinedPipeline = newPipeline.unionOutput(closePipeline)

    // Increment the counter for open pipelines
    pipelineCounter
      .getOrElseUpdate(counterKey, new AtomicInteger(0))
      .incrementAndGet()

    // Store the new event handler id as associated with the current request
    pipelineRequestEventIds.put(
      requestId,
      combinedPipeline.currentMetadata(
        EventManager.EventHandlerIdMetadataField
      ).asInstanceOf[String]
    )

    combinedPipeline
  }

  /**
   * Creates an event stream pipeline using the provided request and event
   * arguments as input to creating the new event stream.
   *
   * @param requestArgs The request arguments to use when creating the stream
   * @param eventArgs The event arguments to use when creating the stream
   * @return A new pipeline of events and associated data
   */
  private def newEventStream(
    requestArgs: Seq[JDIRequestArgument],
    eventArgs: Seq[JDIEventArgument]
  ): IdentityPipeline[EventAndData] = {
    val allArgs = (requestArgs ++ eventArgs).distinct
    eventManager
      .addEventDataStream(etInstance, eventArgs: _*)
      .map(t => (t._1.asInstanceOf[E], t._2))
      .map(t => {
        val vm = Try(t._1.virtualMachine())
        val svm = vm.flatMap(vm => Try(scalaVirtualMachineManager(vm)))
        svm.map(s => (_newEventInfo(s, t._1, allArgs), t._2)).get
      }).noop()
  }

  /**
   * Creates a new function used for closing generated pipelines.
   *
   * @param requestId The id of the request
   * @param counterKey The key used to decrement the underlying pipeline counter
   * @return The new function for closing the pipeline
   */
  private def newPipelineCloseFunc(
    requestId: String,
    counterKey: CounterKey
  ): (Option[Any]) => Unit = (data: Option[Any]) => {
    val pCounter = pipelineCounter(counterKey)

    val totalPipelinesRemaining = pCounter.decrementAndGet()

    import org.scaladebugger.api.profiles.Constants.CloseRemoveAll
    if (totalPipelinesRemaining == 0 || data.exists(_ == CloseRemoveAll)) {
      _removeRequestById(requestId)
      pipelineRequestEventIds.remove(requestId).foreach(
        _.foreach(eventManager.removeEventHandler)
      )
      pCounter.set(0)
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy