The first thing you will want to do is create the classes that will implement your application. This chapter describes class creation protocol, including some special features that can apply to all instances of a class.
Subclass Creation
explains how to define new GemStone classes, class implementation formats and other ways classes can store data.
Creating Classes With Invariant Instances
describes how to make objects invariant.
Creating Classes with Special Cases of Persistence
explains how classes can be defined so that their instances or instance variables are not stored in the repository.
Customer-defined Special classes
describes how to customize the GemStone-supplied special classes to define your own special classes.
Almost every class in the GemStone system understands a message that causes it to create a subclass of itself.
Object subclass: 'Animal'
instVarNames: #('habitat' 'name' 'predator')
classVars: #('AllAnimals')
classInstVars: #('AllOfSpecies')
poolDictionaries: #()
inDictionary: UserGlobals
This subclass creation message establishes a name ('Animal') for the new class and provides for three named instance variables ('habitat', 'name', and 'predator'), a class variable ('AllAnimals'), and a class instance variable ('AllOfSpecies'). The new class is installed in the symbolDictionary UserGlobals of the user who executes this code. You may also include reference to poolDictionaries, if this is useful for your application. Pool dictionaries are included by value, not by name; in other words, you use the reference to the pool dictionary, not a String.
The String used for the new class’s name must follow the general rule for variable names — that is, it must begin with an alphabetic character and its length must not exceed 1024 characters.
There are a number of subclass creation methods. The first keyword (in the example above, subclass:) defines the implementation format — more on this in the next section. Subclass creation methods with additional keywords are provided to provide other information to use when creating the class.
Some GemStone server classes cannot be subclassed. This is an attribute of the class. Execute class subclassesDisallowed to determine if a specific class can be subclassed.
Objects typically encapsulate data and behavior. The behavior is defined as methods on a class and the data is stored in the object. The data may be stored in named instance variables, indexed instance variables (Collection elements), or by value in specialized internal structures.
The implementation format refers to how the basic structure of the objects are defined by the class, which is done when the class is created. Implementation may be inherited from the superclass, or by using specific subclass creation methods you can specify the implementation format of the class.
Many types of objects have named instance variables, but no indexable variables. Objects may have up to 2030 named instance variables, which are referred to by name in the code for that class. This limit includes all inherited instance variables as well as instance variables defined by the class.
This is the default format; subclass creation methods that begin with the subclass: keyword will create classes of this format, if another format is not inherited.
Indexable objects have a variable number of elements, essentially instance variables that are referenced by an Integer index; these are may be referred to as indexed instance variables, varying instance variables, or unnamed instance variables. The number of an object’s indexed instance variables can increase dynamically at run time, up to 240-1 (about a trillion). There are two general cases of indexable objects:
Pointer-format
Pointer-format indexable objects allow the instance variables to refer to any other object. Pointer-format objects may also have named instance variables.
Subclass creation methods that create indexed classes with pointer objects begin with the keyword indexableSubclass:.
Byte-format
This format is used for objects with indexed instance variables that are specialized for storing byte values, SmallIntegers in the range 0...255. Byte-format objects may not have named instance variables.
Subclass creation methods that create byte indexable classes begin with byteSubclass:.
You may not create byte-indexable subclasses of pointer-indexable classes, nor vice-versa, nor can you create indexable subclasses of NSCs.
These classes store data with neither names nor indexes. They are suited to applications in which access is by value, rather than by name or position. Classes with this format are subclasses of UnorderedCollection, and are the classes for which Indexes are implemented.
You cannot directly define classes with this format, although you can subclass from existing kernel classes. Subclasses of NSC classes may have named instance variables, but not indexed instance variables.
Instances of a number of kernel classes are encoded entirely in the object identifier. Special objects do not use up an object ID (i.e., are not in the object table), do not take up separate space in the repository (beyond the original reference itself), and equal values always compare as identical.
Specials include:
Character, Boolean, UndefinedObject,
SmallInteger, SmallDouble, SmallFraction,
SmallScaledDecimal, SmallDate, SmallTime, SmallDateAndTime
Many of these have a limited range in which instances are special, and a corresponding non-special class for out of range values.
You may create a limited number of your own special Classes, by modifying template classes to encode your application data with 56 bits of the OOP value. This is described under Customer-defined Special classes.
The implementation formats defined in the last section define several types of instance variables. Class definitions also include the following variable types:
Class variables
A class variable is a variable whose name and value are shared by a class, all of its instances, its subclasses, and all of their instances. Both class and instance methods of the class and its subclasses can refer to the variable. You can think of these variables as falling somewhere between local and global in their scope.
Class instance variables
A class instance variable is a variable whose name and value are shared by a class, but not by its instances. Subclasses inherit the variable’s name but not its value. Only class methods of a class and its subclasses can refer to class instance variables. Class instance variables are useful when a class and its subclasses need to share the same structure, but not the same value, for a variable.
Pool variables
The pool variables are an Array of SymbolDictionary instances that are searched when attempting to bind a variable name during instance method compilation. Pool variables come after class variables and before globals in precedence. They are typically used when methods in a number of classes share values.
For example, one could define a SymbolDictionary with a key of #'CR' and a value of (Character codePoint: 13). If this SymbolDictionary were included in the class definition as a pool dictionary, then instance methods in the class could use CR as a way to reference the value and make the code more readable.
Global variables
Global variables are not tied to a class. They may be entries in a SymbolDictionary referenced in the UserProfile’s SymbolList.
In addition to the fixed instance variables, which are the same for every instance of that class, you may also add dynamic instance variables to most instances.
Dynamic instance variables are key/value pairs that are stored with the instance like other instance variables, but may be added to specific instances of a class and not to other instances, without changing the class definition.
You cannot add dynamic instance variables to invariant objects, nor to Specials, nor to classes or metaclasses.
The maximum number of dynamic instance variables that can be added to an object is 255. However, the maximum may be lower for classes with many instance variables, since an object cannot be changed to a large object by adding dynamic instance variables. So, more exactly, the actual limit for the number of dynamic instance variables is calculated:
(255 min: ((2034 - self class instSize) / 2)
To add a dynamic instance variable, set the value using:
anObject dynamicInstVarAt: nameSymbol put: value
For example, say you have an instance of Animal representing the Bald Eagle. Bald Eagles are an endangered species, so you might want to add the legal and conservation information to this instance, but not to other instances of Animals.
theBaldEagle dynamicInstVarAt: #legalStatus
put: 'Bald and Golden Eagle Protection Act'.
You can check what dynamic instance variables have been defined for an object:
topaz 1> printit
theBaldEagle dynamicInstanceVariables
%
an Array
#1 legalStatus
and retrieve the stored value for a dynamic instance variable:
topaz 1> printit
theBaldEagle dynamicInstVarAt: #legalStatus
%
Bald and Golden Eagle Protection Act
If the Bald Eagle was no longer protected and this information was no longer needed, you could remove the dynamic instance variable
theBaldEagle removeDynamicInstVar: #legalStatus
The name and data for dynamic instance variables are persisted in the repository like any other instance variable data. Dynamic instance variables allow you to add instance variables to instance of a class, without the need to migrate. However, dynamic instance variables are less efficient than named instance variables, and make for code that is more difficult to maintain.
Note that if you add a dynamic instance variable to an object, it does not impact any existing equality semantics. If the dynamic instance variable should be considered when determining if two objects are equal, you must add or update the implementation of = that applies for that object (which may also involve updating hash); this is not recommended for GemStone kernel classes, and should be done with caution if there may be existing instance, since it can cause collection lookup problems.
In addition to implementation format and variables, there are other features of classes that can be, or must be, defined when the class is created. These are provided via subclass creation methods with additional keywords.
The subclass creation methods follow the form in Example 2.2.
Object subclass: 'Animal'
instVarNames: #('habitat' 'name' 'predator')
classVars: #('AllOfSpecies')
classInstVars: #('AllAnimals')
poolDictionaries: #()
inDictionary: UserGlobals
newVersionOf: Animal
description: 'Class describing Animals'
options: #()
The newVersionOf: allows you to create a new class that has the same classsHistory as an existing class; this is covered in detail in Chapter 10. See Versions of Classes.
The description: keyword allows you to provide documentation as part of the class definition. You can also explicitly set the comment after the class has been created by using the comment: method. For example:
Animal comment: 'Class describing Animal, created for the Programmers Guide'.
The options: keyword allows you to specify a collection of symbols to defined specific features of the new subclass. The options can include any of these:
See DbTransient for details. This option cannot be used in combination with #instancesNonPersistent or #instancesInvariant |
|
All instances of this class will be made invariant as soon as they are committed. If any class is defined with instancesInvariant, all its subclasses must also have instancesInvariant. Cannot be used in combination with #instancesNonPersistent or #dbTransient |
|
See Non-Persistent Classes for details. This option cannot be used in combination with #dbTransient or #instancesInvariant |
|
Log the creation of this class, including if a new class instance/version is not created, to the gem log or linked topaz output using GsFile class>>gciLogServer:. This legacy option is not persistant and not printed; to set class creation logging for all classes, use SessionTemps current at: #GsClass_logCreation put: true. |
|
If this symbol is included, the class remains modifiable after creation. No instances can be created until you make the class unmodifiable by sending it the message immediateInvariant. |
|
If this symbol is included, it must be first, and in this case options are not inherited from the superclass nor from an existing version of the class. This applies to the options #subclassesDisallowed, #disallowGciStore, #traverseByCallback, #dbTransient, #instancesNonPersistent, and #instancesInvariant |
|
This is needed only when modifying superclass hierarchy above classes with special format. It is never inherited. |
|
Note that some of these options are handled in specific ways when new versions of classes are created. Class versioning and history are described in Chapter 11.
For more details on class creation protocol, refer to methods in the image.
In addition to the subclass creation methods described here, there are many other subclass creation methods in the image, including methods with the keywords inClassHistory:, isInvariant:, constraints:, isModifiable:, and instancesInvariant:. These methods are deprecated, and should not be used, although they remain to avoid problems with filing older code into the current image.
For data that must not ever be changed, GemStone provides two ways to make objects invariant or unchangeable. These are object-level invariance, and class-level invariance.
Any object can be made invariant by sending it the message immediateInvariant (a method defined by class Object). This mechanism provides a form of write-protecting objects that is useful for maintaining the integrity of your database. Once immediateInvariant is sent to an object, no modifications can be made to any of the object’s instance variables, nor can the size or class of the object be changed. The immediateInvariant message takes effect immediately, but can be reversed by aborting the transaction in which it was sent. Once the transaction has been committed, you cannot reverse the effect of this message. The message isInvariant returns true if the receiver is invariant; false otherwise.
In class-level invariance, the definition of the class specifies that all instances of the class are invariant. Such an instance can be modified only during the transaction in which it is created. When the transaction is committed, the instance becomes invariant and no further modifications can be made to any of its instance variables, nor can the size or class of the object be changed. This mechanism is useful for supporting literals in methods and in other limited situations, but is generally more cumbersome than object-level invariance.
Class-level invariance can be specified during class creation by including the #instancesInvariant symbol in the options: keyword argument. You cannot also define the class with non-persistent instances (#instancesNonPersistent), nor with non-persistent instances variable data (#dbTransient).
The following example creates a subclass of Animal whose instances are invariant:
In some cases, you may want either objects or the instance variables of objects to not be persistent, that is, not be written to disk. For example, you may want to include session-dependent information that shouldn’t be read by another session, or data that is bulky and can be recreated easily. There are several ways to handle this.
You can define a class as having only non-persistent instances. This means that instances of this class cannot be committed, so you cannot include references to instances of non-persistent classes within a persistent data structure.
To create a class with non-persistent instances, in the options: keyword argument, include the symbol #instancesNonPersistent. You cannot also define the class with non-persistent instances variables (#dbTransient), nor with invariant instances (#instancesInvariant).
As discussed under KeySoftValueDictionary, GemStone provides a class called KeySoftValueDictionary, which allows you to manage non-persistent objects that are large and take time to create, but can be recreated whenever needed from small, readily available objects (tokens).
You cannot commit instances of a non-persistent class. If you attempt to do so, GemStone issues an error that indicates whether the object’s class or a superclass is non-persistent. (The non-persistent status of a class is inherited by all of its subclasses.)
To determine whether a class’s instances are non-persistent, you can send the following message:
theClass instancesNonPersistent
This message returns true if the class is non-persistent, false otherwise.
To make all instances of a class non-persistent, send the message:
theClass makeInstancesNonPersistent
Similarly, send this message to make all instances of a class persistent:
theClass makeInstancesPersistent
To make all instances of a class (and all of its subclasses) non-persistent, even if the class is non-modifiable:
ClassOrganizer makeInstancesNonPersistent: theClass
Similarly, you can send this message to make all instances of a class persistent, even if the class is non-modifiable:
ClassOrganizer makeInstancesPersistent: theClass
Classes can also be defined as DbTransient. Instances of classes that are DbTransient can be committed — that is, there is no error if they are committed — but their instance variables are not written to disk. This is useful if you need to encapsulate objects that should not be persistent, such as semaphores, within object structures that do need to be persistent and shared.
To create a class with DbTransient instances, in the options: keyword argument, include the symbol #dbTransient. You cannot also define the class with non-persistent instances (#instancesNonPersistent), nor with invariant instances (#instancesInvariant).
When a data structure containing an instance of a DbTransient class is committed, the instance variables of the DbTransient object are written to the repository as nil. Whenever a DbTransient object is read into a session, all of its instance variables are nil.
Since DbTransient instances are stored only in memory, they are affected by the in-memory GC operations. (See Managing VM Memory. Also see Chapter 11 of the System Administration Guide.)
If memory becomes low, the transient objects may be stubbed out of memory. When needed, it is re-read from the repository. However, all the instance variables will be nil after a re-read. To prevent losing non-nil instance variable values, you should keep a reference to DbTransient instances in session state.
Since the DbTransient object will remain in memory while referenced from session state, the reference from session state should be removed when the DbTransient object is no longer needed, to avoid filling up memory and causing an out of memory error.
Note that while DbTransient objects are only committed once (on creation), and so do not normally cause concurrency conflicts, if they are clustered the object will be written (still with all instance variables nil), and could potentially cause a concurrency conflict.
To set a class so all instances are DbTransient, send:
aClass makeInstancesDbTransient
aClass must be a non-indexable pointer class. This will cause any instance of aClass to be DbTransient. The change takes place immediately.
aClass makeInstancesNotDbTransient
will cause instances to be non-DbTransient, that is, allow instance variables to be written to disk.
Special classes are classes in which all the object data is encoded in the OOP value itself. GemStone special classes include Character, SmallInteger, SmallDouble, SmallDateAndTime, and others.
GemStone also provides 16 predefined, customizable Special classes. Each can encode 56 bits of data. You will need to be convert the actual data you are storing into a 56 bit integer, and decode back into the data to support your setter and getter instance variables.
The available classes are named Special56bit0-Special56bit15.
To create an application-specific Special class, you will modify one of these class. It is legal to create an association to this class from the same or another SymbolDictionary, and to modify the GsObjectSecurityPolicy of the class. The new class will have the name you define, but you should leave the association with the original name in the Globals dictionary, for upgrade.
The Special classes include a number of primitives, which are defined on the class, so you are not limited to this location in the hierarchy. These methods are in a catagory ’Base Methods’ and should not be modified. You may add all necessary methods to other categories within the class to support the specific functions you need.
These are the steps to customize a Special class for your application:
1. Create an association in the target SymbolDictionary from the target name classNameString to the specific Special56bitN class.
2. As SystemUser, implement Special56bitN class >> name, and execute specialClass >> changeNameTo: classNameString. If you want to edit this class as a non-SystemUser user, you may change it’s securityPolicy using:
specialClass objectSecurityPolicy: anObjectSecurityPolicy
3. Bit-encode the specific data for your special into the 56 available bits, invoking the Class method value: to create an instance, and the instance method value to read and decode into your specific data type.
Note that care must be taken for fileout and filein, since the base class methods are a mixture of GemStone methods and application-specific methods. There are a number of ways to manage this, depending on your source management tools. The Money example shows a workaround using customizations to base image fileout.
It is allowed to change the superclass of a Special class; the required primitive methods are defined in each class, rather than inherited. The new superclass (and each superclass up through Object) must be created with the #selfCanBeSpecial class creation option.
An example of using this feature is the example at
$GEMSTONE/examples/smalltalk/Money.gs
Which demonstates using Special56bit0 to implement a Special Money class encoding an amount and an integer currency ID to suport US and Candian Dollars, Euro and Yen. Note that this is an example class, and does not provide the necessary currency support for a real-world application.
This example must be filed in as SystemUser. It creates the Money class in the Published dictionary. Methods on the resulting Money class can be edited by a user with access to the DataCuratorObjectSecurityPolicy.