blob: f268a238ca99c99d763101383ae97b3837b70ec1 [file] [log] [blame]
# File src/library/methods/R/RClassUtils.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2019 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/
testVirtual <-
## Test for a Virtual Class.
## Figures out, as well as possible, whether the class with these properties,
## extension, and prototype is a virtual class.
## Can be forced to be virtual by extending "VIRTUAL". Otherwise, a class is
## virtual only if it has no slots, extends no non-virtual classes, and has a
## NULL Prototype
function(properties, extends, prototype, where)
{
if(length(extends)) {
en <- names(extends)
if(!is.na(match("VIRTUAL", en)))
return(TRUE)
## does the class extend a known non-virtual class?
for(what in en) {
enDef <- getClassDef(what, package=packageSlot(extends[[what]]))
if(!is.null(enDef) && isFALSE(enDef@virtual))
return(FALSE)
}
}
(length(properties) == 0L && is.null(prototype))
}
makePrototypeFromClassDef <-
## completes the prototype implied by
## the class definition.
##
## The following three rules are applied in this order.
##
## If the class has slots, then the prototype for each
## slot is used by default, but a corresponding element in the explicitly supplied
## prototype, if there is one, is used instead (but it must be coercible to the
## class of the slot).
##
## If there are no slots but a non-null prototype was specified, this is returned.
##
## If there is a single non-virtual superclass (a class in the extends list),
## then its prototype is used.
##
## If all three of the above fail, the prototype is `NULL'.
function(slots, ClassDef, extends, where)
{
className <- ClassDef@className
snames <- names(slots)
## try for a single superclass that is not virtual
supers <- names(extends)
## virtual <- NA
dataPartClass <- elNamed(slots, ".Data")
prototype <- ClassDef@prototype
dataPartDone <- is.null(dataPartClass) || is(prototype, dataPartClass)# don't look for data part in supreclasses
## check for a formal prototype object (TODO: sometime ensure that this happens
## at setClass() time, so prototype slot in classRepresentation can have that class
if(!.identC(class(prototype), className) && .isPrototype(prototype)) {
pnames <- prototype@slots
prototype <- prototype@object
}
else
pnames <- names(attributes(prototype))
if(length(slots) == 0L && !is.null(prototype))
return(prototype)
for(i in seq_along(extends)) {
what <- el(supers, i)
exti <- extends[[i]]
if(isFALSE(exti@simple))
next ## only simple contains rel'ns give slots
if(identical(what, "VIRTUAL")) {
## the class is virtual, and the prototype usually NULL
## virtual <- TRUE
} else if(isClass(what, where=packageSlot(exti))) {
cli <- getClassDef(what, package=packageSlot(exti))
slotsi <- names(cli@slots)
pri <- cli@prototype
## once in a while
if(is.null(prototype)) {
prototype <- pri
pnames <- names(attributes(prototype))
## fromClass <- what
}
else if(length(slots)) {
for(slotName in slotsi) {
if(identical(slotName, ".Data")) {
if(!dataPartDone) {
prototype <- setDataPart(prototype, getDataPart(pri), FALSE)
dataPartDone <- TRUE
}
}
else if(is.na(match(slotName, pnames))) {
## possible that the prototype already had this slot specified
## If not, add it now.
slot(prototype, slotName, check=FALSE) <-
attr(pri, slotName)
pnames <- c(pnames, slotName)
}
}
}
else if(!dataPartDone && extends(cli, dataPartClass)) {
prototype <- setDataPart(prototype, pri, FALSE)
dataPartDone <- TRUE
}
}
}
if(length(slots) == 0L)
return(prototype)
if(is.null(prototype))
prototype <- defaultPrototype()
pnames <- names(attributes(prototype))
## watch out for a prototype of this class. Not supposed to happen, but will
## at least for the basic class "ts", and can lead to inf. recursion
pslots <-
if(.identC(class(prototype), className))
names(attributes(unclass(prototype)))
else if(isClass(class(prototype)))
names(getSlots(getClass(class(prototype))))
## else NULL
## now check that all the directly specified slots have corresponding elements
## in the prototype--the inherited slots were done in the loop over extends
if(!is.na(match(".Data", snames))) {
dataPartClass <- elNamed(slots, ".Data")
## check the data part
if(!(isVirtualClass(dataPartClass))) {
if(isClass(class(prototype), where = where)) {
prototypeClass <- getClass(class(prototype), where = where)
OK <- extends(prototypeClass, dataPartClass)
}
else
OK <- FALSE
if(isFALSE(OK))
stop(gettextf("in constructing the prototype for class %s: prototype has class %s, but the data part specifies class %s",
dQuote(className),
dQuote(.class1(prototype)),
dQuote(dataPartClass)),
domain = NA)
}
iData <- -match(".Data", snames)
snames <- snames[iData]
slots <- slots[iData]
}
for(j in seq_along(snames)) {
name <- el(snames, j)
i <- match(name, pnames)
if(is.na(i)) {
## if the class of the j-th element of slots is defined and non-virtual,
## generate an object from it; else insert NULL
slot(prototype, name, check = FALSE) <- tryNew(el(slots, j), where)
}
}
extra <- pnames[is.na(match(pnames, snames)) & !is.na(match(pnames, pslots))]
if(length(extra) && is.na(match("oldClass", supers)))
warning(gettextf("in constructing the prototype for class %s, slots in prototype and not in class: %s",
dQuote(className),
paste(extra, collapse=", ")),
domain = NA)
## now check the elements of the prototype against the class definition
slotDefs <- ClassDef@slots; slotNames <- names(slotDefs)
pnames <- names(attributes(prototype))
pnames <- pnames[!is.na(match(pnames, slotNames))]
check <- rep.int(FALSE, length(pnames))
for(what in pnames) {
pwhat <- slot(prototype, what)
slotClass <- getClassDef(slotDefs[[what]], where)
if(is.null(slotClass) || !extends(class(pwhat), slotClass)) {
if(is.null(pwhat)) { # does this still apply??
}
else if(is(slotClass, "classRepresentation") &&
slotClass@virtual) {} # no nonvirtual prototype;e.g. S3 class
else
check[match(what, pnames)] <- TRUE
}
}
if(any(check))
stop(gettextf("in making the prototype for class %s elements of the prototype failed to match the corresponding slot class: %s",
dQuote(className),
paste(pnames[check],
"(class",
.dQ(slotDefs[match(pnames[check], slotNames)]),
")",
collapse = ", ")),
domain = NA)
prototype
}
newEmptyObject <-
## Utility function to create an empty object into which slots can be
## set. Currently just creates an empty list with class "NULL"
##
## Later version should create a special object reference that marks an
## object currently with no slots and no data.
function()
{
value <- list()
value
}
completeClassDefinition <-
## Completes the definition of Class, relative to the current environment
##
## The completed definition is stored in the session's class metadata,
## to be retrieved the next time that getClass is called on this class,
## and is returned as the value of the call.
function(Class, ClassDef = getClassDef(Class), where, doExtends = TRUE)
{
ClassDef <- .completeClassSlots(ClassDef, where)
immediate <- ClassDef@contains
properties <- ClassDef@slots
prototype <- makePrototypeFromClassDef(properties, ClassDef, immediate, where)
virtual <- ClassDef@virtual
# validity <- ClassDef@validity
# access <- ClassDef@access
# package <- ClassDef@package
extends <- if(doExtends) completeExtends (ClassDef, where = where) else ClassDef@contains
subclasses <- if(doExtends) completeSubclasses(ClassDef, where = where) else ClassDef@subclasses
if(is.na(virtual))
## compute it from the immediate extensions, but all the properties
virtual <- testVirtual(properties, immediate, prototype, where)
## modify the initial class definition object, rather than creating
## a new one, to allow extensions of "classRepresentation"
## Done by a separate function to allow a bootstrap version.
ClassDef <- .mergeClassDefSlots(ClassDef,
slots = properties,
contains = extends,
prototype = prototype,
virtual = virtual,
subclasses = subclasses)
if(any(!is.na(match(names(ClassDef@subclasses), names(ClassDef@contains))))
&& getOption("warn") > 0 ## NEEDED: a better way to turn on strict testing
) {
bad <- names(ClassDef@subclasses)[!is.na(match(names(ClassDef@subclasses), names(ClassDef@contains)))]
warning(gettextf("potential cycle in class inheritance: %s has duplicates in superclasses and subclasses (%s)",
dQuote(Class),
paste(bad, collapse = ", ")),
domain = NA)
}
ClassDef
}
.completeClassSlots <- function(ClassDef, where) {
properties <- ClassDef@slots
## simpleContains <- ClassDef@contains
## Class <- ClassDef@className
## package <- ClassDef@package
ext <- getAllSuperClasses(ClassDef, TRUE)
## ext has the names of all the direct and indirect superClasses but NOT those that do
## an explicit coerce (we can't conclude anything about slots, etc. from them)
if(length(ext)) {
superProps <- vector("list", length(ext)+1L)
superProps[[1L]] <- properties
for(i in seq_along(ext)) {
eClass <- ext[[i]]
if(isClass(eClass, where = where))
superProps[[i+1]] <- getClassDef(eClass, where = where)@slots
}
properties <- unlist(superProps, recursive = FALSE)
## check for conflicting slot names
if(anyDuplicated(allNames(properties))) {
duped <- duplicated(names(properties))
#TEMPORARY -- until classes are completed in place & we have way to match non-inherited slots
properties <- properties[!duped]
# dupNames <- unique(names(properties)[duped])
# if(!is.na(match(".Data", dupNames))) {
# dataParts <- seq_along(properties)[names(properties) == ".Data"]
# dupNames <- dupNames[dupNames != ".Data"]
# ## inherited data part classes are OK but should be consistent
# dataPartClasses <- unique(as.character(properties[dataParts]))
# if(length(dataPartClasses)>1)
# warning("Inconsistent data part classes inherited (",
# paste(dataPartClasses, collapse = ", "),
# "): coercion to some may fail")
# ## remove all but the first .Data
# properties <- properties[-dataParts[-1L]]
# }
# if(length(dupNames)>0) {
# dupClasses <- logical(length(superProps))
# for(i in seq_along(superProps)) {
# dupClasses[i] <- !all(is.na(match(dupNames, names(superProps[[i]]))))
# }
# stop(paste("Duplicate slot names: slots ",
# paste(dupNames, collapse =", "), "; see classes ",
# paste0(c(Class, ext)[dupClasses], collapse = ", ")))
# }
}
}
## ensure that each element of the slots is a valid class reference
undefClasses <- rep.int(FALSE, length(properties))
for(i in seq_along(properties)) {
cli <- properties[[i]]
if(is.null(packageSlot(cli))) {
cliDef <- getClassDef(cli, where)
if(is.null(cliDef))
undefClasses[[i]] <- TRUE
else
packageSlot(properties[[i]]) <- cliDef@package
}
else {
cliDef <- getClassDef(cli)
if(is.null(cliDef))
undefClasses[[i]] <- TRUE
}
}
if(any(undefClasses))
warning(sprintf(gettext("undefined slot classes in definition of %s: %s", domain = "R-methods"),
.dQ(ClassDef@className),
paste0(names(properties)[undefClasses], gettextf("(class %s)", .dQ(unlist(properties, recursive = FALSE)[undefClasses])), collapse = ", ")),
call. = FALSE, domain = NA)
ClassDef@slots <- properties
ClassDef
}
.uncompleteClassDefinition <- function(ClassDef, slotName) {
if(missing(slotName)) {
ClassDef <- Recall(ClassDef, "contains")
Recall(ClassDef, "subclasses")
}
else {
prev <- slot(ClassDef, slotName)
if(length(prev)) {
indir <- vapply(prev, .isIndirectExtension, NA)
slot(ClassDef, slotName) <- slot(ClassDef, slotName)[!indir]
}
ClassDef
}
}
.isIndirectExtension <- function(object) {
is(object, "SClassExtension") && length(object@by) > 0
}
## .mergeSlots <- function(classDef1, classDef2) {
## }
.directSubClasses <- function(ClassDef) {
## no checks for input here:
if(length(sc <- ClassDef@subclasses)) {
names(sc)[vapply(sc, function(cc) cc@distance == 1L, NA)]
} ## else NULL
}
getAllSuperClasses <-
## Get the names of all the classes that this class definition extends.
##
## A utility function used to complete a class definition. It
## returns all the superclasses reachable from this class, in
## depth-first order (which is the order used for matching methods);
## that is, the first direct superclass followed by all its
## superclasses, then the next, etc. (The order is relevant only in
## the case that some of the superclasses have multiple inheritance.)
##
## The list of superclasses is stored in the extends property of the
## session metadata. User code should not need to call
## getAllSuperClasses directly; instead, use getClass()@contains
## (which will complete the definition if necessary).
function(ClassDef, simpleOnly = TRUE) {
temp <- superClassDepth(ClassDef, simpleOnly = simpleOnly)
unique(temp$label[sort.list(temp$depth)])
}
superClassDepth <-
## all the superclasses of ClassDef, along with the depth of the relation
## Includes the extension definitions, but these are not currently used by
## getAllSuperClasses
function(ClassDef, soFar = ClassDef@className, simpleOnly = TRUE)
{
ext <- ClassDef@contains
## remove indirect and maybe non-simple superclasses (latter for inferring slots)
ok <- rep.int(TRUE, length(ext))
for(i in seq_along(ext)) {
exti <- ext[[i]]
if(.isIndirectExtension(exti) ||
(simpleOnly && ! exti @simple))
ok[i] <- FALSE
}
ext <- ext[ok]
immediate <- names(ext)
notSoFar <- is.na(match(immediate, soFar))
immediate <- immediate[notSoFar]
super <- list(label = immediate, depth = rep.int(1, length(immediate)),
ext = ext)
for(i in seq_along(immediate)) {
what <- immediate[[i]]
if(!is.na(match(what, soFar)))
## watch out for loops (e.g., matrix/array have mutual is relationship)
next
exti <- ext[[i]]
soFar <- c(soFar, what)
if(!is(exti, "SClassExtension"))
stop(gettextf("in definition of class %s, information for superclass %s is of class %s (expected \"SClassExtension\")",
dQuote(ClassDef@className),
dQuote(what),
dQuote(class(exti))),
domain = NA)
superClass <- getClassDef(exti@superClass, package = exti@package)
if(is.null(superClass)) {
warning(gettextf("class %s extends an undefined class, %s",
dQuote(ClassDef@className),
dQuote(what)),
domain = NA)
next
}
more <- Recall(superClass, soFar)
whatMore <- more$label
if(!all(is.na(match(whatMore, soFar)))) {
## elminate classes reachable by more than one path
## (This is allowed in the model, however)
ok <- is.na(match(whatMore, soFar))
more$depth <- more$depth[ok]
more$label <- more$label[ok]
more$ext <- more$ext[ok]
whatMore <- whatMore[ok]
}
if(length(whatMore)) {
soFar <- c(soFar, whatMore)
super$depth <- c(super$depth, 1+more$depth)
super$label <- c(super$label, more$label)
super$ext <- c(super$ext, more$ext)
}
}
super
}
selectSuperClasses <-
function(Class, dropVirtual = FALSE, namesOnly = TRUE,
directOnly = TRUE, simpleOnly = directOnly,
where = topenv(parent.frame()))
{
ext <- if(isClassDef(Class))
Class@contains
else if(isClass(Class, where = where))
getClass(Class, where = where)@contains
else stop("'Class' must be a valid class definition or class")
.selectSuperClasses(ext, dropVirtual = dropVirtual, namesOnly = namesOnly,
directOnly = directOnly, simpleOnly = simpleOnly)
}
.selectSuperClasses <- function(ext, dropVirtual = FALSE, namesOnly = TRUE,
directOnly = TRUE, simpleOnly = directOnly)
{
## No argument checking here
addCond <- function(xpr, prev)
if(length(prev)) substitute(P && N, list(P = prev, N = xpr)) else xpr
C <- if(dropVirtual) {
isVirtualExt <- function(x)
getClassDef(x@superClass, package=packageSlot(x))@virtual
quote(!isVirtualExt(exti))
} else expression()
if(directOnly) C <- addCond(quote(length(exti@by) == 0), C)
if(simpleOnly) C <- addCond(quote(exti@simple), C)
if(length(C)) {
F <- function(exti){}; body(F) <- C
(if(namesOnly) names(ext) else ext)[vapply(ext, F, NA, USE.NAMES=FALSE)]
}
else if(namesOnly) names(ext) else ext
}
inheritedSlotNames <- function(Class, where = topenv(parent.frame()))
{
ext <- if(isClassDef(Class))
Class@contains
else if(isClass(Class, where = where))
getClass(Class, where = where)@contains
supcl <- .selectSuperClasses(ext, namesOnly=FALSE) ## maybe simpleOnly = FALSE or use as argument?
supdefs <- lapply(supcl, function(s) {
getClassDef(s@superClass, package=packageSlot(s))
})
unique(unlist(lapply(supdefs, slotNames), use.names=FALSE))
## or just the non-simplified part (*with* names):
## lapply(sapply(supcl, getClassDef, simplify=FALSE), slotNames)
}
isVirtualClass <-
## Is the named class a virtual class? A class is virtual if explicitly declared to
## be, and also if the class is not formally defined.
function(Class, where = topenv(parent.frame())) {
if(isClassDef(Class))
Class@virtual
else if(isClass(Class, where = where))
getClass(Class, where = where)@virtual
else
TRUE
}
assignClassDef <-
## assign the definition of the class to the specially named object
function(Class, def, where = .GlobalEnv, force = FALSE) {
if(!is(def,"classRepresentation"))
stop(gettextf("trying to assign an object of class %s as the definition of class %s: must supply a \"classRepresentation\" object",
dQuote(class(def)),
dQuote(Class)),
domain = NA)
clName <- def@className; attributes(clName) <- NULL
if(!.identC(Class, clName))
stop(gettextf("assigning as %s a class representation with internal name %s",
dQuote(Class),
dQuote(def@className)),
domain = NA)
where <- as.environment(where)
mname <- classMetaName(Class)
if(exists(mname, envir = where, inherits = FALSE) && bindingIsLocked(mname, where)) {
if(force)
.assignOverBinding(mname, def, where, FALSE)
## called this way, e.g., from setIs()
## This is old and bad. Given that the cached version of the class
## will have all the updated info about a class, we should leave
## the locked version alone. But probably too late to fix without
## a lot of flack. (JMC, 2013/10)
else
stop(gettextf("class %s has a locked definition in package %s",
dQuote(Class), sQuote(getPackageName(where))))
}
else
assign(mname, def, where)
if(cacheOnAssign(where)) # will be FALSE for sourceEnvironment's
.cacheClass(clName, def, is(def, "ClassUnionRepresentation"), where)
}
.InitClassDefinition <- function(where) {
defSlots <- list(slots = "list", contains = "list", virtual = "logical",
prototype = "ANY", validity = "OptionalFunction", access = "list",
## the above are to conform to the API; now some extensions
className = "character", package = "character",
subclasses = "list", versionKey = "externalptr", ## or "integer"??
sealed = "logical")
## the prototype of a new class def'n: virtual class with NULL prototype
protoSlots <- list(slots=list(), contains=list(), virtual=NA,
prototype = NULL, validity = NULL,
access = list(), className = character(), package = character(),
subclasses = list(), versionKey = .newExternalptr(),
sealed = FALSE)
proto <- defaultPrototype()
pnames <- names(protoSlots)
for(i in seq_along(protoSlots))
slot(proto, pnames[[i]], FALSE) <- protoSlots[[i]]
classRepClass <- .classNameFromMethods("classRepresentation")
class(proto) <- classRepClass
object <- defaultPrototype()
class(object) <- classRepClass
slot(object, "slots", FALSE) <- defSlots
slot(object, "className", FALSE) <- classRepClass
slot(object, "virtual", FALSE) <- FALSE
slot(object, "prototype", FALSE) <- proto
for(what in c("contains", "validity", "access", "hasValidity", "subclasses",
"versionKey"))
slot(object, what, FALSE) <- elNamed(protoSlots, what)
slot(object, "sealed", FALSE) <- TRUE
slot(object, "package", FALSE) <- getPackageName(where)
## assignClassDef("classRepresentation", object, where)
assign(classMetaName("classRepresentation"), object, where)
## the list of needed generics, initially empty (see .InitStructureMethods)
assign(".NeedPrimitiveMethods", list(), where)
}
.classNameFromMethods <- function(what) {
packageSlot(what) <- "methods"
what
}
.initClassSupport <- function(where) {
setClass("classPrototypeDef", representation(object = "ANY", slots = "character", dataPart = "logical"),
sealed = TRUE, where = where)
setClass(".Other", representation(label = "character"),
sealed = TRUE, where = where) # nonvirtual, nobody's subclass, see testInheritedMethods
## a class and a method for reporting method selection ambiguities
setClass("MethodSelectionReport",
representation(generic = "character", allSelections = "character", target = "character", selected = "character", candidates = "list", note = "character"),
sealed = TRUE, where = where)
setClass("classGeneratorFunction",
representation(className = "character", package = "character"),
contains = "function")
}
newBasic <-
## the implementation of the function `new' for basic classes.
##
## See `new' for the interpretation of the arguments.
function(Class, ...) {
msg <- NULL
value <- switch(Class,
"NULL" = return(NULL), ## can't set attr's of NULL in R
"logical" =,
"numeric" =,
"character" =,
"complex" =,
"double" =,
"integer" =,
"raw" =,
"list" = as.vector(c(...), Class),
"expression" = eval(substitute(expression(...))),
"externalptr" = {
if(nargs() > 1)
stop("'externalptr' objects cannot be initialized from new()")
.newExternalptr()
},
"single" = as.single(c(...)),
## note on array, matrix: not possible to be compatible with
## S-Plus on array, unless R allows 0-length .Dim attribute
"array" = if(!missing(...)) array(...) else structure(numeric(), .Dim =0L),
"matrix" = if (!missing(...)) matrix(...) else matrix(0, 0L, 0L),
# "ts" = ts(...),
# break dependence on package stats
"ts" = if(!missing(...)) stats::ts(...) else
structure(NA, .Tsp = c(1, 1, 1), class = "ts"),
## otherwise:
{
args <- list(...)
if(length(args) == 1L && is(args[[1L]], Class)) {
value <- as(args[[1L]], Class)
}
else if(is.na(match(Class, .BasicClasses)))
msg <- paste0("Calling new() on an undefined and non-basic class (\"",
Class, "\")")
else
msg <-
gettextf("initializing objects from class %s with these arguments is not supported",
dQuote(Class))
}
)
if(is.null(msg))
value
else
stop(msg, domain = NA)
}
## this non-exported function turns on or off
## the use of the S4 type as class prototype
.useS4Prototype <- function(on = TRUE, where = .methodsNamespace) {
if(on)
pp <- .Call(C_Rf_allocS4Object)
else
pp <- list()
.assignOverBinding(".defaultPrototype", where=where, pp, FALSE)
}
defaultPrototype <-
## the starting prototype for a non-virtual class
## Should someday be a non-vector sexp type
function()
.defaultPrototype
reconcilePropertiesAndPrototype <-
## makes a list or a structure look like a prototype for the given class.
##
## Specifically, returns a structure with attributes corresponding to the slot
## names in properties and values taken from prototype if they exist there, from
## `new(classi)' for the class, `classi' of the slot if that succeeds, and `NULL'
## otherwise.
##
function(name, properties, prototype, superClasses, where) {
## the StandardPrototype should really be a type that doesn't behave like
## a vector. But none of the existing SEXP types work. Someday ...
StandardPrototype <- defaultPrototype()
slots <- validSlotNames(allNames(properties))
dataPartClass <- elNamed(properties, ".Data")
dataPartValue <- FALSE
if(!is.null(dataPartClass) && is.null(.validDataPartClass(dataPartClass, where)))
stop(gettextf("in defining class %s, the supplied data part class, %s is not valid (must be a basic class or a virtual class combining basic classes)",
dQuote(name), dQuote(dataPartClass)),
domain = NA)
prototypeClass <- getClass(class(prototype), where = where)
if((!is.null(dataPartClass) || length(superClasses))
&& is.na(match("VIRTUAL", superClasses))) {
## Look for a data part in the superclasses, either an inherited
## .Data slot, or a basic class. Uses the first possibility, warns of conflicts
for(cl in superClasses) {
clDef <- getClassDef(cl, where = where)
if(is.null(clDef))
stop(gettextf("no definition was found for superclass %s in the specification of class %s",
dQuote(cl), dQuote(name)),
domain = NA)
thisDataPart <- .validDataPartClass(clDef, where, dataPartClass)
if(!is.null(thisDataPart)) {
dataPartClass <- thisDataPart
if(!is.null(clDef@prototype)) {
protoClass <- class(clDef@prototype)[1L] # [1]: for (matrix, array)
newObject <-
if (protoClass %in% .AbnormalTypes) {
indirect <- .indirectAbnormalClasses[protoClass]
getClassDef(indirect)@prototype
} else clDef@prototype
dataPartValue <- TRUE
}
}
}
if(length(dataPartClass)) {
if(is.na(match(".Data", slots))) {
properties <- c(list(".Data"= dataPartClass), properties)
slots <- names(properties)
}
else if(!extends(elNamed(properties, ".Data"), dataPartClass))
stop(gettextf("conflicting definition of data part: .Data = %s, superclass implies %s",
dQuote(elNamed(properties, ".Data")),
dQuote(dataPartClass)),
domain = NA)
## pslots <- NULL
if(is.null(prototype)) {
if(dataPartValue)
prototype <- newObject
else if(isVirtualClass(dataPartClass, where = where))
## the equivalent of new("vector")
prototype <- newBasic("logical")
else
prototype <- new(dataPartClass)
prototypeClass <- getClass(class(prototype), where = where)
}
else {
if(extends(prototypeClass, "classPrototypeDef")) {
hasDataPart <- isTRUE(prototype@dataPart)
if(!hasDataPart) {
if(!dataPartValue) # didn't get a .Data object
newObject <- new(dataPartClass)
pobject <- prototype@object
## small amount of head-standing to preserve
## any attributes in newObject & not in pobject
anames <- names(attributes(pobject))
attributes(newObject)[anames] <- attributes(pobject)
prototype@object <- newObject
}
else if(!extends(getClass(class(prototype@object), where = where)
, dataPartClass))
stop(gettextf("a prototype object was supplied with object slot of class %s, but the class definition requires an object that is class %s",
dQuote(class(prototype@object)),
dQuote(dataPartClass)),
domain = NA)
}
else if(!extends(prototypeClass, dataPartClass))
stop(gettextf("a prototype was supplied of class %s, but the class definition requires an object that is class %s",
dQuote(class(prototype)),
dQuote(dataPartClass)),
domain = NA)
}
}
if(is.null(prototype)) { ## non-vector (may extend NULL)
prototype <- StandardPrototype
}
}
## check for conflicts in the slots
allProps <- properties
for(cl in superClasses) {
clDef <- getClassDef(cl, where)
if(is(clDef, "classRepresentation")) {
theseProperties <- getSlots(clDef)
theseSlots <- names(theseProperties)
theseSlots <- theseSlots[theseSlots != ".Data"] # handled already
dups <- !is.na(match(theseSlots, names(allProps)))
for(dup in theseSlots[dups])
if(!extends(elNamed(allProps, dup), elNamed(theseProperties, dup)))
stop(gettextf("Definition of slot %s, in class %s, as %s conflicts with definition, inherited from class %s, as %s",
sQuote(dup),
dQuote(name),
dQuote(elNamed(allProps, dup)),
dQuote(cl),
dQuote(elNamed(theseProperties, dup))),
domain = NA)
theseSlots <- theseSlots[!dups]
if(length(theseSlots))
allProps[theseSlots] <- theseProperties[theseSlots]
}
else
stop(gettextf("class %s extends an undefined class (%s)",
dQuote(name), dQuote(cl)),
domain = NA)
}
undefinedPrototypeSlots <- setdiff(names(prototype), names(allProps))
if (length(undefinedPrototypeSlots) > 0L) {
stop(gettextf("The prototype for class %s has undefined slot(s): %s",
dQuote(name), paste0("'", undefinedPrototypeSlots, "'",
collapse = ", ")))
}
if(is.null(dataPartClass)) {
if(extends(prototypeClass, "classPrototypeDef"))
{}
else {
if(is.list(prototype))
prototype <- do.call("prototype", prototype)
if(is.null(prototype))
prototype <- StandardPrototype
}
}
else {
dataPartDef <- getClass(dataPartClass)
checkDataPart <- !isXS3Class(dataPartDef)
if(checkDataPart)
checkDataPart <-
((is.na(match(dataPartClass, .BasicClasses)) &&
!isVirtualClass(dataPartDef)) || length(dataPartDef@slots))
if(checkDataPart)
stop(gettextf("%s is not eligible to be the data part of another class (must be a basic class or a virtual class with no slots)",
dQuote(dataPartClass)),
domain = NA)
if(extends(prototypeClass, "classPrototypeDef"))
{}
else if(extends(prototypeClass, dataPartClass)) {
if(extends(prototypeClass, "list") && length(names(prototype)))
warning("prototype is a list with named elements (could be ambiguous): better to use function prototype() to avoid trouble.")
}
else if(is.list(prototype))
prototype <- do.call("prototype", prototype)
}
## pnames will be the names explicitly defined in the prototype
if(extends(prototypeClass, "classPrototypeDef")) {
pnames <- prototype@slots
prototype <- prototype@object
if(length(superClasses) == 0L && any(is.na(match(pnames, slots))))
stop(sprintf(ngettext(sum(is.na(match(pnames, slots))),
"named elements of prototype do not correspond to slot name: %s",
"named elements of prototype do not correspond to slot names: %s"),
paste(.dQ(pnames[is.na(match(pnames, slots))]),
collapse =", ")),
domain = NA)
}
else
pnames <- allNames(attributes(prototype))
## now set the slots not yet in the prototype object.
## An important detail is that these are
## set using slot<- with check=FALSE (because the slot will not be there already)
## what <- is.na(match(slots, pnames))
what <- seq_along(properties)
props <- properties[what]
what <- slots[what]
nm <- names(attributes(prototype))
for(i in seq_along(what)) {
propName <- el(what, i)
if(!identical(propName, ".Data") && !propName %in% nm)
# is.null(attr(prototype, propName)))
slot(prototype, propName, FALSE) <- tryNew(el(props, i), where)
}
list(properties = properties, prototype = prototype)
}
tryNew <-
## Tries to generate a new element from this class, but if
## the class is undefined just returns NULL.
##
## For virtual classes, returns the class prototype
## so that the object is valid member of class.
## Otherwise tries to generate a new() object, but in rare
## cases, this might fail if the install() method required
## an argument, so this case is trapped as well.
function(Class, where)
{
ClassDef <- getClassDef(Class, where)
if(is.null(ClassDef))
return(NULL)
else if(isTRUE(ClassDef@virtual))
ClassDef@prototype
else tryCatch(new(ClassDef),
error = function(e) {
value <- ClassDef@prototype
class(value) <- ClassDef@className
value
})
}
empty.dump <- function() list()
isClassDef <- function(object) is(object, "classRepresentation")
showClass <-
## print the information about a class definition.
## If complete==TRUE, include the indirect information about extensions.
function(Class, complete = TRUE, propertiesAreCalled = "Slots")
{
if(isClassDef(Class)) {
ClassDef <- Class
Class <- ClassDef@className
}
else if(complete)
ClassDef <- getClass(Class)
else
ClassDef <- getClassDef(Class)
cat(if(isTRUE(ClassDef@virtual)) "Virtual ",
"Class ", .dQ(Class),
## Show the package if that is non-trivial:
if(nzchar(pkg <- ClassDef@package))
c(" [", if(pkg != ".GlobalEnv") "package" else "in", " \"", pkg,"\"]"),
"\n", sep="")
x <- ClassDef@slots
if(length(x)) {
printPropertiesList(x, propertiesAreCalled)
}
else
cat("\nNo ", propertiesAreCalled, ", prototype of class \"",
.class1(ClassDef@prototype), "\"\n", sep="")
ext <- ClassDef@contains
if(length(ext)) {
cat("\nExtends: ")
showExtends(ext)
}
ext <- ClassDef@subclasses
if(length(ext)) {
cat("\nKnown Subclasses: ")
showExtends(ext)
}
}
printPropertiesList <- function(x, propertiesAreCalled) {
if(length(x)) {
n <- length(x)
cat("\n",propertiesAreCalled, ":\n", sep="")
text <- format(c(names(x), as.character(x)), justify="right")
text <- matrix(text, nrow = 2L, ncol = n, byrow = TRUE)
dimnames(text) <- list(c("Name:", "Class:"), rep.int("", n))
print(text, quote = FALSE)
}
}
showExtends <-
## print the elements of the list of extensions. Also used to print
## extensions recorded in the opposite direction, via a subclass list
function(ext, printTo = stdout())
{
what <- names(ext)
how <- character(length(ext))
for(i in seq_along(ext)) {
eli <- el(ext, i)
if(is(eli, "SClassExtension")) {
how[i] <-
if(length(eli@by))
paste("by class", paste0("\"", eli@by, "\", distance ",
eli@distance, collapse = ", "))
else if(isTRUE(eli@dataPart))
"from data part"
else "directly"
if(!eli@simple) {
if(is.function(eli@test) && !isTRUE(body(eli@test))) {
how[i] <-
paste0(how[i], if(is.function(eli@coerce))
", with explicit test and coerce" else
", with explicit test")
}
else if(is.function(eli@coerce))
how[i] <- paste0(how[i], ", with explicit coerce")
}
}
}
if(isFALSE(printTo))
list(what = what, how = how)
else if(all(!nzchar(how)) || all(how == "directly")) {
what <- paste0('"', what, '"')
if(length(what) > 1L)
what <- c(paste0(what[-length(what)], ","), what[[length(what)]])
cat(file = printTo, what, fill=TRUE)
}
else cat(file = printTo, "\n",
paste0("Class \"", what, "\", ", how, "\n"), sep = "")
}
printClassRepresentation <-
function(x, ...)
showClass(x, propertiesAreCalled="Slots")
## bootstrap definition to be used before getClass() works
possibleExtends <- function(class1, class2, ClassDef1, ClassDef2)
.identC(class1, class2) || .identC(class2, "ANY")
## "Real" definition (assigned in ./zzz.R )
.possibleExtends <-
## Find the information that says whether class1 extends class2,
## directly or indirectly. This can be either a logical value or
## an object containing various functions to test and/or coerce the relationship.
## TODO: convert into a generic function w. methods WHEN dispatch is really fast!
function(class1, class2, ClassDef1 = getClassDef(class1),
ClassDef2 = getClassDef(class2, where = .classEnv(ClassDef1)))
{
if(.identC(class1[[1L]], class2) || .identC(class2, "ANY"))
return(TRUE)
if(is.null(ClassDef1)) # class1 not defined
return(FALSE)
## else
ext <- ClassDef1@contains
if(!is.null(contained <- ext[[class2]]))
contained
else if (is.null(ClassDef2))
FALSE
else { ## look for class1 in the known subclasses of class2
subs <- ClassDef2@subclasses
## check for a classUnion definition, not a plain "classRepresentation"
if(!.identC(class(ClassDef2), "classRepresentation") && isClassUnion(ClassDef2))
## a simple TRUE iff class1 or one of its superclasses belongs to the union
any(c(class1, names(ext)) %in% names(subs))
else {
## class1 could be multiple classes here.
## I think we want to know if any extend
i <- match(class1, names(subs))
i <- i[!is.na(i)]
if(length(i)) subs[[ i[1L] ]] else FALSE
}
}
}
## complete the extends information in the class definition, by following
## transitive chains.
##
## Elements in the immediate extends list may be added and current elements may be
## replaced, either by replacing a conditional relation with an unconditional
## one, or by adding indirect relations.
##
completeExtends <- function(ClassDef, class2, extensionDef, where) {
## check for indirect extensions => already completed
ext <- ClassDef@contains
for(i in seq_along(ext)) {
if(.isIndirectExtension(ext[[i]])) {
ClassDef <- .uncompleteClassDefinition(ClassDef, "contains")
break
}
}
exts <- .walkClassGraph(ClassDef, "contains", where, attr(ext, "conflicts"))
if(length(exts)) {
## ## sort the extends information by depth (required for method dispatch)
## superClassNames <- getAllSuperClasses(ClassDef, FALSE)
## ## FIXME: getAllSuperClassses sometimes misses. Why?
## if(length(superClassNames) == length(exts))
## exts <- exts[superClassNames]
if("oldClass" %in% names(exts) &&
length(ClassDef@slots) > 1L) # an extension of an S3 class
exts <- .S3Extends(ClassDef, exts, where)
}
if(!missing(class2) && length(ClassDef@subclasses)) {
strictBy <- TRUE # FIXME: would like to make this conditional but a safe condition is unknown
subclasses <-
.transitiveSubclasses(ClassDef@className, class2, extensionDef, ClassDef@subclasses, strictBy)
## insert the new is relationship, but without any recursive completion
## (asserted not to be needed if the subclass slot is complete)
for(i in seq_along(subclasses)) {
obji <- subclasses[[i]]
## don't override existing relations
## TODO: have a metric that picks the "closest" relationship
if(!extends(obji@subClass, class2))
setIs(obji@subClass, class2, extensionObject = obji, doComplete = FALSE,
where = where)
}
}
## TODO: move these checks to a tool used by check & conditional on no .S3Class slot
## S3Class <- attr(ClassDef@prototype, ".S3Class")
## if(!is.null(S3Class)) {
## others <- c(ClassDef@className, names(exts))
## others <- others[is.na(match(others, S3Class))]
## if(length(others)>0)
## .checkS3forClass(ClassDef@className, where, others)
## }
exts
}
completeSubclasses <-
function(classDef, class2, extensionDef, where, classDef2 = getClassDef(class2, where)) {
## check for indirect extensions => already completed
ext <- classDef@subclasses
for(i in seq_along(ext)) {
if(.isIndirectExtension(ext[[i]])) {
classDef <- .uncompleteClassDefinition(classDef, "subclasses")
break
}
}
subclasses <- .walkClassGraph(classDef, "subclasses", where)
if(!missing(class2) && length(classDef@contains)) {
strictBy <- TRUE
contains <-
.transitiveExtends(class2, classDef@className, extensionDef, classDef@contains, strictBy)
## insert the new is relationship, but without any recursive completion
## (asserted not to be needed if the subclass slot is complete)
for(i in seq_along(contains)) {
obji <- contains[[i]]
cli <- contains[[i]]@superClass
cliDef <- getClassDef(cli, package=packageSlot(obji))
## don't override existing relations
## TODO: have a metric that picks the "closest" relationship
if(!extends(classDef2, cliDef))
setIs(class2, cli, extensionObject = obji,
doComplete = FALSE, where = where)
}
}
subclasses
}
## utility function to walk the graph of super- or sub-class relationships
## in order to incorporate indirect relationships
.walkClassGraph <- function(ClassDef, slotName, where, conflicts = character())
{
ext <- slot(ClassDef, slotName)
if(length(ext) == 0)
return(ext)
className <- ClassDef@className
## the super- vs sub-class is identified by the slotName
superClassCase <- identical(slotName, "contains")
what <- names(ext)
for(i in seq_along(ext)) { # note that this loops only over the original ext
by <- what[[i]]
if(isClass(by, where = packageSlot(ext[[i]]))) {
byDef <- getClassDef(by, package=packageSlot(ext[[i]]))
exti <- slot(byDef, slotName)
coni <- attr(exti, "conflicts") # .resolveSuperclasses makes this
if(superClassCase && length(coni) > 0) {
conflicts <- unique(c(conflicts, coni))
}
## add in those classes not already known to be super/subclasses
exti <- exti[is.na(match(names(exti), what))]
if(length(exti)) {
if(superClassCase) {
strictBy <- TRUE # FIXME: need to find some safe test allowing non-strict
exti <- .transitiveExtends(className, by, ext[[i]], exti, strictBy)
}
else {
strictBy <- TRUE
exti <- .transitiveSubclasses(by, className, ext[[i]], exti, strictBy)
}
ext <- c(ext, exti)
}
}
else
stop(gettextf("the '%s' list for class %s, includes an undefined class %s",
if(superClassCase) "superClass" else "subClass",
dQuote(className),
dQuote(.className(by))),
domain = NA)
}
what <- names(ext) ## the direct and indirect extensions
if(!all(is.na(match(what, className)))) {
ok <- is.na(match(what, className))
## A class may not contain itself, directly or indirectly
## but a non-simple cyclic relation, involving setIs, is allowed
for(i in seq_along(what)[!ok]) {
exti <- ext[[i]]
if(!is(exti, "conditionalExtension")) {
if(superClassCase) {
whatError <- "contain itself"
}
else {
whatError <- "have itself as a subclass"
}
## this is not translatable
stop(sprintf("class %s may not %s: it contains class %s, with a circular relation back to %s",
dQuote(className), whatError,
dQuote(exti@by),
dQuote(className)),
domain = NA)
}
}
ext <- ext[ok]
}
## require superclasses to be sorted by distance
distOrder <- sort.list(vapply(ext, function(x) x@distance, 1))
ext <- ext[distOrder]
if(superClassCase && (anyDuplicated(what) || length(conflicts) > 0))
ext <- .resolveSuperclasses(ClassDef, ext, where, conflicts)
ext
}
.reportSuperclassConflicts <- function(className, ext, where) {
what <- names(ext)
conflicts <- character()
for(i in seq_along(ext)) {
by <- what[[i]]
## report only the direct superclass from which inconsistencies are inherited
wherei <- packageSlot(ext[[i]])
if(identical(ext[[i]]@distance, 1) && isClass(by, where = wherei)) {
byDef <- getClassDef(by, package=wherei)
exti <- byDef@contains
coni <- attr(exti, "conflicts") # .resolveSuperclasses makes this
if( length(coni) > 0) {
warning(gettextf("class %s is inheriting an inconsistent superclass structure from class %s, inconsistent with %s",
.dQ(className), .dQ(by),
paste(.dQ(coni), collapse = ", ")),
call. = FALSE, domain = NA)
conflicts <- unique(c(conflicts, coni))
}
}
}
newconflicts <- attr(ext, "conflicts")
if(length(newconflicts) > length(conflicts))
warning(gettextf("unable to find a consistent ordering of superclasses for class %s: order chosen is inconsistent with the superclasses of %s",
.dQ(className),
paste(.dQ(setdiff(newconflicts, conflicts)),
collapse = ", ")),
call. = FALSE, domain = NA)
}
.resolveSuperclasses <- function(classDef, ext, where, conflicts = attr(ext, "conflicts")) {
## find conditional extensions, ignored in superclass ordering
.condExts <- function(contains)
vapply(contains, function(x) is(x, "conditionalExtension" ), NA)
.noncondExtsClass <- function(cl) {
if(isClass(cl, where = where) ) {
contains <- getClass(cl, where = where)@contains
names(contains)[!.condExts(contains)]
}
else cl
}
what <- names(ext)
dups <- unique(what[duplicated(what)])
if(length(dups) > 0) {
## First, eliminate all conditional relations, which never override non-conditional
affected <- match(what, dups, 0) > 0
conditionals <- .condExts(ext)
if(any(conditionals)) {
affected[conditionals] <- FALSE
what2 <- what[affected]
dups <- unique(what2[duplicated(what2)])
if(length(dups) == 0) {
## eliminating conditonal relations removed duplicates
if(length(conflicts) > 0)
attr(ext, "conflicts") <- unique(c(conflicts, attr(ext, "conflicts")))
return(ext)
}
## else, go on with conditionals eliminated
}
directSupers <- vapply(classDef@contains, function(x) identical(x@distance, 1), NA)
directSupers <- unique(names(classDef@contains[directSupers]))
## form a list of the superclass orderings of the direct superclasses
## to check consistency with each way to eliminate duplicates
## Once again, conditional relations are eliminated
superExts <- lapply(directSupers, .noncondExtsClass)
names(superExts) <- directSupers
retain = .choosePos(classDef@className, what, superExts, affected)
if(is.list(retain)) {
these <- retain[[2]]
conflicts <- unique(c(conflicts, these)) # append the new conflicts
retain <- retain[[1]]
}
## eliminate the affected & not retained
affected[retain] <- FALSE
ext <- ext[!affected]
}
## even if no dups here, may have inherited some conflicts,
## which will be copied to the contains list.
## FUTURE NOTE (7/09): For now, we are using an attribute for conflicts,
## rather than promoting the ext list to a new class, which may be desirable
## if other code comes to depend on the conflicts information.
attr(ext, "conflicts") <- conflicts
ext
}
classMetaName <-
## a name for the object storing this class's definition
function(name)
methodsPackageMetaName("C", name)
# regexp for matching class metanames; semi-general but assumes the
# meta pattern starts with "." and has no other special characters
.ClassMetaPattern <- function()
paste0("^[.]",substring(methodsPackageMetaName("C",""),2))
##FIXME: C code should take multiple strings in name so paste() calls could be avoided.
methodsPackageMetaName <-
## a name mangling device to simulate the meta-data in S4
function(prefix, name, package = "")
## paste(".", prefix, name, sep="__") # too slow
.Call(C_R_methodsPackageMetaName, prefix, name, package)
## a non-exported regexp that matches methods metanames
## This is quite general and matches all patterns that could be generated
## by calling methodsPackageMetaName() with a sequence of capital Latin letters
## Used by package.skeleton in utils
.methodsPackageMetaNamePattern <- "^[.]__[A-Z]+__"
requireMethods <-
## Require a subclass to implement methods for the generic functions, for this signature.
##
## For each generic, `setMethod' will be called to define a method that throws an error,
## with the supplied message.
##
## The `requireMethods' function allows virtual classes to require actual classes that
## extend them to implement methods for certain functions, in effect creating an API
## for the virtual class. Otherwise, default methods for the corresponding function would
## be called, resulting in less helpful error messages or (worse still) silently incorrect
## results.
function(functions, signature,
message = "", where = topenv(parent.frame()))
{
for(f in functions) {
method <- getMethod(f, optional = TRUE)
if(!is.function(method))
method <- getGeneric(f, where = where)
## this is not eval()ed in this namespace
body(method) <-
substitute(stop(methods:::.missingMethod(FF, MESSAGE,
if(exists(".Method")) .Method),
domain = NA),
list(FF = f, MESSAGE = message))
environment(method) <- .GlobalEnv
setMethod(f, signature, method, where = where)
}
NULL
}
## Construct an error message for an unsatisfied required method.
.missingMethod <- function(f, message = "", method) {
if(nzchar(message))
message <- paste0("(", message, ")")
message <- paste("for function", f, message)
if(is(method, "MethodDefinition")) {
target <- paste(.dQ(method@target), collapse=", ")
defined <- paste(.dQ(method@defined), collapse=", ")
message <- paste("Required method", message, "not defined for signature",
target)
if(!identical(target, defined))
message <- paste(message, ", required for signature", defined)
}
else message <- paste("Required method not defined", message)
message
}
getSlots <- function(x) {
classDef <- if(isClassDef(x)) x else getClass(x)
props <- classDef@slots
value <- as.character(props)
names(value) <- names(props)
value
}
## check for reserved slot names. Currently only "class" is reserved
validSlotNames <- function(names) {
if(is.na(match("class", names)))
names
else
stop("\"class\" is a reserved slot name and cannot be redefined")
}
### utility function called from primitive code for "@"
getDataPart <- function(object, NULL.for.none = FALSE) {
if(typeof(object) == "S4") {
## explicit .Data or .xData slot
## Some day, we may merge both of these as .Data
value <- attr(object, ".Data")
if(is.null(value)) {
value <- attr(object, ".xData")
if(is.null(value) && !NULL.for.none)
stop("Data part is undefined for general S4 object")
}
return(if(identical(value, .pseudoNULL)) NULL else value)
}
temp <- getClass(class(object))@slots
if(length(temp) == 0L)
return(object)
if(is.na(match(".Data", names(temp)))) {
if(NULL.for.none)
return(NULL)
else
stop(gettextf("no '.Data' slot defined for class %s",
dQuote(class(object))),
domain = NA)
}
## else
dataPart <- temp[[".Data"]]
switch(dataPart,
## the common cases, for efficiency
numeric = , vector = , integer = , character = , logical = ,
complex = , list =
attributes(object) <- NULL,
matrix = , array = {
value <- object
attributes(value) <- NULL
attr(value, "dim") <- attr(object, "dim")
attr(value, "dimnames") <- attr(object, "dimnames")
object <- value
},
ts = {
value <- object
attributes(value) <- NULL
attr(value, "ts") <- attr(object, "ts")
object <- value
},
## default:
if(is.na(match(dataPart, .BasicClasses))) {
## keep attributes not corresponding to slots
attrVals <- attributes(object)
attrs <- names(attrVals)
attrs <- attrs[is.na(match(attrs, c("class", names(temp))))]
attributes(object) <- attrVals[attrs]
}
else
## other basic classes have no attributes
attributes(object) <- NULL
)
object
}
setDataPart <- function(object, value, check = TRUE) {
if(check || typeof(object) == "S4") {
classDef <- getClass(class(object))
slots <- getSlots(classDef)
dataSlot <- .dataSlot(names(slots))
if(length(dataSlot) == 1)
dataClass <- elNamed(slots, dataSlot)
else if(check)
stop(gettextf("class %s does not have a data part (a .Data slot) defined",
dQuote(class(object))),
domain = NA)
else # this case occurs in making the methods package. why?
return(.mergeAttrs(value, object))
value <- as(value, dataClass) # note that this is strict as()
if(typeof(object) == "S4") {
if(is.null(value))
value <- .pseudoNULL
attr(object, dataSlot) <- value
return(object)
}
}
.mergeAttrs(value, object)
}
.validDataPartClass <- function(cl, where, prevDataPartClass = NULL) {
if(is(cl, "classRepresentation")) {
ClassDef <- cl
cl <- ClassDef@className
}
else
ClassDef <- getClass(cl, TRUE)
value <- switch(cl,
matrix = , array = cl,
## otherwise
elNamed(ClassDef@slots, ".Data"))
if(is.null(value)) {
if(.identC(cl, "structure"))
value <- "vector"
else if((extends(cl, "vector") || !is.na(match(cl, .BasicClasses))))
value <- cl
else if(extends(cl, "oldClass") && isVirtualClass(cl)) {
}
else if(isTRUE(ClassDef@virtual) &&
length(ClassDef@slots) == 0L &&
length(ClassDef@subclasses) ) {
## look for a union of basic classes
subclasses <- ClassDef@subclasses
what <- names(subclasses)
value <- cl
for(i in seq_along(what)) {
ext <- subclasses[[i]]
##TODO: the following heuristic test for an "original"
## subclass should be replaced by a suitable class (extending SClassExtension)
if(length(ext@by) == 0L && ext@simple && !ext@dataPart &&
is.na(match(what[i], .BasicClasses))) {
value <- NULL
break
}
}
}
}
if(!(is.null(value) || is.null(prevDataPartClass) || extends(prevDataPartClass, value) ||
isVirtualClass(value, where = where))) {
warning(gettextf("more than one possible class for the data part: using %s rather than %s",
.dQ(prevDataPartClass), .dQ(value)), domain = NA)
value <- NULL
}
value
}
.dataSlot <- function(slotNames) {
dataSlot <- c(".Data", ".xData")
dataSlot <- dataSlot[match(dataSlot, slotNames, 0)>0]
if(length(dataSlot) > 1)
stop("class cannot have both an ordinary and hidden data type")
dataSlot
}
.mergeAttrs <- function(value, object, explicit = NULL) {
supplied <- attributes(object)
if(length(explicit))
supplied[names(explicit)] <- explicit
valueAttrs <- attributes(value)
## names are special.
if(length(supplied$names) && length(valueAttrs$names) == 0L) {
if(length(value) != length(object))
length(supplied$names) <- length(value)
}
if(length(valueAttrs)) { ## don't overwrite existing attrs
valueAttrs$class <- NULL ## copy in class if it's supplied
supplied[names(valueAttrs)] <- valueAttrs
} ## else -- nothing to protect
attributes(value) <- supplied
if(isS4(object))
.asS4(value)
else
value
}
.newExternalptr <- function()
.Call(C_R_externalptr_prototype_object)
## modify the list moreExts, currently from class `by', to represent
## extensions instead from an originating class; byExt is the extension
## from that class to `by'
.transitiveExtends <- function(from, by, byExt, moreExts, strictBy) {
what <- names(moreExts)
### if(!strictBy) message("Extends: ",from, ": ", paste(what, collapse = ", "))
for(i in seq_along(moreExts)) {
toExt <- moreExts[[i]]
to <- what[[i]]
toExt <- .combineExtends(byExt, toExt, by, to, strictBy)
moreExts[[i]] <- toExt
}
moreExts
### if(!strictBy) message("Done")
}
.transitiveSubclasses <- function(by, to, toExt, moreExts, strictBy) {
## what <- names(moreExts)
### if(!strictBy) message("Subclasses: ",by, ": ", paste(what, collapse = ", "))
for(i in seq_along(moreExts)) {
byExt <- moreExts[[i]]
byExt <- .combineExtends(byExt, toExt, by, to, strictBy)
moreExts[[i]] <- byExt
}
moreExts
### if(!strictBy) message("Done")
}
.combineExtends <- function(byExt, toExt, by, to, strictBy) {
## construct the composite coerce method, taking into account the strict=
## argument.
f <- toExt@coerce
toExpr <- body(f)
fBy <- byExt@coerce
byExpr <- body(fBy)
## if both are simple extensions, so is the composition
if(byExt@simple && toExt@simple) {
expr <- (if(byExt@dataPart)
substitute({if(strict) from <- from@.Data; EXPR},
list(EXPR = toExpr))
else if(toExt@dataPart)
substitute({from <- EXPR; if(strict) from@.Data},
list(EXPR = byExpr))
else (if(identical(byExpr, quote(from)) && identical(toExpr, quote(from)))
quote(from)
else
substitute({from <- E1; E2}, list(E1 = byExpr, E2 = toExpr))
)
)
body(f, envir = environment(f)) <- expr
}
else {
toExt@simple <- FALSE
if(!identical(byExpr, quote(from)))
body(f, envir = environment(f)) <-
substitute( {from <- as(from, BY, strict = strict); TO},
list(BY = by, TO = toExpr))
}
toExt@coerce <- f
f <- toExt@test
toExpr <- body(f)
byExpr <- body(byExt@test)
## process the test code
if(!isTRUE(byExpr)) {
if(!isTRUE(toExpr))
body(f, envir = environment(f)) <- substitute((BY) && (TO),
list(BY = byExpr, TO = toExpr))
else
body(f, envir = environment(f)) <- byExpr
}
toExt@test <- f
f <- byExt@replace
byExpr <- body(f)
if(!strictBy) {
toDef <- getClassDef(to, package=packageSlot(toExt))
byDef <- getClassDef(by, package=packageSlot(byExt))
strictBy <- is.null(toDef) || is.null(byDef) || toDef@virtual || byDef@virtual
}
if (isVirtualClass(by, .requirePackage(packageSlot(byExt)))) {
skipDef <- getClassDef(by, package=packageSlot(byExt))
skipExt <- skipDef@contains[[to]]
if (!is.null(skipExt)) {
body(f, envir = environment(f)) <-
call("as", body(skipExt@replace), byExt@subClass)
}
} else {
expr <- substitute({
.value <- as(from, BY, STRICT)
as(.value, TO) <- value
value <- .value
BYEXPR
}, list(BY=by, TO = to, BYEXPR = byExpr, STRICT = strictBy))
body(f, envir = environment(f)) <- expr
}
toExt@replace <- f
toExt@by <- toExt@subClass
toExt@subClass <- byExt@subClass
toExt@distance <- toExt@distance + byExt@distance
## the combined extension is conditional if either to or by is conditional
if(is(byExt, "conditionalExtension") && !is(toExt, "conditionalExtension"))
class(toExt) <- class(byExt)
toExt
}
## construct the expression that implements the computations for coercing
## an object to one of its superclasses
## The fromSlots argument is provided for calls from makeClassRepresentation
## and completeClassDefinition,
## when the fromClass is in the process of being defined, so slotNames() would fail
.simpleCoerceExpr <- function(fromClass, toClass, fromSlots, toDef) {
toSlots <- names(toDef@slots)
sameSlots <- (length(fromSlots) == length(toSlots) &&
!any(is.na(match(fromSlots, toSlots))))
if(is.null(packageSlot(toClass))) {
toClass <- toDef@className
if(is.null(packageSlot(toClass))) # is this possible?
packageSlot(toClass) <- toDef@package
}
chClass <- as.character(toClass) # dropping package attrib
if(sameSlots)
substitute({class(from) <- CLASS; from}, list(CLASS = toClass))
else if(length(toSlots) == 0L) {
## either a basic class or something with the same representation
if(is.na(match(chClass, .BasicClasses)))
substitute({ attributes(from) <- NULL; class(from) <- CLASS; from},
list(CLASS = toClass))
else if(isVirtualClass(toDef))
quote(from)
else {
## a basic class; a vector type, matrix, array, or ts
switch(chClass,
matrix = , array = {
quote({.dm <- dim(from); .dn <- dimnames(from)
attributes(from) <- NULL; dim(from) <- .dm
dimnames(from) <- .dn; from})
},
ts = {
quote({.tsp <- tsp(from); attributes(from) <- NULL
tsp(from) <- .tsp; class(from) <- "ts"; from})
},
quote({attributes(from) <- NULL; from})
)
}
}
else {
substitute({ value <- new(CLASS)
for(what in TOSLOTS)
slot(value, what) <- slot(from, what)
value },
list(CLASS = chClass, TOSLOTS = toSlots))
}
}
.simpleReplaceExpr <- function(toDef) {
toSlots <- names(toDef@slots)
substitute({
for(what in TOSLOTS)
slot(from, what) <- slot(value, what)
from
}, list(TOSLOTS = toSlots))
}
## the boot version of newClassRepresentation (does no checking on slots to avoid
## requiring method selection on coerce).
newClassRepresentation <- function(...) {
value <- new("classRepresentation")
slots <- list(...)
slotNames <- names(slots)
for(i in seq_along(slotNames))
slot(value, slotNames[[i]], FALSE) <- slots[[i]]
value
}
## create a temporary definition of a class, but one that is distinguishable
## (by its class) from the real thing. See comleteClassDefinition
.tempClassDef <- function(...) {
value <- new("classRepresentation")
slots <- list(...)
slotNames <- names(slots)
for(i in seq_along(slotNames))
slot(value, slotNames[[i]], FALSE) <- slots[[i]]
value
}
## the real version of newClassRepresentation, assigned in ..First.lib
.newClassRepresentation <- function(...)
new("classRepresentation", ...)
.insertExpr <- function(expr, el) {
if(!is(expr, "{"))
expr <- substitute({EXPR}, list(EXPR = expr))
expr[3L:(length(expr)+1)] <- expr[2L:length(expr)]
expr[[2L]] <- el
expr
}
## utility guaranteed to return only the first string of the class.
## Would not be needed if we dis-allowed S3 classes with multiple strings (or
## if the methods package version of class dropped the extra strings).
.class1 <- function(x) {
cl <- class(x)
if(length(cl) > 1L)
cl[[1L]]
else
cl
}
substituteFunctionArgs <-
function(def, newArgs, args = formalArgs(def), silent = FALSE,
functionName = "a function")
{
if(!identical(args, newArgs)) {
if( !missing(functionName) ) # this style does not allow translation
functionName <- paste("for", functionName)
n <- length(args)
if(n != length(newArgs))
stop(sprintf("trying to change the argument list of %s with %d arguments to have arguments (%s)",
functionName, n, paste(newArgs, collapse = ", ")),
domain = NA)
bdy <- body(def)
## check for other uses of newArgs
checkFor <- newArgs[is.na(match(newArgs, args))]
locals <- all.vars(bdy)
if(length(checkFor) && any(!is.na(match(checkFor, locals))))
stop(sprintf("get rid of variables in definition %s (%s); they conflict with the needed change to argument names (%s)",
functionName,
paste(checkFor[!is.na(match(checkFor, locals))], collapse = ", "),
paste(newArgs, collapse = ", ")), domain = NA)
ll <- vector("list", 2L*n)
for(i in seq_len(n)) {
ll[[i]] <- as.name(args[[i]])
ll[[n+i]] <- as.name(newArgs[[i]])
}
names(ll) <- c(args, newArgs)
body(def, envir = environment(def)) <- substituteDirect(bdy, ll)
if(!silent) {
msg <-
sprintf("NOTE: arguments in definition %s changed from (%s) to (%s)",
functionName,
paste(args, collapse = ", "),
paste(newArgs, collapse = ", "))
message(msg, domain = NA)
}
}
def
}
.makeValidityMethod <- function(Class, validity) {
if(!is.null(validity)) {
if(!is.function(validity))
stop(gettextf("a validity method must be a function of one argument, got an object of class %s",
dQuote(class(validity))),
domain = NA)
validity <- substituteFunctionArgs(validity, "object", functionName = sprintf("validity method for class '%s'", Class))
}
validity
}
# the bootstrap version of setting slots in completeClassDefinition
.mergeClassDefSlots <- function(ClassDef, ...) {
slots <- list(...); slotNames <- names(slots)
for(i in seq_along(slots))
slot(ClassDef, slotNames[[i]], FALSE) <- slots[[i]]
ClassDef
}
## the real version: differs only in checking the slot values
..mergeClassDefSlots <- function(ClassDef, ...) {
slots <- list(...); slotNames <- names(slots)
for(i in seq_along(slots))
slot(ClassDef, slotNames[[i]]) <- slots[[i]]
ClassDef
}
### fix the annoying habit of R giving function definitions the local environment by default
.gblEnv <- function(f) {
environment(f) <- .GlobalEnv
f
}
## a utility for makePrototypeFromClassDef that causes inf. recursion if used too early
..isPrototype <- function(p)is(p, "classPrototypeDef")
## the simple version
.isPrototype <- function(p) .identC(class(p), "classPrototypeDef")
.className <- function(cl) if(is(cl, "classRepresentation")) cl@className else as(cl, "character")
## bootstrap version: all classes and methods must be in the version of the methods
## package being built in the toplevel environment: MUST avoid require("methods") !
.requirePackage <- function(package, mustFind = TRUE)
topenv(parent.frame())
## real version of .requirePackage
..requirePackage <- function(package, mustFind = TRUE) {
value <- package
if(nzchar(package)) {
## lookup as lightning fast as possible:
if (.Internal(exists(package, .Internal(getNamespaceRegistry()),
"any", FALSE)))
value <- getNamespace(package)
else {
if(identical(package, ".GlobalEnv"))
return(.GlobalEnv)
if(identical(package, "methods"))
return(topenv(parent.frame())) # booting methods
}
}
if(is.environment(value))
return(value)
topEnv <- getOption("topLevelEnvironment")
if(is.null(topEnv))
topEnv <- .GlobalEnv
if(!is.null(pkgN <- get0(".packageName", topEnv, inherits=TRUE)) &&
.identC(package, pkgN))
return(topEnv) # kludge for source'ing package code
if(nzchar(package) && require(package, character.only = TRUE)) {}
else {
if(mustFind)
stop(gettextf("unable to find required package %s",
sQuote(package)),
domain = NA)
else
return(NULL)
}
getNamespace(package)
}
.classDefEnv <- function(classDef) {
.requirePackage(classDef@package)
}
## bootstrap version, mustn't fail
.classEnv <- function(Class, default = .requirePackage("methods"), mustFind = TRUE) {
package <- packageSlot(Class)
if(is.null(package)) {
## unconditionally use the methods package
default
}
else
.requirePackage(package)
}
## to be .classEnv() --- currently used in 'Matrix' (via wrapper)
..classEnv <- function(Class, default = .requirePackage("methods"), mustFind = TRUE) {
package <- { if(is.character(Class)) packageSlot(Class) else
## must then be a class definition
Class@package }
if(is.null(package)) {
## use the default, but check that the class is there, and if not
## try a couple of other heuristics
value <- default
def <- getClassDef(Class, value, NULL)
if(is.null(def)) {
value <- .GlobalEnv
def <- getClassDef(Class, value, NULL)
if(is.null(def)) {
value <- .requirePackage("methods")
if(!identical(default, value)) # user supplied default
def <- getClassDef(Class, value, NULL)
}
}
if(is.null(def) && mustFind)
stop(gettextf("unable to find an environment containing class %s",
dQuote(Class)),
domain = NA)
value
}
else
.requirePackage(package)
}
## find a generic function reference, using the package slot if present
## FIXME: this and .classEnv should be combined and implemented in C for speed
## They differ in that .classEnv uses the class metaname when it searches; i.e.,
## they use getClassDef and .getGeneric resp. Also, .getEnv returns baseenv() rather
## than generating an error if no generic found (so getGeneric can return gen'c for prim'ves)
.genEnv <- function(f, default = .requirePackage("methods"), package = "")
{
if(!nzchar(package))
package <- packageSlot(f)
if(is.null(package)) {
## use the default, but check that the object is there, and if not
## try a couple of other heuristics
value <- default
def <- .getGeneric(f, value)
if(is.null(def)) {
value <- .GlobalEnv
def <- .getGeneric(f, value)
if(is.null(def)) {
value <- .requirePackage("methods")
if(!identical(default, value)) # user supplied default
def <- .getGeneric(f, value)
}
}
if(is.null(def))
baseenv()
else
value
}
else
.requirePackage(package)
}
## cache and retrieve class definitions If there is a conflict with
## packages a list of classes will be cached
## See .cacheGeneric, etc. for analogous computations for generics
.classTable <- new.env(TRUE, baseenv())
assign("#HAS_DUPLICATE_CLASS_NAMES", FALSE, envir = .classTable)
## FIXME We've seen duplicated classes in .classTable
.duplicateClassesExist <- function(on) {
value <- get("#HAS_DUPLICATE_CLASS_NAMES", envir = .classTable)
if(nargs())
assign("#HAS_DUPLICATE_CLASS_NAMES", on, envir = .classTable)
value
}
.cacheClass <- function(name, def, doSubclasses = FALSE, env) {
if(!isFALSE(doSubclasses)) # only when is(def, "ClassUnionRepresentation")
.recacheSubclasses(def@className, def, env)
if(!is.null(prev <- .classTable[[name]])) {
newpkg <- def@package
if(is(prev, "classRepresentation")) {
if(identical(prev, def))
return()
pkg <- prev@package # start a per-package list
if(identical(pkg, newpkg)) { # redefinition
## cache for S3, to override possible previous cache
.cache_class(name, .extendsForS3(def))
return(.classTable[[name]] <- def)
}
else if(.simpleDuplicateClass(def, prev))
return()
prev <- list(prev)
names(prev) <- pkg
}
## now prev is a named list of class definitions (>= 1),
## where the names are names of packages (rather: namespaces)
i <- match(newpkg, names(prev))
if(is.na(i))
prev[[newpkg]] <- def
else if(identical(def, prev[[i]]))
return()
else # replace previous
prev[[i]] <- def
def <- prev
if(length(def) > 1L)
.duplicateClassesExist(TRUE)
}
.classTable[[name]] <- def # return()s invisibly
}
## test for identical def, prev class definitions
## An exhaustive test would be very complicated, having to test
## superclasses in detail, prototypes for the slots, etc.
.simpleDuplicateClass <- function(def, prev) {
supers <- names(def@contains)
prevSupers <- names(prev@contains)
if(length(supers) != length(prevSupers) ||
any(is.na(match(supers, prevSupers))))
return(FALSE)
verbose <- getOption("verbose")
S3 <- "oldClass" %in% supers
if(S3) {
## it is possible one of these is inconsistent, but unlikely
## and S3 class attributes have no package so duplicates are useless
return(TRUE)
}
## if there are already duplicate classes, we check duplicates
## for the superclasses
dupsExist <- .duplicateClassesExist()
if(dupsExist) {
dups <- match(supers, multipleClasses(), 0) > 0
if(any(dups)) {
if(verbose)
message(gettextf("Note: some superclasses of class %s in package %s have duplicate definitions. This definition is not being treated as equivalent to that from package %s",
dQuote(def@className),
sQuote(def@package),
sQuote(prev@package)),
domain = NA)
return(FALSE)
}
}
## now check the slots
slots <- names(def@slots)
prevSlots <- names(prev@slots)
if(length(slots) != length(prevSlots) ||
any(is.na(match(slots, prevSlots))))
return(FALSE)
for(what in slots) {
slotClasses <- def@slots
prevClasses <- prev@slots
clWhat <- slotClasses[[what]]
prevWhat <- prevClasses[[what]]
if(!identical(as.character(clWhat), as.character(prevWhat)) ||
(dupsExist && !identical(as.character(packageSlot(clWhat)),
as.character(packageSlot(prevWhat)))))
return(FALSE)
}
if(verbose)
message(gettextf("Note: the specification for class %s in package %s seems equivalent to one from package %s: not turning on duplicate class definitions for this class.",
dQuote(def@className),
sQuote(def@package),
sQuote(prev@package)),
domain = NA)
TRUE
}
.uncacheClass <- function(name, def) {
if(!is.null(prev <- .classTable[[name]])) {
if(is(def, "classRepresentation")) # paranoia: should only be called this way
newpkg <- def@package
else
newpkg <- ""
if(is(prev, "classRepresentation") && identical(prev@package, newpkg) )
return(remove(list = name, envir = .classTable))
i <- match(newpkg, names(prev))
if(!is.na(i))
prev[[i]] <- NULL
else # we might warn about unchaching more than once
return()
if(length(prev) == 0L)
return(remove(list = name, envir = .classTable))
else if(length(prev) == 1L)
prev <- prev[[1L]]
assign(name, prev, envir = .classTable)
}
}
## .getClassesFromCache() and .resolveClassList()
## are the workhorses of class access
## The underlying C code will return name if it is not a character vector
## in the assumption this is a classRepresentation or subclass of that.
## In principle, this could replace the checks on class(name) in getClassDef
## and new(), which don't work for subclasses of classRepresentation anyway.
.getClassesFromCache <- function(name) {
.Call(C_R_getClassFromCache, name, .classTable)
}
## When .simpleGetClassFromCache returns a list, pick the most appropriate
.resolveClassList <- function(value, where, package, resolve.confl = "first",
resolve.msg = TRUE)
{
if(is.null(package))
package <- if(is.character(where)) where
else getPackageName(where, FALSE) # may be ""
pkgs <- names(value)
i <- match(package, pkgs, 0L)
if(i == 0L && package != "methods") ## try 'methods':
i <- match("methods", pkgs, 0L)
if(i > 0L)
value[[i]]
else { ## still NULL -- but we *do* want to return one of the class definitions!
switch(resolve.confl,
"none" = NULL,
"first" = {
if(resolve.msg) {
message(gettextf(
"Found more than one class \"%s\" in cache; using the first, from namespace '%s'",
value[[1]]@className, pkgs[1]), domain=NA)
message("Also defined by ",
paste(sQuote(pkgs[-1]), collapse = " "))
}
value[[1]]
},
"all" = value) # return all, a list
}
}
.getClassFromCache <- function(name, where, package = packageSlot(name),
resolve.confl = "first", resolve.msg = TRUE)
{
value <- .getClassesFromCache(name)
if(is.list(value)) {
## multiple classes with this name -- choose at most one
value <- .resolveClassList(value, where, package, resolve.confl,
resolve.msg)
}
value
}
##' Insert superclass information into all the subclasses of this class.
## Used (in 1 place only) to incorporate inheritance information from classUnions
.recacheSubclasses <- function(class, def, env) {
subs <- def@subclasses
subNames <- names(subs)
for(i in seq_along(subs)) {
what <- subNames[[i]]
subDef <- getClassDef(what, package=packageSlot(subs[[i]]))
if(is.null(subDef))
subDef <- getClassDef(what, env) # may be the case for members of a classUnion
if(is.null(subDef))
warning(gettextf("undefined subclass %s of class %s; definition not updated",
.dQ(what), .dQ(def@className)))
else if(is.na(match(what, names(subDef@contains)))) {
## insert the new superclass to maintain order by distance
cntns <- subDef@contains
cntns[[class]] <- subs[[i]]
cntns <- cntns[sort.list(vapply(cntns, function(x) x@distance, 1))]
subDef@contains <- cntns
.cacheClass(what, subDef, FALSE, env)
}
}
NULL
}
## alternative to .recacheSubclasses, only needed for non-unions
## Inferior in that nonlocal subclasses will not be updated, hence the
## warning when the subclass is not in where
.checkSubclasses <- function(class, def, class2, def2, where, where2) {
where <- as.environment(where)
where2 <- as.environment(where2)
subs <- def@subclasses
subNames <- names(subs)
extDefs <- def2@subclasses
for(i in seq_along(subs)) {
what <- subNames[[i]]
if(.identC(what, class2))
next # catch recursive relations
cname <- classMetaName(what)
if(!is.null(subDef <- get0(cname, envir = where, inherits = FALSE))) {
cwhere <- where
}
else if(!is.null(subDef <- get0(cname, envir = where2, inherits = FALSE))) {
cwhere <- where2
}
else {
## happens (wrongly) in a package which imports 'class' but not 'subclass' from another package
## *and* extends 'class', e.g., by defining a class union with it as member.
## Fact is that at the end, the subclass is seen to be updated fine.
message(gettextf(paste("From .checkSubclasses(): subclass %s of class %s is not local and",
"is not updated for new inheritance information currently;",
"\n[where=%s, where2=%s]"),
.dQ(what), .dQ(class), format(where), format(where2)),
domain = NA)
next
}
extension <- extDefs[[what]]
if(is.null(extension)) # not possible if the setIs behaved?
warning(gettextf("no definition of inheritance from %s to %s, though the relation was implied by the setIs() from %s",
.dQ(what), .dQ(def2@className), .dQ(class)),
call. = FALSE, domain = NA)
else if(is.na(match(class2, names(subDef@contains)))) {
## The only "real action": seems only necessary to be called
## during 'methods' "initializing class and method definitions":
if(isTRUE(as.logical(Sys.getenv("_R_METHODS_SHOW_CHECKSUBCLASSES", "false"))))
message(sprintf(paste( # currently only seen from setClassUnion() -> setIs() ->
"Debugging .checkSubclasses(): assignClassDef(what=\"%s\", *, where=%s, force=TRUE);\n",
"E := environment(): %s; parent.env(E): %s"), what, format(cwhere),
format(E <- environment()), format(parent.env(E))))
subDef@contains[[class2]] <- extension
assignClassDef(what, subDef, cwhere, TRUE)
} # else no action (incl no warning!) at all
}
NULL
}
.removeSuperclassBackRefs <- function(Class, classDef, classWhere)
{
if(length(classDef@contains)) {
superclasses <- names(classDef@contains)
for(what in superclasses) {
cdef <- .getClassFromCache(what, classWhere, resolve.confl = "all")
if(is(cdef, "classRepresentation"))
.removeSubClass(what, Class, cdef)
else if(is.list(cdef))
lapply(cdef, function(cl) .removeSubClass(what, Class, cl))
}
}
NULL
}
## remove subclass from the list of subclasses of class
## in the cache and possibly in the attached package environment
.removeSubClass <- function(class, subclass, cdef) {
if(is.null(cdef)) {}
else {
newdef <- .deleteSubClass(cdef, subclass)
if(!is.null(newdef))
.cacheClass(class, newdef, FALSE, cdef@package)
## the class definition in the search list may have been altered
## (e.g., when classes are created in the global environment_
pname <- cdef@package
if(identical(pname, ".GlobalEnv")) {
pos <- 1
}
else {
pname <- paste0("package:", pname)
pos <- match(pname, search(), 0)
}
if(pos) {
penv <- as.environment(pname)
cmeta <- classMetaName(class)
if(!is.null(cdefp <- penv[[cmeta]])) {
if(subclass %in% names(cdefp@subclasses)) {
newdef <- .deleteSubClass(cdefp, subclass)
if(!is.null(newdef)) {
## unfortunately, assignClassDef assigns the subclass info
## even in a locked binding. Would be nice to change that,
## but probably too much would break.
if(bindingIsLocked(cmeta, penv))
.assignOverBinding(cmeta, newdef, penv, FALSE)
else
penv[[cmeta]] <- newdef
}
}
}
}
}
sig <- signature(from=subclass, to=class)
if(existsMethod("coerce", sig))
.removeCachedMethod("coerce", sig)
if(existsMethod("coerce<-", sig))
.removeCachedMethod("coerce<-", sig)
}
.deleteSubClass <- function(cdef, subclass) {
subclasses <- cdef@subclasses
ii <- match(subclass, names(subclasses), 0)
## the subclass may not be there, e.g., if that class has been
## unloaded.
if(ii > 0) {
cdef@subclasses <- subclasses[-ii]
cdef
}
else
NULL
}
## remove superclass from definition of class in the cache & in environments
## on search list
.removeSuperClass <- function(class, superclass) {
cdef <- getClassDef(class)
if(is.null(cdef)) {}
else {
newdef <- .deleteSuperClass(cdef, superclass)
if(!is.null(newdef))
.cacheClass(class, newdef, FALSE, where)
}
sig <- signature(from=class, to=superclass)
if(existsMethod("coerce", sig))
.removeCachedMethod("coerce", sig)
if(existsMethod("coerce<-", sig))
.removeCachedMethod("coerce<-", sig)
evv <- findClass(class, .GlobalEnv) # what about hidden classes? how to find them?
mname <- classMetaName(class)
for(where in evv) {
if(!is.null(cdef <- where[[mname]])) {
newdef <- .deleteSuperClass(cdef, superclass)
if(!is.null(newdef)) {
assignClassDef(class, newdef, where, TRUE)
## message("deleted ",superclass, " from ",class, "in environment")
}
}
}
NULL
}
.deleteSuperClass <- function(cdef, superclass) {
superclasses <- cdef@contains
ii <- match(superclass, names(superclasses), 0L)
if(ii) {
cdef@contains <- superclasses[-ii]
for(subclass in names(cdef@subclasses))
.removeSuperClass(subclass, superclass)
cdef
}
else
NULL
}
classesToAM <- function(classes, includeSubclasses = FALSE,
abbreviate = 2) {
.mergeMatrices <- function(m1, m2) {
if(nrow(m1) == 0)
return(m2)
dn1 <- dimnames(m1)
dn2 <- dimnames(m2)
rows <- unique(c(dn1[[1]], dn2[[1]]))
columns <- unique(c(dn1[[2]], dn2[[2]]))
value <- matrix(0, length(rows), length(columns), dimnames = list(rows, columns))
value[dn1[[1]], dn1[[2]] ] <- m1
value[dn2[[1]], dn2[[2]] ] <- m2
value
}
if(length(includeSubclasses) == 1)
includeSubclasses <- rep.int(includeSubclasses, length(classes))
if(!is(includeSubclasses, "logical") || length(includeSubclasses) != length(classes))
stop("argument 'includeSubclasses' must be a logical, either one value or a vector of the same length as argument 'classes'")
value <- matrix(0,0,0)
for(i in seq_along(classes)) {
class <- classes[[i]] # to allow for package attribute
classDef <- getClass(class) # throws an error if undefined. Make a warning?
value <- .mergeMatrices(value, .oneClassToAM(classDef, includeSubclasses[[i]]))
}
abbr <- match(as.integer(abbreviate), 0:3)-1
if(length(abbr) != 1 || is.na(abbr))
stop("argument 'abbreviate' must be 0, 1, 2, or 3")
if(abbr %% 2)
dimnames(value)[[1]] <- abbreviate(dimnames(value)[[1]])
if(abbr %/% 2)
dimnames(value)[[2]] <- abbreviate(dimnames(value)[[2]])
value
}
.oneClassToAM <- function(classDef, includeSubclasses = FALSE, short = FALSE) {
findEdges <- function(extensions) {
superclasses <- names(extensions)
edges <- numeric()
for(what in superclasses) {
whatDef <- getClassDef(what, package=packageSlot(extensions[[what]]))
ifrom <- match(what, nodes)
if(is.null(whatDef) || is.na(ifrom))
next
exts <- whatDef@contains
whatedges <- names(exts)
ito <- match(whatedges, nodes, 0)
for(i in seq_along(exts))
if(ito[[i]] >0 && exts[[i]]@distance == 1)
edges <- c(edges, ifrom, ito[[i]])
}
edges
}
nodes <- c(classDef@className, names(classDef@contains))
if(includeSubclasses)
nodes <- c(nodes, names(classDef@subclasses))
nodes <- unique(nodes)
labels <-
if(isTRUE(short)) abbreviate(nodes)
else if(is.character(short)) {
if(length(short) != length(nodes))
stop(gettextf("needed the supplied labels vector of length %d, got %d",
length(nodes), length(short)), domain = NA)
else short
} else nodes
size <- length(nodes)
value <- matrix(0, size, size, dimnames = list(labels, labels))
ifrom <- match(classDef@className, nodes) # well, 1, but just for consistency
## the following could use the current fact that direct superclasses come
## first, but the efficiency gain is minor, so we use the findEdges logic
extensions <- classDef@contains
superclasses <- names(extensions)
ito <- match(superclasses, nodes)
edges <- numeric()
for(i in seq_along(extensions)) {
exti <- extensions[[i]]
if(exti@distance == 1)
edges <- c(edges, ifrom, ito[[i]])
}
edges <- c(edges, findEdges(classDef@contains))
if(includeSubclasses) {
edges <- c(edges, findEdges(classDef@subclasses))
}
edges <- t(matrix(edges, nrow=2))
value[edges] <- 1
value
}
.choosePos <- function (thisClass, superclasses, subNames, affected)
## find if possible a set of superclass relations that gives a consistent
## ordering and eliminates any duplicates in the affected relations
## Note that the returned indices are against the index of superclasses
## If no successful selection is possible, return (one of) the best
## attempt, and the superclass(es) inconsistently embedded
{
candidates <- list()
allNames <- c(thisClass, superclasses)
dups <- unique(superclasses[affected])
whichCase <- names(subNames)
for(what in dups) {
where <- seq_along(allNames)[match( allNames, what,0)>0]
## make a list of all the subsets to remove duplicates
whatRemove <- lapply(-seq_along(where), function(x,y) y[x], y=where)
if(length(candidates) == 0)
candidates <- whatRemove
else # all the pairwise combinations with the previous
candidates <- outer(candidates, whatRemove,
function(x,y)mapply(c,x,y, SIMPLIFY=FALSE))
}
## check each way to make the list unique against each superclass extension
problems <- function(x,y) any(diff(match(y, x)) < 0)
possibles <- lapply(candidates, function(x, names)names[-x], names=allNames)
## the next could be vectorized, but here we choose instead to exit early.
scores <- vector("list", length(possibles))
for(i in seq_along(possibles)) {
score <- vapply(subNames, problems, NA, x=possibles[[i]])
scores[[i]] <- whichCase[score]
if(!any(score))
return(-candidates[[i]]+1)
}
# the first min. scoring possibility and its score
i <- which.min(lengths(scores))
list(-candidates[[i]]+1, scores[[i]])
}
.checkGeneric <- function(what, where) {
.checkFun <- function(x) {
maybe <- if(!is.null(f <- get0(x, where))) is.function(f) else FALSE
if(maybe)
maybe <- is(f, "genericFunction") ||
(length(grep("UseMethod", deparse(f))) > 0) ||
is.primitive(f)
maybe
}
vapply(what, .checkFun, NA)
}
S3forS4Methods <- function(where, checkClasses = character()) {
allClasses <- getClasses(where)
if(length(checkClasses) > 0)
allClasses <- allClasses[match(allClasses, checkClasses, 0) > 0]
if(length(allClasses) == 0)
return(allClasses)
pattern <- paste0("([.]",allClasses, "$)", collapse="|")
allObjects <- names(where)
allObjects <- allObjects[-grep("^[.][_][_]", allObjects)] # remove meta data
allObjects <- grep(pattern, allObjects, value = TRUE)
if(length(allObjects) > 0) {
badMethods <- allObjects
funs <- sub(pattern, "", badMethods)
uniqueFuns <- unique(funs)
uniqueFuns <- uniqueFuns[nzchar(uniqueFuns)]
possible <- .checkGeneric(uniqueFuns, where)
if(!any(possible))
return(character())
uniqueFuns <- uniqueFuns[possible]
badMethods <- badMethods[match(funs, uniqueFuns, 0) > 0]
allObjects <- badMethods
attr(allObjects, "functions") <- uniqueFuns
}
allObjects
}
## ## this function warns of S3 methods for S4 classes, but only once per package
## ## per session.
## .checkS3forS4 <- function(method) {
## envir <- environment(method)
## pkg <- getPackageName(envir)
## if(!nzchar(pkg)) pkg <- getPackageName(parent.env(pkg)) #? if generic function
## if(!nzchar(pkg)) pkg <- format(envir)
## if(!exists(".WarnedS3forS4", .GlobalEnv, inherits = FALSE))
## assign(".WarnedS3forS4", character(), envir = .GlobalEnv)
## if(is.na(match(pkg, .WarnedS3forS4))) {
## methods <- S3forS4Methods(envir)
## .WarnedS3forS4 <<- c(.WarnedS3forS4, pkg)
## if(length(methods) > 0) {
## warning("S3 methods written for S4 classes will fail inheritance!\nPackage ", pkg, " apparently has ",
## length(methods), " such methods for the functions ", paste(attr(methods, "functions"), collapse = ", "), "\n\n",
## "Possible dangerous methods: ", paste(methods, collapse =", "),
## "\n\n(Warnings generated once per package per session)")
## }
## }
## }
## a warning when a class is defined that extends classes with S3 methods.
## .checkS3forClass <- function(className, where, what = className) {
## badMethods <- S3forS4Methods(where, what)
## if(length(badMethods) > 0) {
## msg <- paste0("The apparent methods are ", paste('"',badMethods, '"', collapse = ", "))
## warning("Some of the superclasses in the definition of class \"",
## className, "\" have apparent S3 methods.\n\nThese will be hidden by the S3 class that this class contains. (See ?Methods)\n\n", msg)
## }
## }
## a utility to detect mixin classes: meant to be fast for use in
## initialize methods (cf the "matrix" method in BasicClasses.R)
isMixin <- function(classDef) {
val <- 0
cc <- classDef@contains
## relies on the superclasses in contains slot being ordered by distance
for(cl in cc) {
if(cl@distance > 1 || val > 1)
break
val <- val + 1
}
val > 1
}
.classDefIsLocked <- function(classDef) {
what <- classMetaName(classDef@className)
env <- .NamespaceOrEnvironment(classDef@package)
is.environment(env) && exists(what, envir = env, inherits = FALSE) &&
bindingIsLocked(what, env)
}