blob: 069a3048b2bf899463d000bc3b3208900d141498 [file] [log] [blame] [raw]
package li.cil.oc.server.machine
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import li.cil.oc.OpenComputers
import li.cil.oc.api.driver.MethodWhitelist
import li.cil.oc.api.driver.NamedBlock
import li.cil.oc.api.machine
import li.cil.oc.api.machine.Arguments
import li.cil.oc.api.machine.Context
import li.cil.oc.api.network.FilteredEnvironment
import li.cil.oc.api.network.ManagedPeripheral
import li.cil.oc.server.driver.CompoundBlockEnvironment
import scala.collection.immutable
import scala.collection.mutable
object Callbacks {
private val cache = mutable.Map.empty[Class[_], immutable.Map[String, Callback]]
def apply(host: Any) = host match {
case multi: CompoundBlockEnvironment => dynamicAnalyze(host)
case peripheral: ManagedPeripheral => dynamicAnalyze(host)
case filtered: FilteredEnvironment => dynamicAnalyze(host)
case _ => cache.getOrElseUpdate(host.getClass, dynamicAnalyze(host))
}
// Clear the cache; used when world is unloaded, mostly to allow reacting to
// stuff (aka configs) that may influence which @Callbacks are enabled.
def clear(): Unit = {
cache.clear()
}
def fromClass(environment: Class[_]) = staticAnalyze(environment)
private def dynamicAnalyze(host: Any) = {
val whitelists = mutable.Buffer.empty[Set[String]]
val callbacks = mutable.Map.empty[String, Callback]
// Lazy val to allow referencing it in closures before it's actually
// initialized after the base whitelist has been compiled.
lazy val whitelist = whitelists.reduceOption(_.intersect(_)).getOrElse(Set.empty)
def shouldAdd(name: String) = !callbacks.contains(name) && (whitelist.isEmpty || whitelist.contains(name))
def process(environment: Any) = {
environment match {
case list: MethodWhitelist => whitelists += Option(list.whitelistedMethods).fold(Set.empty[String])(_.toSet)
case _ =>
}
val priority = environment match {
case named: NamedBlock => named.priority
case _ => 0
}
val filter = environment match {
case filtered: FilteredEnvironment => (s: String) => shouldAdd(s) && filtered.isCallbackEnabled(s)
case _ => shouldAdd _
}
environment match {
case peripheral: ManagedPeripheral =>
(priority, () => {
for (name <- peripheral.methods() if filter(name)) {
callbacks += name -> new PeripheralCallback(name)
}
staticAnalyze(environment.getClass, Option(filter), Option(callbacks))
})
case _ =>
(priority, () => staticAnalyze(environment.getClass, Option(filter), Option(callbacks)))
}
}
// First collect whitelist and priority information, then sort and
// fetch callbacks.
(host match {
case multi: CompoundBlockEnvironment => multi.environments.map(env => process(env._2))
case single => Seq(process(single))
}).sortBy(-_._1).map(_._2).foreach(_())
callbacks.toMap
}
private def staticAnalyze(seed: Class[_], shouldAdd: Option[String => Boolean] = None, optCallbacks: Option[mutable.Map[String, Callback]] = None) = {
val callbacks = optCallbacks.getOrElse(mutable.Map.empty[String, Callback])
var c: Class[_] = seed
while (c != null && c != classOf[Object]) {
val ms = c.getDeclaredMethods
ms.filter(_.isAnnotationPresent(classOf[machine.Callback])).foreach(m =>
if (m.getParameterTypes.size != 2 ||
m.getParameterTypes()(0) != classOf[Context] ||
m.getParameterTypes()(1) != classOf[Arguments]) {
OpenComputers.log.error(s"Invalid use of Callback annotation on ${m.getDeclaringClass.getName}.${m.getName}: invalid argument types or count.")
}
else if (m.getReturnType != classOf[Array[AnyRef]]) {
OpenComputers.log.error(s"Invalid use of Callback annotation on ${m.getDeclaringClass.getName}.${m.getName}: invalid return type.")
}
else if (!Modifier.isPublic(m.getModifiers)) {
OpenComputers.log.error(s"Invalid use of Callback annotation on ${m.getDeclaringClass.getName}.${m.getName}: method must be public.")
}
else {
val a = m.getAnnotation[machine.Callback](classOf[machine.Callback])
val name = if (a.value != null && a.value.trim != "") a.value else m.getName
if (shouldAdd.fold(true)(_(name))) {
callbacks += name -> new ComponentCallback(m, a)
}
}
)
c = c.getSuperclass
}
callbacks
}
// ----------------------------------------------------------------------- //
abstract class Callback(val annotation: machine.Callback) {
def apply(instance: AnyRef, context: Context, args: Arguments): Array[AnyRef]
}
class ComponentCallback(val method: Method, annotation: machine.Callback) extends Callback(annotation) {
override def apply(instance: AnyRef, context: Context, args: Arguments) = try {
method.invoke(instance, context, args).asInstanceOf[Array[AnyRef]]
} catch {
case e: InvocationTargetException => throw e.getCause
}
}
class PeripheralCallback(name: String) extends Callback(new PeripheralAnnotation(name)) {
override def apply(instance: AnyRef, context: Context, args: Arguments) =
instance match {
case peripheral: ManagedPeripheral => peripheral.invoke(name, context, args)
case _ => throw new NoSuchMethodException()
}
}
}