blob: 4294dffee7b9ccd5a72ccf0e402910626d8fac4e [file] [log] [blame]
# File src/library/methods/R/refClass.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2015 The R Core Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# A copy of the GNU General Public License is available at
# https://www.R-project.org/Licenses/
## Classes to support OOP-style classes with reference-semantics for fields
## and class-based methods.
## Implementation of the R-based version of these classes (using environments)
envRefInferField <- function(self, field, thisClass, selfEnv = as.environment(self)) {
'Install a field method into the environment of object
self from reference class thisClass.'
fields <- thisClass@fieldPrototypes
if(exists(field, envir = fields, inherits = FALSE)) {
## this allows lazy installation of fields (not currently used)
value <- get(field, envir = fields)
}
else {
methods <- thisClass@refMethods
if(exists(field, envir = methods, inherits = FALSE)) {
value <- get(field, envir = methods)
## install this method and any methods it may call
value <- installClassMethod(value, self, field, selfEnv, thisClass)
}
else
stop(gettextf("%s is not a valid field or method name for reference class %s",
sQuote(field),
dQuote(thisClass@className)),
domain = NA)
}
value
}
installClassMethod <- function(def, self, me, selfEnv, thisClass) {
if(is(def, "externalMethodDef") || !is(def, "refMethodDef")) {
## Don't process either an external method (not needed),
## or a special object in the class refMethods
## environment (will cause an error). Assign it unchanged.
assign(me, def, envir = thisClass@refMethods)
return(def)
}
depends <- def@mayCall
environment(def) <- selfEnv # for access to fields and methods
assign(me, def, envir = selfEnv)
## process those that are not in the instance environment, now that
## this method has been assigned.
done <- names(selfEnv)
notDone <- setdiff(depends, done)
superCase <- match("callSuper", notDone, 0)
if(superCase > 0) {
if(nzchar(def@superClassMethod))
notDone[[superCase]] <- def@superClassMethod
else
stop(gettextf("a call to superClass() is in the method %s but there is no superclass definition of this method for class %s",
sQuote(me),
dQuote(thisClass@className)),
domain = NA)
}
for(what in notDone)
installClassMethod(get(what, envir = thisClass@refMethods), self, what, selfEnv, thisClass)
if(superCase > 0) {
## provide an environment with the correct callSuper() definition,
## with selfEnv as its parent (can't override the definition of "callSuper"
## in selfEnv--there may be other methods with a callSuper() in them
newEnv <- new.env(FALSE, parent = selfEnv)
assign("callSuper", get(def@superClassMethod, envir = selfEnv),
envir = newEnv)
environment(def) <- newEnv
assign(me, def, envir = selfEnv)
## the callSuper() inside def now goes to the right method
}
def
}
..hasCodeTools <- FALSE
.hasCodeTools <- function() {
if(!isTRUE(..hasCodeTools)) # will be FALSE when methods is built, keep checking
.assignOverBinding("..hasCodeTools",length(list.files(system.file(package = "codetools"))) > 0,
.methodsNamespace, FALSE)
..hasCodeTools
}
.getGlobalFuns <- function(def) {
if(.hasCodeTools())
codetools::findGlobals(def, merge = FALSE)$functions
else
unique(unlist(lapply(def, all.names)))
}
makeClassMethod <- function(def, name, Class, superClassMethod = "", allMethods) {
if(identical(formalArgs(def)[[1]], ".self"))
def <- externalRefMethod(def)
if(is(def, "externalRefMethod")) { # either just created or passed in as argument
## the method just passes .self and its arguments to the actual method function
def@name <- name
def@refClassName <- Class
def@superClassMethod <- superClassMethod
return(def)
}
depends <- .getGlobalFuns(def)
## find the field methods called ...
if("usingMethods" %in% depends) { # including those declared
declared <- .declaredMethods(def)
## look for invalid declared methods
if(length(declared) && any(! declared %in% allMethods))
warning(gettextf("methods declared in usingMethods() but not found: %s",
paste0(declared[! declared %in% allMethods], collapse = ", ")))
depends <- c(declared, depends)
}
depends <- depends[match(depends, allMethods, 0) > 0]
new("refMethodDef", def, mayCall = depends, name = name,
refClassName = Class, superClassMethod = superClassMethod)
}
refObjectClass <- function(object) {
Class <- class(object)
classDef <- getClassDef(Class)
if(is(classDef, "refClassRepresentation"))
classDef
else
stop(gettextf("%s is not a reference class",
dQuote(Class)),
domain = NA)
}
envRefSetField <- function(object, field,
thisClass = refObjectClass(object),
env = as.environment(object), value) {
fieldClass <- thisClass@fieldClasses[[field]]
if(is.null(fieldClass))
stop(gettextf("%s is not a field in class %s",
sQuote(field),
dQuote(thisClass@className)),
domain = NA)
else
assign(field, value, envir = env)
object
}
.initForEnvRefClass <- function(.Object, ...) {
Class <- class(.Object)
classDef <- getClass(Class)
objectParent <- classDef@refMethods$.objectParent
if(is.null(objectParent)) {
## This warning would be reasonable if we required re-installing packages for R 3.3.0
## warning(
## gettextf("Class definition for Class \"%s\" doesn't have a parent environment for objects defined.\n A package may need to be re-installed", Class))
objectParent <- .NamespaceOrPackage(classDef@package)
}
selfEnv <- new.env(TRUE, objectParent)
## the parent environment will be used by field methods, to make
## them consistent with functions in this class's package
.Object@.xData <- selfEnv
## install prototypes and active bindings
prototypes <- classDef@fieldPrototypes
fieldClasses <- classDef@fieldClasses
fields <- names(fieldClasses)
for(field in fields) {
fp <- prototypes[[field]] # prototype or NULL
if(is(fp, "activeBindingFunction")) {
environment(fp) <- selfEnv
makeActiveBinding(field, fp, selfEnv)
if(is(fp, "defaultBindingFunction")) {
## ensure an initial value
class <- fieldClasses[[field]]
value <- if(!isVirtualClass(class)) new(class) # else NULL
assign(.bindingMetaName(field), value, envir = selfEnv)
}
}
else
assign(field, fp, envir = selfEnv)
}
## assign references to the object and to its class definition
selfEnv$.self <- .Object
selfEnv$.refClassDef <- classDef
if(is.function(classDef@refMethods$initialize)) {
.Object$initialize(...)
## intialize methods are allowed to change .self
.Object <- selfEnv$.self
}
else {
if(nargs() > 1) {
.Object <-
methods::initRefFields(.Object, classDef, selfEnv, list(...))
}
}
if(is.function(classDef@refMethods$finalize))
reg.finalizer(selfEnv, function(x) x$.self$finalize(), TRUE)
lockBinding(".self", selfEnv)
lockBinding(".refClassDef", selfEnv)
## validObject was called from the S4 initialize; check that
## a method specified for the ref. class is satisfied, if there is one
if(is(classDef@validity, "function"))
validObject(.Object)
.Object
}
## old version, for back compatibility. Could be deleted after 2.15.0
initFieldArgs <- function(.Object, classDef, selfEnv, ...)
initRefFields(.Object, classDef, selfEnv, list(...))
initRefFields <- function(.Object, classDef, selfEnv, args) {
if(length(args)) {
snames <- allNames(args)
which <- nzchar(snames)
elements <- args[which]
supers <- args[!which]
elNames <- names(elements)
for(super in supers) {
if(!is(super, "refClass")) {
warning(gettextf("unnamed arguments to $new() must be objects from a reference class; got an object of class %s",
dQuote(class(super))),
domain = NA)
next
}
fields <- names(super$.refClassDef@fieldClasses)
##<FIXME> need an object$fields for the above </FIXME>
## assign field if it is not already specified
fields <- fields[is.na(match(fields, elNames))]
for(field in fields)
elements[[field]] <- super$field(field)
elNames <- names(elements)
}
## assign the fields
for(field in elNames)
envRefSetField(.Object, field, classDef, selfEnv, elements[[field]])
}
.Object
}
.dollarForEnvRefClass <- function(x, name) {
what <- substitute(name)
if(is.symbol(what))
what <- as.character(what)
else
what <- name
selfEnv <- as.environment(x)
if(exists(what, envir = selfEnv, inherits = FALSE))
## either a field or previously cached method
get(what, envir = selfEnv)
else if(is(x, "envRefClass"))
## infer (usually) the method, cache it and return it
envRefInferField(x, what, getClass(class(x)), selfEnv)
else # don't know the reference class(e.g., x is the refMethods env.)
stop(gettextf("%s is not a valid field or method name for this class",
sQuote(what)),
domain = NA)
}
.dollarGetsForEnvRefClass <- function(x, name, value) {
what <- substitute(name)
if(is.symbol(what))
what <- as.character(what)
else
what <- name
selfEnv <- as.environment(x)
envRefSetField(x, what, refObjectClass(x), selfEnv, value)
invisible(x)
}
utils::globalVariables(".envRefMethods")# (codetools analysis)
.envRefMethods <-
list(
export = function(Class) {
'
Returns the result of coercing the object to
Class. No effect on the object itself.
'
if(match(.refClassDef@className, Class, 0) > 0)
return(.self)
classDef <- getClass(Class)
if(is(classDef, "refClassRepresentation") &&
!is.na(match(Class, .refClassDef@refSuperClasses))) {
value <- new(classDef)
env <- as.environment(value)
selfEnv <- as.environment(.self)
fieldClasses <- classDef@fieldClasses
for(field in names(fieldClasses)) {
current <- get(field, envir = selfEnv)
if(!is(current, fieldClasses[[field]]))
stop(gettextf("the class of field %s in the object is not compatible with the desired class %s in the target",
sQuote(field),
dQuote(fieldClasses[[field]])),
domain = NA)
assign(field, envir = env, current)
}
value
}
else if(is(classDef, "classRepresentation")) # use standard S4 as()
methods::as(.self, Class)
else if(is.character(Class) && length(Class) == 1)
stop(gettextf("%s is not a defined class in this environment",
dQuote(Class)),
domain = NA)
else
stop("invalid 'Class' argument: should be a single string")
},
import = function(value, Class = class(value)) {
'
Imports value, replacing the part of the current object
corresponding to Class (if argument Class is missing
it is taken to be class(value)). The Class must be one
of the reference superclasses of the current class (or
that class itself, but then you could just overrwite the object).
'
if(!missing(Class))
value <- value$export(Class)
classDef <- getClass(Class)
if(is(classDef, "refClassRepresentation") &&
(!is.na(match(Class, .refClassDef@refSuperClasses))
|| identical(classDef@className, .refClassDef@className))) {
env <- as.environment(value)
selfEnv <- as.environment(.self)
fieldClasses <- .refClassDef@fieldClasses
for(field in names(classDef@fieldClasses)) {
current <- get(field, envir = env)
if(!is(current, fieldClasses[[field]]))
stop(gettextf("the class of field %s in the object is not compatible with the desired class %s in the target",
sQuote(field),
dQuote(fieldClasses[[field]])),
domain = NA)
assign(field, envir = selfEnv, current)
}
invisible(.self)
}
else
stop(gettextf("%s is not one of the reference super classes for this object",
dQuote(Class)),
domain = NA)
},
callSuper = function(...) stop("direct calls to callSuper() are invalid: should only be called from another method"),
initFields = function(...) {
if(missing(...)) .self else
initRefFields(.self, .refClassDef, as.environment(.self), list(...))
},
copy = function(shallow = FALSE) {
def <- .refClassDef
value <- new(def)
vEnv <- as.environment(value)
selfEnv <- as.environment(.self)
for(field in names(def@fieldClasses)) {
if(shallow)
assign(field, get(field, envir = selfEnv), envir = vEnv)
else {
current <- get(field, envir = selfEnv)
if(is(current, "envRefClass"))
current <- current$copy(FALSE)
assign(field, current, envir = vEnv)
}
}
value
},
getRefClass = function(Class = .refClassDef) methods::getRefClass(Class),
getClass = function(...) if(nargs()) methods::getClass(...) else .refClassDef,
field = function(name, value) if(missing(value)) base::get(name, envir = .self) else {
if(is.na(match(name, names(.refClassDef@fieldClasses))))
stop(gettextf("%s is not a field in this class",
sQuote(name)),
domain = NA)
base::assign(name, value, envir = .self)
},
trace = function(..., classMethod = FALSE) {
' Insert trace debugging for the specified method. The arguments are
the same as for the trace() function in package "base". The first argument
should be the name of the method to be traced, quoted or not.
The additional argument classMethod= can be supplied as TRUE (by name only)
in order to trace a method in a generator object (e.g., "new") rather than
in the objects generated from that class.
'
.TraceWithMethods(..., where = .self, classMethod = classMethod)
},
untrace = function(..., classMethod = FALSE) {
' Untrace the method given as the first argument.
'
.TraceWithMethods(..., untrace=TRUE, where = .self, classMethod=classMethod)
},
show = function() {
if(is.null(cl <- tryCatch(class(.self), error=function(e)NULL))) {
cat('Prototypical reference class object\n')
} else {
cat('Reference class object of class ', classLabel(cl), '\n',
sep = "")
fields <- names(.refClassDef@fieldClasses)
for(fi in fields) {
cat('Field "', fi, '":\n', sep = "")
methods::show(field(fi))
}
}
},
usingMethods = function(...) {
' Reference methods used by this method are named as the arguments
either quoted or unquoted. In the code analysis phase of installing the
the present method, the declared methods will be included. It is essntial
to declare any methods used in a nonstandard way (e.g., via an apply function).
Methods called directly do not need to be declared, but it is harmless to do so.
$usingMethods() does nothing at run time.
'
NULL
}
)
## construct a list of class methods for envRefClass
makeEnvRefMethods <- function() {
methods <- .envRefMethods
allMethods <- names(methods)
for(method in allMethods) {
methods[[method]] <- makeClassMethod(methods[[method]],
method, "envRefClass", "", allMethods)
}
## some values to bootstrap the parent environment for objects
methods$.objectParent <- .methodsNamespace
methods$.objectPackage <- "methods"
methods
}
## initialize some reference classes
.InitRefClasses <- function(envir)
{
## class to define a reference class
## Should be split into an abstract class and a standard version
## to use environments, so other variants might use interfaces
## to OOP languages, and proxy objects
setClass("refClassRepresentation",
representation(fieldClasses = "list",
fieldPrototypes = "environment",
refMethods = "environment",
refSuperClasses = "character"),
contains = "classRepresentation", where = envir)
## the virtual class from which all true reference clases
## inherit. Its subclasses require methods
## for getting & setting fields and related tasks
setClassUnion("refClass", where = envir)
## the union of all reference objects
## (including those not belonging to refClass)
setClassUnion("refObject", c("environment", "externalptr", "name", "refClass"),
where = envir)
## a class for field methods, with a slot for their dependencies,
## allowing installation of all required instance methods
setClassUnion("SuperClassMethod", "character")
## helper classes for active binding of fields
setClass("activeBindingFunction", contains = "function")
setClass("defaultBindingFunction",
representation(field = "character", className = "character"),
contains = "activeBindingFunction")
## class to mark uninitialized fields
setClass("uninitializedField",
representation(field = "character", className = "character"))
## class for (internal) ref. methods, with object as function's environment
setClass("refMethodDef",
representation(mayCall = "character", name = "character",
refClassName = "character",
superClassMethod = "SuperClassMethod"),
contains = "function", where = envir)
## and make a traceable version of the class
.makeTraceClass(.traceClassName("refMethodDef"), "refMethodDef", FALSE)
setIs("refMethodDef", "SuperClassMethod", where = envir)
## external ref. methods with explicit .self argument, standard environment
gen <- setClass("externalRefMethod",
slots = c(actual = "function"),
contains = "refMethodDef", where = envir)
assign("externalRefMethod", gen, envir = envir)
setClass("envRefClass", contains = c("environment","refClass"), where =envir)
## bootstrap envRefClass as a refClass
def <- new("refClassRepresentation",
refMethods = as.environment(makeEnvRefMethods()))
as(def, "classRepresentation") <- getClassDef("envRefClass", where = envir)
assignClassDef("envRefClass", def, where = envir)
setMethod("initialize", "envRefClass", methods:::.initForEnvRefClass,
where = envir)
## NOTE: "$" method requires setting in .InitStructureMethods()
setMethod("$", "envRefClass", .dollarForEnvRefClass, where = envir)
setMethod("$<-", "envRefClass", .dollarGetsForEnvRefClass, where = envir)
setMethod("show", "envRefClass",
function(object) object$show())
setClass("refGeneratorSlot") # a temporary virtual class to allow the next definition
## the refClassGenerator class
setClass("refObjectGenerator", representation(generator ="refGeneratorSlot"),
contains = c("classGeneratorFunction", "refClass"), where = envir)
setMethod("$", "refObjectGenerator",
function(x, name) eval.parent(substitute(x@generator$name)), where = envir)
setMethod("$<-", "refObjectGenerator",
function(x, name, value) eval.parent(substitute(x@generator$name <- value)),
where = envir)
## next call is touchy: setRefClass() uses an object of class
## refGeneratorSlot, but the class should have been defined before
## that object is created.
setRefClass("refGeneratorSlot",
fields = list(def = "ANY", className = "ANY"),
methods = .GeneratorMethods, where = envir)
setMethod("show", "refClassRepresentation",
function(object) showRefClassDef(object), where = envir)
setMethod("show", "refObjectGenerator",
function(object) showRefClassDef(object$def, "Generator for class"),
where = envir)
setMethod("show", "refMethodDef", showClassMethod, where = envir)
setMethod("show", "externalRefMethod", showClassMethod, where = envir)
setMethod("initialize", "externalRefMethod",
function(.Object, def, ...) {
.Object@.Data <- eval(substitute(
function(...) {
.f <- DEF
.f(.self, ...)
}, list(DEF = def)))
.Object@actual <- def
callNextMethod(.Object, ...)
}, where = envir)
## Now do "localRefClass"; doesn't need to be precisely here
## but this ensures it is not done too early or too late
setRefClass("localRefClass", methods = .localRefMethods,
where = envir) # should this have contains = "VIRTUAL"?
setMethod("$<-", "localRefClass",
function(x, name, value) {
w <- parent.frame()
x <- .ensureLocal(x, w)
what <- substitute(name)
if (is.symbol(what))
what <- as.character(what)
else what <- name
selfEnv <- as.environment(x)
envRefSetField(x, what, refObjectClass(x), selfEnv, value)
invisible(x)
} , where = envir)
}
getRefSuperClasses <- function(classes, classDefs) {
supers <- character()
for(i in seq_along(classes)) {
clDef <- classDefs[[i]]
supers <- c(supers, clDef@refSuperClasses)
}
unique(supers)
}
.getMethodDefs <- function(what, env) {
methods <- objects(envir = env, all.names = TRUE)
missing <- is.na(match(what, methods))
if(any(missing)) {
warning(gettextf(
"Methods not found: %s", paste(dQuote(methods[missing]), collapse = ", ")))
what <- what[!missing]
}
if(length(what) < 1)
return(NULL)
else if(length(what) == 1)
get(what, envir = env)
else
lapply(what, function(x) get(x, envir = env))
}
.GeneratorMethods <- list(methods = function(...) {
methodsEnv <- def@refMethods
if(nargs() == 0)
return(sort(names(methodsEnv)))
methodDefs <- list(...)
if(nargs() == 1 && is(methodDefs[[1]], "character"))
return(.getMethodDefs(methodDefs[[1]], methodsEnv))
if(methods:::.classDefIsLocked(def))
stop(gettextf("the definition of class %s in package %s is locked, methods may not be redefined",
dQuote(def@className),
sQuote(def@package)),
domain = NA)
## allow either name=function, ... or a single list
if(length(methodDefs) == 1 && is.list(methodDefs[[1]]))
methodDefs <- methodDefs[[1]]
## append existing local methods, so they are re-analysed for new method names
methodDefs <- c(methodDefs, .thisClassMethods(methodsEnv, def@className))
mnames <- names(methodDefs)
if(is.null(mnames) || !all(nzchar(mnames)))
stop("arguments to methods() must be named, or one named list")
## look for methods to remove (new definition is NULL)
removeThese <- vapply(methodDefs, is.null, NA)
if(any(removeThese)) {
rmNames <- mnames[removeThese]
mnames <- mnames[!removeThese]
methodDefs <- methodDefs[!removeThese]
remove(list = rmNames, envir = methodsEnv)
if(length(mnames) == 0)
return(invisible(methodsEnv))
}
allMethods <- as.list(methodsEnv)
## get a list of processed methods, plus any
## overriden superclass methods
newMethods <- insertClassMethods(allMethods, className, methodDefs, names(def@fieldClasses), FALSE)
for(what in names(newMethods))
assign(what, newMethods[[what]], envir = methodsEnv)
## calls to $methods() only work in package source or
## as load actions. Use the topenv() if that seems like
## the namespace in preparation, or the namespace if available
env <- topenv(parent.frame()); declare <- TRUE
if(!is.null(pkg <- get0(".packageName", envir = env)) && pkg == def@package)
{}
else if(isNamespaceLoaded(def@package))
env <- asNamespace(def@package)
else
declare <- FALSE
if(declare)
utils::globalVariables(names(newMethods), env)
invisible(methodsEnv)
},
fields = function() {
'
Returns the named vector of classes
for the fields in this class. Fields
defined with accessor functions have
class "activeBindingFunction".
'
unlist(def@fieldClasses)
},
new = function(...) {
methods::new(def, ...)
},
help = function(topic) {
'
Prints simple documentation for the method or field
specified by argument topic, which should be the name
of the method or field, quoted or not. With no topic,
prints the definition of the class.
'
if(missing(topic)) {
writeLines(
c('Usage: $help(topic) where topic is the name of a method (quoted or not)',
paste('The definition of class', className, 'follows.')))
methods::show(def)
}
else {
if(is.name(substitute(topic)))
topic <- as.character(substitute(topic))
else
topic <- as.character(topic)
env <- def@refMethods
if(exists(topic, envir = env)) {
writeLines(.refMethodDoc(topic, env))
}
else {
cat(gettextf("topic %s is not a method name in class %s\nThe class definition follows\n",
sQuote(topic),
dQuote(className)))
show(def)
}
}
},
lock = function(...) methods:::.lockRefFields(def, ...),
## define accessor functions, store them in the refMethods environment
## of the class definition.
accessors = function(...) {
firstCap <- function(names) {
firstChars <- substr(names, 1,1)
modChars <- toupper(firstChars)
substr(names, 1, 1) <- modChars
list(get = paste0("get", names), set = paste0("set", names))
}
if(methods:::.classDefIsLocked(def))
stop(gettextf("the definition of class %s in package %s is locked so fields may not be modified",
dQuote(def@className),
sQuote(def@package)),
domain = NA)
fieldNames <- c(...)
methodNames <- firstCap(fieldNames)
getters <- methodNames$get
setters <- methodNames$set
accessors <- list()
for(i in seq_along(fieldNames)) {
what <- fieldNames[[i]]
field <- as.name(what)
CLASS <- def@fieldClasses[[what]]
if(is.null(CLASS))
stop(gettextf("%s is not a field in class %s",
sQuote(what),
dQuote(def@className)),
domain = NA)
accessors[[getters[[i]] ]] <-
eval(substitute(function() X, list(X = field)))
if(CLASS == "ANY")
accessors[[setters[[i]] ]] <-
eval(substitute(function(value) {
value <- as(value, CLASS, strict = FALSE)
X <<- value
invisible(value)
},
list(X = field, CLASS = CLASS)))
else
accessors[[setters[[i]] ]] <-
eval(substitute(function(value) {
X <<- value
invisible(value)
},
list(X = field)))
}
## install the accessors
methods(accessors)
invisible(accessors)
}
)## end{ .GeneratorMethods }
.localRefMethods <-
list(
ensureLocal = function() {
'Ensure that a shallow copy has been made of this object
to localize any further changes. Must be called before any reference
class method modifies a field.
'
methods:::.ensureLocal(.self, parent.frame())
}
)
.makeCall <- function(name, x) {
n <- length(argls <- formals(x))
noDeflt <- if(n > 0) vapply(argls, function(x) !is.name(x) || nzchar(as.character(x)), NA)
if (n) {
arg.names <- names(argls)
}
Call <- paste0("$", name, "(")
for (i in seq_len(n)) {
Call <- paste0(Call, arg.names[i], if (noDeflt[[i]]) " = ")
if (i != n)
Call <- paste0(Call, ", ")
}
paste0(Call, ")\n")
}
`insertFields<-` <- function(fieldList, value) {
newNames <- names(value)
## check for valid overrides of existing field definitions
hasFields <- match(newNames, names(fieldList),0) > 0
if(any(hasFields)) {
for(field in newNames[hasFields])
## the new field class must be a subclass of the old
if(is.na(match(fieldList[[field]], c(extends(value[[field]]),"ANY"))))
stop(gettextf("the overriding class (\"%s\") of field %s is not a subclass of the existing field definition (\"%s\")",
value[[field]],
sQuote(field),
fieldList[[field]]),
domain = NA)
}
fieldList[newNames] <- value
fieldList
}
.bindingMetaName <- function(fieldName)
paste0(".->", fieldName)
.makeActiveBinding <- function(thisField) {
if(is(thisField, "activeBindingFunction"))
thisField
else
new("activeBindingFunction", thisField)
}
.makeDefaultBinding <- function(fieldName, fieldClass, readOnly = FALSE, where) {
metaName <- .bindingMetaName(fieldName)
if(readOnly)
## write-once into the metaName object
f <- eval(substitute(function(value) {
if(missing(value))
dummyFieldName
else {
## this is not eval()ed in this namespace
methods:::.setDummyField(.self, dummyField, dummyClass, thisField, TRUE, value)
value
}
}, list(dummyField = metaName, thisField = fieldName,
dummyClass = fieldClass, dummyFieldName = as.name(metaName))))
else
f <- eval(substitute(function(value) {
if(missing(value))
dummyFieldName
else {
## this is not eval()ed in this namespace
methods:::.setDummyField(.self, dummyField, dummyClass, thisField, FALSE, value)
value
}
}, list(dummyField = metaName, dummyClass = fieldClass,
thisField = fieldName, dummyFieldName = as.name(metaName))))
environment(f) <- where ## <note> Does this matter? </note>
f <- new("defaultBindingFunction", f,
field = fieldName, className = fieldClass)
init <- (if(isVirtualClass(fieldClass))
new("uninitializedField", field = fieldName,
className = fieldClass)
else new(fieldClass))
value <- list(f, init)
names(value) <- c(fieldName, metaName)
value
}
.setDummyField <- function(self, metaName, fieldClass, fieldName, onceOnly, value) {
if(is(value, fieldClass))
value <- as(value, fieldClass, strict = FALSE) # could be more efficient?
else
stop(gettextf(
"invalid assignment for reference class field %s, should be from class %s or a subclass (was class %s)",
sQuote(fieldName), dQuote(fieldClass), dQuote(class(value))),
call. = FALSE)
selfEnv <- as.environment(self)
if(onceOnly) {
if(bindingIsLocked(metaName, selfEnv))
stop(gettextf("invalid replacement: reference class field %s is read-only",
sQuote(fieldName)),
call. = FALSE)
else {
assign(metaName, value, envir = selfEnv)
lockBinding(metaName, selfEnv)
}
}
else
assign(metaName, value, envir = selfEnv)
}
refClassInformation <- function(Class, contains, fields, refMethods, where) {
if(length(contains) > 0) {
superClassDefs <- lapply(contains,
function(what) {
if(is(what, "classRepresentation"))
what
else if(is.character(what))
getClass(what, where = where)
else
stop(gettextf("the 'contains' argument should be the names of superclasses: got an element of class %s",
dQuote(class(what))),
domain = NA)
})
missingDefs <- vapply(superClassDefs, is.null, NA)
if(any(missingDefs))
stop(gettextf("no definition found for inherited class: %s",
paste0('"',contains[missingDefs], '"', collapse = ", ")),
domain = NA)
superClasses <- unlist(lapply(superClassDefs,
function(def) def@className), FALSE)
isRefSuperClass <- vapply(superClassDefs, function(def)
is(def, "refClassRepresentation"), NA)
}
else {
superClassDefs <- list()
superClasses <- character()
isRefSuperClass <- logical()
}
if(!any(isRefSuperClass)) {
superClasses <- c(superClasses, "envRefClass")
isRefSuperClass <- c(isRefSuperClass, TRUE)
superClassDefs[["envRefClass"]] <- getClass("envRefClass", where = where)
}
refSuperClasses <- superClasses[isRefSuperClass]
otherRefClasses <- getRefSuperClasses(refSuperClasses, superClassDefs[isRefSuperClass])
refSuperClasses <- unique(c(refSuperClasses, otherRefClasses))
## process the field definitions. The call from setRefClass
## guarantees that fields is a named list.
fieldNames <- names(fields)
nf <- length(fields)
fieldClasses <- character(nf)
names(fieldClasses) <- fieldNames
fieldPrototypes <- list()
for(i in seq_len(nf)) {
thisName <- fieldNames[[i]]
thisField <- fields[[i]]
## a field definition can be:
## 1. character string name of the class
## 2. a binding function
if(is.character(thisField)) {
if(length(thisField) != 1)
stop(gettextf("a single class name is needed for field %s, got a character vector of length %d",
sQuote(thisName),
length(thisField)),
domain = NA)
if(is.null(getClassDef(thisField, where = where)))
stop(gettextf("class %s for field %s is not defined",
dQuote(thisField),
sQuote(thisName)),
domain = NA)
fieldClasses[[i]] <- thisField
if(thisField != "ANY")
fieldPrototypes <- c(fieldPrototypes,
.makeDefaultBinding(thisName, thisField, where = where))
else
fieldPrototypes[[thisName]] <-
new("uninitializedField", field = thisName,
className = "ANY")
}
else if(is.function(thisField)) {
fieldClasses[[i]] <- "activeBindingFunction"
fieldPrototypes[[thisName]] <- .makeActiveBinding(thisField)
}
else
stop(gettextf("field %s was supplied as an object of class %s; must be a class name or a binding function",
sQuote(thisName),
dQuote(class(thisField))),
domain = NA)
}
## assemble inherited information
fc <- fp <- cm <- list() #; fr <- character()
## assign in reverse order so nearer superclass overrides
for(cl in rev(superClassDefs[isRefSuperClass])) {
fcl <- cl@fieldClasses
fpl <- as.list(cl@fieldPrototypes, all.names = TRUE) # turn env into list
cml <- as.list(cl@refMethods, all.names = TRUE) # ditto
insertFields(fc) <- fcl
fp[names(fpl)] <- fpl
cm[names(cml)] <- cml
}
insertFields(fc) <- fieldClasses
fp[names(fieldPrototypes)] <- fieldPrototypes
## process and insert reference methods
cm <- insertClassMethods(cm, Class, refMethods, names(fc), TRUE)
list(superClasses = superClasses, refSuperClasses = refSuperClasses,
fieldClasses = fc, fieldPrototypes = fp,
refMethods = cm)
}
superClassMethodName <- function(def)
paste(def@name, def@refClassName, sep = "#")
insertClassMethods <- function(methods, Class, value, fieldNames, returnAll) {
## process reference methods, return either the entire updated methods
## or the processed new methods in value, plus superclass versions
theseMethods <- names(value)
prevMethods <- names(methods) # catch refs to inherited methods as well
allMethods <- unique(c(theseMethods, prevMethods))
returnMethods <- if(returnAll) methods else value
check <- TRUE
for(method in theseMethods) {
prevMethod <- methods[[method]] # NULL or superClass method
if(is.null(prevMethod)) {
## kludge because default version of $initialize() breaks bootstrapping of methods package
superClassMethod <- if(identical(method, "initialize"))
"initFields" else ""
}
else if(identical(prevMethod@refClassName, Class))
superClassMethod <- prevMethod@superClassMethod
else {
superClassMethod <- superClassMethodName(prevMethod)
returnMethods[[superClassMethod]] <- prevMethod
}
def <- makeClassMethod(value[[method]], method, Class,
superClassMethod, allMethods)
check <- check && .checkFieldsInMethod(def, fieldNames, allMethods)
returnMethods[[method]] <- def
}
if(is.na(check) && .methodsIsLoaded())
message(gettextf("code for methods in class %s was not checked for suspicious field assignments (recommended package %s not available?)",
dQuote(Class),
sQuote("codetools"))
, domain = NA)
returnMethods
}
## refField <- function(class = "ANY", get = .stdGetField, set = .stdSetField, binding = NULL,
## name = "", where = topenv(parent.frame())) {
## if(isFALSE(set))
## set <- .invalidSetField
## new("refFieldDefinition", fieldName = name, fieldClass = class,
## get = get, set = set, binding = binding)
## }
setRefClass <- function(Class, fields = character(),
contains = character(),
methods = list(),
where = topenv(parent.frame()),
inheritPackage = FALSE,
...) {
fields <- inferProperties(fields, "field")
## theseMethods <- names(methods) # non-inherited, for processing later
## collect the method and field definitions
info <- refClassInformation(Class, contains, fields, methods, where)
## make codetools happy:
superClasses <- refSuperClasses <- fieldClasses <- fieldPrototypes <-
refMethods <- NULL
## think Python's multiple assignment operator
for(what in c("superClasses", "refSuperClasses", "fieldClasses",
"fieldPrototypes", "refMethods"))
assign(what, info[[what]])
## temporarily assign an ordinary class definition
## to allow the checks and defaults from setClass to be applied
## and to get the classGeneratorFunction
## Note: the classGeneratorFunction has the class name, not the explicit definition
classFun <- setClass(Class, contains = superClasses,
where = where, ...)
## now, override the class definiton with the complete definition
classDef <- new("refClassRepresentation",
getClassDef(Class, where = where),
fieldClasses = fieldClasses,
refMethods = as.environment(refMethods),
fieldPrototypes = as.environment(fieldPrototypes),
refSuperClasses = refSuperClasses)
.setObjectParent(classDef@refMethods,
if(inheritPackage) refSuperClasses else NULL, where)
assignClassDef(Class, classDef, where)
generator <- new("refGeneratorSlot")
env <- as.environment(generator)
env$def <- classDef
env$className <- Class
.declareVariables(classDef, where)
value <- new("refObjectGenerator", classFun, generator = generator)
invisible(value)
}
getRefClass <- function(Class, where = topenv(parent.frame())) {
if(is(Class, "refClassRepresentation")) {
classDef <- Class
Class <- classDef@className
}
else if(is.character(Class)) {
classDef <- getClass(Class, where = where)
if(!is(classDef, "refClassRepresentation"))
stop(gettextf("class %s is defined but is not a reference class",
dQuote(Class)),
domain = NA)
}
else
stop(gettextf("class must be a reference class representation or a character string; got an object of class %s",
dQuote(class(Class))),
domain = NA)
generator <- new("refGeneratorSlot")
env <- as.environment(generator)
env$className <- Class
env$def <- classDef
classFun <- classGeneratorFunction(Class, where)
## but, the package is always from the class definition, not the local environment
classFun@package <- classDef@package
new("refObjectGenerator", classFun, generator = generator)
}
refClassFields <- function(Class) {
ClassDef <- getClass(Class)
if(is(ClassDef, "refClassRepresentation"))
ClassDef@fieldClasses
else
stop(gettextf("not a reference class: %s", ClassDef@name),
domain = NA)
}
refClassMethods <- function(Class) {
ClassDef <- getClass(Class)
if(is(ClassDef, "refClassRepresentation"))
value <- as.list(ClassDef@refMethods)
else
stop(gettextf("not a reference class: %s", ClassDef@name),
domain = NA)
## possibly temporary: return methods to pure functions
for(i in seq_along(value))
value[[i]] <- as(value[[i]], "function")
value
}
showClassMethod <- function(object) {
cl <- class(object)
cat("Class method definition")
if(!.identC(cl, "refMethodDef"))
cat(sprintf(" (class %s)", dQuote(cl)))
cat(sprintf(" for method %s()\n", object@name))
if(is(object, "externalRefMethod"))
show(object@actual)
else
show(as(object, "function"))
if(length(object@mayCall))
.printNames("\nMethods used: ", object@mayCall)
}
.printNames <- function(header, names, separateLine = TRUE) {
names <- paste0('"', names, '"')
if(separateLine) {
cat(header, "\n", sep = "")
cat(names, sep = ", ", fill = TRUE, labels = " ")
} else {
cat(header, ": ", sep = "")
cat(names, sep = ", ", fill = TRUE)
}
cat("\n")
}
showRefClassDef <- function(object, title = "Reference Class") {
cat(title," \"", object@className,"\":\n", sep="")
fields <- object@fieldClasses
if(length(fields)) {
printPropertiesList(fields, "Class fields")
locked <- .getLockedFieldNames(object)
if(length(locked))
.printNames("Locked Fields", locked, FALSE)
}
else
cat("\nNo fields defined\n")
methods <- names(object@refMethods)
if(length(methods))
.printNames("\nClass Methods: ", methods)
else
cat ("\nNo Class Methods\n")
supers <- object@refSuperClasses
if(length(supers))
.printNames("Reference Superclasses: ", supers)
}
.assignExpr <- function(e) {
value <- list()
value[[codetools::getAssignedVar(e)]] <- deparse(e, nlines = 1L)
value
}
.mergeAssigns <- function(previous, new) {
for(what in names(new)) {
previous[[what]] <-
if(is.null(previous[[what]])) new[[what]]
else paste(previous[[what]], new[[what]], sep="; ")
}
previous
}
.assignedVars <- function(e) {
locals <- list()
globals <- list()
walker <- codetools::makeCodeWalker(call = function(e, w) {
callto <- e[[1]]
if(is.symbol(callto)) switch(as.character(callto),
"<-" = , "=" = {
locals <<- .mergeAssigns(locals, .assignExpr(e))
},
"<<-" = {
globals <<- .mergeAssigns(globals, .assignExpr(e))
})
for (ee in as.list(e))
if (! missing(ee)) codetools::walkCode(ee, w)
},
leaf = function(e, w) NULL
)
codetools::walkCode(e, walker)
list(locals = locals, globals = globals)
}
.checkFieldsInMethod <- function(methodDef, fieldNames, methodNames) {
if(!.hasCodeTools())
return(NA)
p0q <- function(x) paste0('"', x, '"', collapse = "; ")
if(is(methodDef, "refMethodDef")) {
methodName <- p0q(methodDef@name)
className <- p0q(methodDef@refClassName)
}
else {
methodName <- className <- ""
}
assigned <- .assignedVars(body(methodDef))
locals <- names(assigned$locals)
localsAreFields <- match(locals, fieldNames, 0) > 0
if(any(localsAreFields))
warning(gettextf("local assignment to field name will not change the field:\n %s\n Did you mean to use \"<<-\"? ( in method %s for class %s)",
paste(unlist(assigned$locals)[localsAreFields], collapse="; "), methodName, className),
domain = NA)
globals <- names(assigned$globals)
## check non-fields, but allow to .self (will be an
## error except in $initialize())
globalsNotFields <- is.na(match(globals, c(fieldNames, ".self")))
if(any(globalsNotFields))
warning(gettextf("non-local assignment to non-field names (possibly misspelled?)\n %s\n( in method %s for class %s)",
paste(unlist(assigned$globals)[globalsNotFields], collapse="; "), methodName, className),
domain = NA)
globalsInMethods <- match(globals, methodNames, 0) > 0
if(any(globalsInMethods))
stop(gettextf("non-local assignment to method names is not allowed\n %s\n( in method %s for class %s)",
paste(unlist(assigned$globals)[globalsInMethods], collapse="; "), methodName, className),
domain = NA)
!any(localsAreFields) && !any(globalsNotFields)
}
.refMethodDoc <- function(topic, env) {
f <- get(topic, envir = env)
msg <- c("Call:",.makeCall(topic, f), "")
bb <- body(f)
## look for self-documentation
if(is(bb, "{") && length(bb) > 1 && is(bb[[2]], "character"))
msg <- c(msg, bb[[2]], "")
msg
}
## the locked fields are stored as a hidden object in the fieldPrototypes environment
## but this might change, so the .get, .set functions should be used
.lockedFieldsMetaName <- ".#lockedFields"
.getLockedFieldNames <- function(def) {
env <- def@fieldPrototypes
value <- env[[.lockedFieldsMetaName]]
if(is.null(value))
character()
else
value
}
.setLockedFieldNames <- function(def, value) {
env <- def@fieldPrototypes
env[[.lockedFieldsMetaName]] <- value
value
}
.lockRefFields <- function(def, ...) {
lockedFields <- .getLockedFieldNames(def)
if(nargs()<2)
return(lockedFields)
fields <- c(...)
if(is.character(fields) && all(nzchar(fields))) {}
else
stop("arguments must all be character string names of fields")
if(.classDefIsLocked(def))
stop(gettextf("the definition of class %s in package %s is locked so fields may not be modified",
dQuote(def@className),
sQuote(def@package)),
domain = NA)
env <- def@fieldPrototypes
className <- def@className
for(what in fields) {
if(what %in% lockedFields) {
warning(gettextf("field %s is already locked", sQuote(what)),
domain = NA)
next
}
current <- env[[what]]
if(is.null(current))
stop(gettextf("%s is not a field in class %s",
sQuote(what),
dQuote(className)),
domain = NA)
if(is(current, "activeBindingFunction")) {
if(is(current, "defaultBindingFunction"))
env[[what]] <- .makeDefaultBinding(current@field,
current@className, TRUE, environment(current))[[what]]
else
stop(gettextf("field %s of class %s has a non-default binding and cannot be locked",
sQuote(what),
dQuote(className)),
domain = NA)
}
else {
## capture the current prototype value with a read-only binding function
binding <- .makeDefaultBinding(current@field,
current@className, TRUE, environment(current))
env[[what]] <- binding[[what]]
metaName <- .bindingMetaName(what)
env[[metaName]] <- current
}
lockedFields <- c(lockedFields, what)
}
.setLockedFieldNames(def, lockedFields)
invisible(env)
}
## set ".objectParent" as the parent environment for objects from this ref. class.
## If there are no ref superclasses from another package, it will be "where", normally
## the namespace of this package; otherwise it will be the .objectParent from the
## superclass(es). These must agree.
## Also sets .objectPackage with the package name, for infomation purposes
.setObjectParent <- function(refMethods, refSuperClasses, where) {
env <- empty <- emptyenv()
for(cl in refSuperClasses) {
if(identical(cl, "envRefClass"))
break # finished all application classes
clRefMethods <- getClass(cl)@refMethods
clEnv <- clRefMethods$.objectParent
if(identical(env, empty)) { # use this one
env <- clEnv
pkg <- clRefMethods$.objectPackage
}
else if(!identical(clEnv, env)) {
.nQuote <- function(what) paste0('"', what, '"')
stop(gettextf("Reference superclasses must come from the same package for the environment to be defined: got %s and %s",
.nQuote(clRefMethods$.objectPackage), .nQuote(pkg)))
}
}
if(identical(env, empty)) {
pkg <- where$.packageName
if(is.null(pkg))
pkg <- ".GlobalEnv"
refMethods$.objectParent <- where
refMethods$.objectPackage <- pkg
}
else {
refMethods$.objectParent <- env
refMethods$.objectPackage <- pkg
}
}
## declare field and method names global to avoid spurious
## messages from codetools
.declareVariables <- function(def, env) {
utils::globalVariables(c(names(def@fieldClasses), names(def@refMethods),
".self"),
env)
}
.declaredMethods <- function(method) {
methods <- character()
if(!.hasCodeTools())
return(methods)
.theseMethods <- function(e, w) {
if(length(e) < 2) character()
else
sapply(as.list(e)[-1], function(what)
methods <<- c(methods, if(is.symbol(what)) as.character(what) else if(is.character(what)) what else character()))
}
walker <- codetools::makeCodeWalker(
handler = function(v, w) {
if(identical(v, "usingMethods"))
.theseMethods
else
NULL
},
leaf = function(e, w) NULL)
codetools::walkCode(body(method), walker)
unique(methods)
}
getMethodsAndAccessors <- function(Class) {
def <- getClass(Class)
if(!is(def, "refClassRepresentation"))
stop(gettextf("%s is not a reference class",
dQuote(def@className)))
ff <- def@fieldPrototypes
accs <- vapply(ff, function(what) is(what, "activeBindingFunction") && !is(what, "defaultBindingFunction"), NA)
c(as.list(def@refMethods), as.list(ff)[accs])
}
## Reference classes that guarantee to change fields only in the
## local environment. The method for `$<-` checks that the lhs object
## has been registered in a list of local reference class objects in
## the frame where the call is evaluated. If not, a shallow copy
## of the object's .self (environment) is made, replaces the variable
## and is registered. The effect should be that locality of assignment
## is preserved wtihout the deep copy generated by the R evaluator
## for complex assignments that are not primitives, e.g., `@<-`
.ensureLocal <- function(object, where) {
if(!is(object, "envRefClass"))
stop(gettextf("Class %s is not a subclass of %s; functional semantics not defined for this class", dQuote(class(object)), dQuote("envRefClass")))
selfEnv <- as.environment(object)
if(exists(".localRefObjects", envir = where, inherits = FALSE)) {
locals <- get(".localRefObjects", envir = where)
for(i in rev(seq_along(locals)))
if(identical(selfEnv, locals[[i]]))
return(object)
}
else
locals <- list()
## the object should be assigned in environment where=
what <- NULL
for(obj in as.list(where, all.names=TRUE)) {
if(is(obj, "envRefClass") && identical(selfEnv, as.environment(obj))) {
what <- obj
break
}
}
if(is.null(what))
stop("Could not find local object in supplied environment")
## do a shallow copy and record it as local
value <- .shallowCopy(object, selfEnv)
locals[[length(locals)+1]] <- as.environment(value)
assign(".localRefObjects", locals, envir = where)
value
}
## a shallow copy of a reference object
## This code depends on knowledge of how classes extend "environment"
.shallowCopy <- function(object, selfEnv) {
newEnv <- list2env(as.list(selfEnv, all.names=TRUE), hash=TRUE)
attr(object, ".xData") <- newEnv
assign(".self", object, envir = newEnv)
object
}
## return a list of all the methods from this class previously stored in
## the class's methods environment
.thisClassMethods <- function(methodsEnv, className) {
value <- list()
for(what in names(methodsEnv)) {
def <- get(what, envir = methodsEnv)
if(is(def, "refMethodDef") && def@refClassName == className)
value[[what]] <- def@.Data # the function only
}
value
}