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

org.scaladebugger.api.virtualmachines.StandardScalaVirtualMachine.scala Maven / Gradle / Ivy

The newest version!
package org.scaladebugger.api.virtualmachines

import java.util.concurrent.atomic.AtomicBoolean

import org.scaladebugger.api.lowlevel.ManagerContainer
import org.scaladebugger.api.lowlevel.breakpoints.PendingBreakpointSupport
import org.scaladebugger.api.lowlevel.exceptions.PendingExceptionSupport
import org.scaladebugger.api.lowlevel.methods.{PendingMethodEntrySupport, PendingMethodExitSupport}
import org.scaladebugger.api.lowlevel.utils.JDIHelperMethods
import org.scaladebugger.api.lowlevel.watchpoints.{PendingAccessWatchpointSupport, PendingModificationWatchpointSupport}
import org.scaladebugger.api.profiles.pure.PureDebugProfile
import org.scaladebugger.api.profiles.ProfileManager
import org.scaladebugger.api.profiles.traits.DebugProfile
import org.scaladebugger.api.utils.{Logging, LoopingTaskRunner}
import com.sun.jdi._
import org.scaladebugger.api.profiles.scala210.Scala210DebugProfile

import scala.util.Try

/**
 * Represents a virtual machine running Scala code.
 *
 * @param manager The manager containing this virtual machine
 * @param _virtualMachine The underlying virtual machine
 * @param profileManager The manager used to provide specific implementations
 *                       of debugging via profiles
 * @param loopingTaskRunner The runner used to process events from remote JVMs
 * @param uniqueId A unique id assigned to the Scala virtual machine on the
 *                 client (library) side to help distinguish multiple VMs
 */
class StandardScalaVirtualMachine(
  override val manager: ScalaVirtualMachineManager,
  protected val _virtualMachine: VirtualMachine,
  protected val profileManager: ProfileManager,
  private val loopingTaskRunner: LoopingTaskRunner,
  override val uniqueId: String = java.util.UUID.randomUUID().toString
) extends ScalaVirtualMachine with JDIHelperMethods with Logging {
  private val initialized = new AtomicBoolean(false)
  private val started = new AtomicBoolean(false)
  protected lazy val eventManager = lowlevel.eventManager

  /**
   * Indicates whether or not the virtual machine has started (received the
   * start event).
   *
   * @return True if started, otherwise false
   */
  override def isStarted = started.get()

  /**
   * Indicates whether or not the virtual machine has been initialized.
   *
   * @return True if initialized, otherwise false
   */
  override def isInitialized: Boolean = initialized.get()

  /** Builds a string with the identifier of this virtual machine. */
  private def vmString(message: String) = s"(Scala VM $uniqueId) $message"

  /**
   * Creates a new instance of a manager container with newly-initialized
   * managers.
   *
   * @param loopingTaskRunner The looping task runner to provide to various
   *                          managers
   * @return The new container of managers
   */
  protected def newManagerContainer(
    loopingTaskRunner: LoopingTaskRunner
  ): ManagerContainer = ManagerContainer.fromVirtualMachine(
    virtualMachine = _virtualMachine,
    loopingTaskRunner = loopingTaskRunner,
    autoStartEventManager = false
  )

  /**
   * Represents the cache of objects available on the virtual machine.
   * Caching is done manually, so this cache is not populated as objects are
   * created on the virtual machine.
   */
  override lazy val cache: ObjectCache = new ObjectCache

  /** Represents the collection of low-level APIs for the virtual machine. */
  override lazy val lowlevel = newManagerContainer(loopingTaskRunner)

  /**
   * Initializes the ScalaVirtualMachine system.
   *
   * @param defaultProfile The default profile to use with the virtual machine
   * @param startProcessingEvents If true, immediately starts processing events
   */
  override def initialize(
    defaultProfile: String,
    startProcessingEvents: Boolean
  ): Unit = synchronized {
    assert(!isInitialized,
      s"Scala virtual machine '$uniqueId' already initialized!")

    logger.debug(vmString("Initializing Scala virtual machine!"))

    // Register our standard profiles
    registerStandardProfiles()

    // Mark our default profile
    this.use(defaultProfile)

    logger.debug(vmString("Adding custom event handlers!"))

    // Mark start event to load all of our system classes
    this.withProfile(defaultProfile).getOrCreateVMStartRequest().foreach(_ => {
      // Mark the VM as started
      started.set(true)

      logger.debug(vmString("Received start event!"))
      logger.trace(vmString("Refreshing all class references!"))
      lowlevel.classManager.refreshAllClasses()

      lowlevel.classManager.allFileNames.foreach(processPendingForFile)
      lowlevel.classManager.allClasses.map(_.name()).foreach(processPendingForClass)
    })

    // Mark class prepare events to signal refreshing our classes
    this.withProfile(defaultProfile).getOrCreateClassPrepareRequest().foreach(classPrepareEvent => {
      val referenceType = classPrepareEvent.referenceType
      val referenceTypeName = referenceType.name
      val fileName = lowlevel.classManager.fileNameForReferenceType(
        referenceType.toJdiInstance
      )

      // TODO: Extract these checks out to helper methods (perhaps in the
      // lowlevel class manager)
      if (
        referenceTypeName.startsWith("java") ||
        referenceTypeName.startsWith("sun") ||
        referenceTypeName.startsWith("scala")
      ) {
        logger.trace(vmString(s"Received new core class: $referenceTypeName"))
      } else {
        logger.trace(vmString(s"Received new non-core class: $referenceTypeName"))
      }
      lowlevel.classManager.refreshClass(referenceType.toJdiInstance)

      processPendingForFile(fileName)
      processPendingForClass(referenceTypeName)
    })

    initialized.set(true)

    // Try to start the event manager if indicated
    if (startProcessingEvents) this.startProcessingEvents()
  }

  /**
   * Starts actively processing events from the remote virtual machine.
   */
  override def startProcessingEvents(): Unit = synchronized {
    assert(isInitialized, "Scala virtual machine must be initialized!")

    // Ignore if already processing events
    if (isProcessingEvents) return

    // Process all pending requests immediately
    logger.debug("Processing all pending requests!")
    processOwnPendingRequests()

    logger.debug("Starting low-level event manager!")
    Try(eventManager.start())
  }

  protected def processOwnPendingRequests(): Unit =
    lowlevel.processPendingRequests()

  /**
   * Stops actively processing events from the remote virtual machine.
   */
  override def stopProcessingEvents(): Unit = Try(eventManager.stop())

  /**
   * Indicates whether or not events from the remote virtual machine are
   * actively being processed.
   *
   * @return True if being processed, otherwise false
   */
  override def isProcessingEvents: Boolean = eventManager.isRunning

  private def registerStandardProfiles(): Unit = {
    this.register(
      PureDebugProfile.Name,
      new PureDebugProfile(this, lowlevel)(_virtualMachine)
    )

    this.register(
      Scala210DebugProfile.Name,
      new Scala210DebugProfile(this, lowlevel)(_virtualMachine)
    )
  }

  private def processPendingForFile(fileName: String): Unit = {
    lowlevel.productIterator.foreach {
      case p: PendingBreakpointSupport  =>
        logger.trace(vmString(s"Processing any pending breakpoints for $fileName!"))
        p.processPendingBreakpointRequestsForFile(fileName)
      case _                            =>
    }
  }

  private def processPendingForClass(className: String): Unit = {
    lowlevel.productIterator.foreach {
      case p: PendingExceptionSupport               =>
        logger.trace(vmString(s"Processing any pending exceptions for $className!"))
        p.processPendingExceptionRequestsForClass(className)
      case p: PendingAccessWatchpointSupport        =>
        logger.trace(vmString(s"Processing any pending access watchpoints for $className!"))
        p.processPendingAccessWatchpointRequestsForClass(className)
      case p: PendingModificationWatchpointSupport  =>
        logger.trace(vmString(s"Processing any pending modification watchpoints for $className!"))
        p.processPendingModificationWatchpointRequestsForClass(className)
      case p: PendingMethodEntrySupport             =>
        logger.trace(vmString(s"Processing any pending method entries for $className!"))
        p.processPendingMethodEntryRequestsForClass(className)
      case p: PendingMethodExitSupport              =>
        logger.trace(vmString(s"Processing any pending method exits for $className!"))
        p.processPendingMethodExitRequestsForClass(className)
      case _                                        =>
    }
  }

  /**
   * Represents the underlying virtual machine represented by this Scala
   * virtual machine.
   *
   * @return The JDI VirtualMachine instance
   */
  override val underlyingVirtualMachine: VirtualMachine = _virtualMachine

  /**
   * Resumes the virtual machine represented by the profile.
   */
  override def resume(): Unit = underlyingVirtualMachine.resume()

  /**
   * Suspends the virtual machine represented by the profile.
   */
  override def suspend(): Unit = underlyingVirtualMachine.suspend()

  /**
   * Registers the profile using the provided name. Ignores any registration
   * under an already-used name.
   *
   * @param name The name of the profile to register
   * @param profile The profile to register
   */
  override def register(
    name: String,
    profile: DebugProfile
  ): Option[DebugProfile] = profileManager.register(name, profile)

  /**
   * Retrieves the profile with the provided name.
   *
   * @param name The name of the profile to retrieve
   * @return Some debug profile if found, otherwise None
   */
  override def retrieve(name: String): Option[DebugProfile] =
    profileManager.retrieve(name)

  /**
   * Unregisters the profile with the provided name.
   *
   * @param name The name of the profile to unregister
   * @return Some debug profile if unregistered, otherwise None
   */
  override def unregister(name: String): Option[DebugProfile] =
    profileManager.unregister(name)
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy