blob: 6f43973e987001ca95d71cc9fdfd6697cc34b59a [file] [log] [blame]
# File src/library/methods/R/oldClass.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/
## assumes oldClass has been defined as a virtual class
setOldClass <- function(Classes, prototype = NULL,
where = topenv(parent.frame()), test = FALSE,
S4Class) {
simpleCase <- is.null(prototype)
mainClass <- Classes[[1L]]
prevDef <- getClassDef(mainClass, where, inherits = FALSE)
if(!missing(S4Class)) {
if(test)
stop("not allowed to have test==TRUE and an S4Class definition")
if(!is(S4Class, "classRepresentation")) {
if(is.character(S4Class)) {
clName <- S4Class
S4Class <- getClass(S4Class)
if(.identC(clName, Classes[[1L]]))
removeClass(clName, where = where) # so Recall() will work
}
else
stop(gettextf("argument 'S4Class' must be a class definition: got an object of class %s",
dQuote(class(S4Class))),
domain = NA)
}
if(!is.null(prototype)) {
S4prototype <- S4Class@prototype
## use the explicit attributes from the supplied argument, else S4prototype
S4Class@prototype <- .mergeAttrs(prototype, S4prototype)
}
## register simple S3 class(es), including main class, if it's not defined already
Recall(Classes, where = where)
return(.S4OldClass(Classes[[1L]], if(length(Classes) > 1) Classes[[2L]] else "oldClass", S4Class, where, prevDef))
}
if(test)
return(.setOldIs(Classes, where))
if(!is.null(prevDef)) {
on.exit(.restoreClass(prevDef, where))
removeClass(mainClass, where = where) # so Recall() will work
}
prevClass <- "oldClass"
S3Class <- character() #will accumulate the S3 classes inherited
## The table of S3 classes, used
## to convert S4 objects in S3 method dispatch.
## TODO: should provide an optional argument to setOldClass()
## to prevednt this conversion if it's not needed
if(is.null(S3table <- where$.S3MethodsClasses)) {
S3table <- new.env()
assign(".S3MethodsClasses", S3table, envir = where)
}
dataPartClass <- NULL
for(cl in rev(Classes)) {
S3Class <- c(cl, S3Class)
if(isClass(cl, where)) {
def <- getClass(cl, where)
if(!extends(def, prevClass)) {
## maybe an object type or other valid data part
cl1 <- .validDataPartClass(cl, where, dataPartClass)
if(is.null(cl1))
stop(gettextf("inconsistent old-style class information for %s; the class is defined but does not extend %s and is not valid as the data part",
dQuote(cl),
dQuote(prevClass)),
domain = NA)
else dataPartClass <- cl1
}
else {
prevP <- def@prototype
if(missing(prototype))
prototype <- prevP # keep track of inherited prototype for use in mainClass
prevS3Class <- attr(prevP, ".S3Class")
if(length(prevS3Class) > length(S3Class)) #implies cl is registered S3 class
S3Class <- prevS3Class
}
}
else {
useP <- TRUE
if(cl != mainClass || simpleCase) {
setClass(cl, contains = c(prevClass, "VIRTUAL"), where = where)
}
else if(isClass(class(prototype)))
setClass(cl, contains = prevClass, prototype = prototype, where = where)
else { #exceptionally, we allow an S3 object from the S3 class as prototype
if(.class1(prototype) != mainClass)
stop(gettextf('the S3 class of the prototype, "%s", is undefined; only allowed when this is the S3 class being registered ("%s")', .class1(prototype), mainClass), domain = NA)
setClass(cl, contains = prevClass, where = where)
useP <- FALSE
}
def <- getClassDef(cl, where)
if(useP) clp <- def@prototype else clp <- prototype
attr(clp, ".S3Class") <- S3Class
def@prototype <- .notS4(clp)
assignClassDef(cl, def, where = where)
## add the class to the table of S3 classes
assign(cl, def, envir= S3table)
}
prevClass <- cl
}
if(!is.null(prevDef)) # cancel error action
on.exit()
}
.restoreClass <- function(def, where) {
cl <- def@className
message(gettextf("restoring definition of class %s", dQuote(cl)),
domain = NA)
if(isClass(cl, where = where))
removeClass(cl, where = where)
assignClassDef(cl, def, where = where)
}
.S4OldClass <- function(Class, prevClass, def,where, prevDef) {
## def is the S4 version of this class def'n, maybe by another class
## name, and may or may not already extend oldClass
curDef <- getClassDef(Class, where) # asserted to be defined
## arrange to restore previous definition if there was one. Also done in setOldClass
## when no S4Class argument supplied
if(!is.null(prevDef)) {
on.exit(.restoreClass(prevDef, where))
removeClass(Class, where = where) # so Recall() will work
}
if(!identical(def@className, curDef@className))
def <- .renameClassDef(def, curDef@className)
## check that any common slots will give a valid S3 object
.validS3Extends(def, curDef)
def@slots[names(curDef@slots)] <- curDef@slots
ext <- c(def@contains, curDef@contains)
## correct ordering & duplicate resolution: copied from .walkClassGraph
distOrder <- sort.list(vapply(ext, function(x) x@distance, 1))
ext <- ext[distOrder]
if(anyDuplicated(names(ext)))
ext <- .resolveSuperclasses(def, ext, where)
def@contains <- ext
oldSupers <- setdiff(names(def@contains), names(curDef@contains))
addSubclass <- function(super) {
superDef <- getClassDef(super, where)
superWhere <- .findOrCopyClass(super, superDef, where, "subclass")
superDef@subclasses[[Class]] <- def@contains[[super]]
assignClassDef(super, superDef, superWhere, TRUE)
}
lapply(oldSupers, addSubclass)
subcls <- curDef@subclasses
if(length(subcls) > 0) {
def@subclasses[names(subcls)] <- subcls
}
proto <- def@prototype
if(is.null(attr(proto, ".S3Class"))) { # no S3 class slot, as will usually be true
attr(proto, ".S3Class") <- if(.identC(prevClass, "oldClass")) Class else S3Class(curDef@prototype)
def@prototype <- proto
}
assignClassDef(Class, def, where = where)
## allow an existing superclass relation to remain (it may have a coerce method)
## Otherwise, create a simple transformation, which relies on consistency
## in the slots.
if(!extends(def, prevClass, maybe = FALSE))
setIs(Class, prevClass, classDef = def, where = where)
slotsMethod <- function(object) NULL
body(slotsMethod) <- substitute({LIST}, list(LIST = def@slots))
setMethod("slotsFromS3", Class, slotsMethod, where = where)
if(!is.null(prevDef)) # cancel error action
on.exit()
}
.validS3Extends <- function(classDef1, classDef2) {
slots2 <- classDef2@slots
if(length(slots2) > 0) {
n2 <- names(slots2)
slots1 <- classDef1@slots
n1 <- names(slots1)
bad <- character()
for(what in n2[match(n2, n1, 0) > 0])
if(!extends(slots1[[what]], slots2[[what]])) {
message(gettextf("slot %s: class %s should extend class %s",
sQuote(what),
dQuote(slots1[[what]]),
dQuote(slots2[[what]])),
domain = NA)
bad <- c(bad, what)
}
if(length(bad)>0)
stop(
gettextf("invalid S4 class corresponding to S3 class: slots in S4 version must extend corresponding slots in S3 version: fails for %s",
paste0('"', bad, '"', collapse = ", ")),
domain = NA)
}
TRUE
}
##.initS3Classes will make this generic, with a method for "oldClass"
slotsFromS3 <- function(object) {
list()
}
utils::globalVariables("CLASS")
.oldTestFun <- function(object) CLASS %in% attr(object, "class")
.oldCoerceFun <- function(from, strict = TRUE) {
if(strict)
stop(gettextf("explicit coercion of old-style class (%s) is not defined", paste(class(from), collapse = ", ")), domain = NA)
from
}
.oldReplaceFun <- function(from, to, value)
stop(gettextf("explicit replacement not defined for as(x, \"%s\") <- value for old-style class %s",
to, dQuote(class(from)[1L])),
domain = NA)
## the inheritance of these S3 classes must be decided on a per-instance
## basis. At one time, there were classes in base/stats that had this
## property, (e.g., POSIXt, POSIX{cl}t) but apparently no longer.
## The possibility is still allowed
## for user-defined S3 classes.
.setOldIs <- function(Classes, where) {
if(length(Classes) != 2)
stop(gettextf("argument 'Classes' must be a vector of two classes; got an argument of length %d", length(Classes)), domain = NA)
for(cl in Classes) {
if(isClass(cl, where)) {
if(!extends(cl, "oldClass"))
warning(gettextf("inconsistent old-style class information for %s (maybe mixing old and new classes?)",
dQuote(cl)), domain = NA)
}
else
setClass(cl, representation("oldClass", "VIRTUAL"), where = where)
}
Class1 <- Classes[[1L]]
for(cl in Classes[-1L]) {
tfun <- .oldTestFun
body(tfun, envir = environment(tfun)) <-
substitute(inherits(object, CLASS), list(CLASS = cl))
setIs(Class1, cl, test = tfun, coerce = .oldCoerceFun,
replace = .oldReplaceFun, where = where)
}
NULL
}
isXS3Class <- function(classDef) {
".S3Class" %in% names(classDef@slots)
}
S3Class <- function(object) {
value <- attr(object, ".S3Class")
if(is.null(value)) {
if(isS4(object)) {
if(is.na(match(".Data", names(getClass(class(object))@slots))))
stop(gettextf("'S3Class' only defined for extensions of %s or classes with a data part: not true of class %s",
dQuote("oldClass"),
dQuote(class(object))),
domain = NA)
class(getDataPart(object))
}
else
class(object)
}
else
value
}
.S3Class <- S3Class # alias for functions with S3Class as an argument
.addS3Class <- function(class, prototype, contains, where) {
for(what in contains) {
whatDef <- getClassDef(what@superClass, package=packageSlot(what))
if(isXS3Class(whatDef))
class <- c(class, attr(whatDef@prototype, ".S3Class"))
}
attr(prototype, ".S3Class") <- unique(class)
prototype
}
"S3Class<-" <- function(object, value) {
if(isS4(object)) {
current <- attr(object, ".S3Class")
if(is.null(current)) {
if(is.na(match(value, .BasicClasses)))
stop(gettextf("'S3Class' can only assign to S4 objects that extend \"oldClass\"; not true of class %s",
dQuote(class(object))),
domain = NA)
mode(object) <- value ## may still fail, a further check would be good
}
else
slot(object, ".S3Class") <- value
}
else
class(object) <- value
object
}
## rename a class definition: needs to change if any additional occurences of class
## name are added, other than the className slot and the super/sub class names
## in the contains, subclasses slots respectively.
.renameClassDef <- function(def, className) {
## oldName <- def@className
validObject(def) # to catch any non-SClassExtension objects
def@className <- className
comp <- def@contains
for(i in seq_along(comp))
comp[[i]]@subClass <- className
def@contains <- comp
comp <- def@subclasses
for(i in seq_along(comp))
comp[[i]]@superClass <- className
def@subclasses <- comp
def
}
## extends() w/o conditional inheritance: used for S3 inheritance, method
## selection on S4 objects
..extendsForS3 <- function(Class)
extends(Class, maybe = FALSE)
## dummy version while generating methods package
.extendsForS3 <- function(Class)
extends(Class)