17. Working with Classes and Methods

Previous chapter

Next chapter

An object responds to messages defined and stored with its class and its class’s superclasses. The classes named Object, Class, and Behavior are superclasses of every class. Although the mechanism involved may be a little confusing, the practical implication is easy to grasp — every class understands the instance messages defined by Object, Class, and Behavior.

This chapter provides an overview of the Behavior methods that are inherited by all classes, and so can be used to programmatically create and access methods, categories, pool dictionaries and variables for your classes.

Creating and Removing Methods
describes the protocol in class Behavior for adding and removing methods.

Information about Class and Methods
describes the protocol in class Behavior for examining the method dictionary of a class.

ClassOrganizer
describes the protocol in class Behavior for examining, adding, and removing method categories.

Handling Deprecated Methods
How to locate and clean up references to methods that have been deprecated.

17.1 Creating and Removing Methods

Class Behavior defines messages for creating methods and removing methods.

Defining Simple Accessing and Updating Methods

Class Behavior provides an easy way to define simple methods for establishing and returning the values of instance variables. For each instance variable named by a symbol in the argument array, the message compileAccessingMethodsFor: arrayOfSymbols creates one method that sets the instance variable’s value and another method that returns it. These methods are added to the categories “Accessing” (return the instance variable’s value) and “Updating” (set its value).

For example, this invocation of the method:

Animal compileAccessingMethodsFor: #(#name)

has the same effect as the following topaz:

category: 'Accessing'  
method: Animal  
name
	^name
%
category: 'Updating'
method: Animal  
name: aName
    name := aName
%

You can also use compileAccessingMethodsFor: to define class methods for accessing class, class instance and pool variables, by sending compileAccessingMethodsFor: to the class of the class that defines the variables of interest.

The similar method compileMissingAccessingMethods will create accessing methods for any instance variables for which accessor methods with the standard selector do not already exist.

Compiling Methods

Class Behavior defines the basic method for compiling a new method for a class and adding the method to the class’s method dictionary.

An invocation of the method has this form:

aClass compileMethod: sourceString 
	dictionaries:  arrayOfSymbolDicts 
	category:      aCategoryNameString 
	environmentId: environmentId

The first argument, sourceString, is the text of the method to be compiled, beginning with the method’s selector. The second argument, arrayOfSymbolDicts, is an array of SymbolDictionaries to be used in resolving the source code symbols in sourceString. Under most circumstances, you will probably use your symbol list for this argument. The third argument names the category to which the new method is to be added.

environmentId specifies one of potentially multiple compile environments, provided for applications using multiple environments; it is normally 0. Unles syou are using multiple environments, use 0; or omit this keyword, and it will default to an environmentId of 0.

You may use a simplified form,

aClass compileMethod: sourceString 

which defaults to using the current symbol list, the category '(as yet unclassified)', and an environment Id of 0.

The following code compiles an accessor method named habitat for the class Animal, adding it to the category “Accessing”:

	Animal 
		compileMethod:
			'habitat
			"Return the value of the habitat instance variable"
			^habitat'
		dictionaries: (System myUserProfile symbolList)
		category: 'Accessing'
	 	environmentId: 0

When you write methods for compilation in this way, remember to double each apostrophe within the source string.

If compileMethod:... executes successfully, it adds the new method to the receiver. If the source string contains errors, this method signals a CompileError, with details on the specific causes of the failure.

Removing Methods

You can remove a method by sending removeSelector: to a class or metaclass.

The following examples remove instance and class methods, respectively:

Animal removeSelector: #habitat
 
Animal class removeSelector:#newWithName:favoriteFood:habitat:

To remove all methods in a method category, as well as the category itself, use
removeCategory: categoryName. For example,

Animal removeCategory: 'Accessing'

Pragmas

A pragma is a literal selector or keyword message pattern that occurs between angle brackets at the start of a method after any temporaries. Pragmas are useful to provide metadata about methods.

Specifying a pragma is done using unary or keyword method selector syntax, and may include arguments that are literals - they may not include variables. For example,

<foo: 123 >
<foo: 5 bar: 'update'>
<bar>

While they follow method selector syntax, they are symbol literals within the method, not message sends. A primitive directives in GemStone looks like a pragma, but is not; primitive: is a reserved word in the first pragma in a method.

You may include multiple pragmas on one or multiple lines.

Example 17.1 Compiling a method with pragmas

	Animal 
		compileMethod:
			'checkHabitat
			<version: 2.1> <AnimalManagement>
			^self habitat isPreferred'
		dictionaries: (System myUserProfile symbolList)
		category: 'Operations'

environmentId: 0

 

Pragma class

The Pragma class provides a way to find out information about pragmas. An instance of Pragma references the method that defines it, and the keyword and argument or arguments.

Pragma class methods provide search capabilities. Pragma >> allNamed:in: returns a collection of all Pragmas with the given keyword in methods in the given class.

For example:

(Pragma allNamed: #AnimalManagement in: Animal) 
	first method
 GsNMethod Animal>>checkHabitat
 

Sending GsNMethod >> pragmas will return an array of instances of Pragma.

17.2 Information about Class and Methods

Classes Behavior and Class define messages that let you discover information about a class, such as the class’s instance variables, selectors, and categories. The class ClassOrganizer provides searching over methods in the image.

For full protocol, see the image.

Information about the Class

Protocol in Class provides listing of superclasses and subclasses:

Class >> allSubclasses
Class >> allSuperclasses
Class >> allInstances 

Each class also has a class comment and a category. This information can be accessed and updated using:

Class >> comment
Class >> comment: aString
Class >> category
Class >> category: aString

Information about Instance, Class, and Shared Pool variables

Protocol in Behavior allows you to discover the class variables names, instance variable names, and shared pools defined for a given class, or for that class and all its superclasses.

Behavior >> classVarNames
Behavior >> allClassVarNames
Behavior >> instVarNames
Behavior >> allInstVarNames
Behavior >> sharedPools
Behavior >> allSharedPools

Information about Method Selectors

Protocol in Behavior allows you to discover the selectors for the methods in a class, or in that class and its superclasses, and query on particular selectors.

Behavior >> selectors
Behavior >> allSelectors
Behavior >> includesSelector: aSelector
Behavior >> canUnderstand: aSelector
Behavior >> whichClassIncludesSelector: aSelector

Accessing and Managing Method Categories

The methods in a class are associated with a method category, which is used to organize and document the method but does not affect execution. Method categories can be managed programmatically using the following methods in Behavior:

Behavior >> categoryNames
Behavior >> selectorsIn: categoryName
Behavior >> categoryOfSelector: selector
Behavior >> addCategory: categoryName
Behavior >> removeCategory: categoryName
Behavior >> renameCategory: categoryName to: newCategoryName
Behavior >> moveMethod: aSelector toCategory: categoryName

Specific Methods

Each method is compiled into an instance of GsNMethod. You can query a class for its methods, and get source code and other information about the method.

To get the source code for a method, use:

Behavior >> sourceCodeAt: aSelector

To retrieve the compiled method itself, use:

Behavior >> compiledMethodAt: aSelector

This returns an instance of GsNMethod, from which you can then get source code. For example,

(Animal compiledMethodAt: #habitat) sourceString

Some GsNMethod methods that may be particularly useful are:

GsNMethod >> sourceString
GsNMethod >> sourceStringToFirstComment
GsNMethod >> selector
GsNMethod >> recompileFromSource

17.3 Transient Methods

Defining a method creates a persistent method on that class, and in most cases, the classes you are using are shared with other users. You can modify a method in an individual transaction, and you will be able to use your modification within that transaction, but any commit will make that change persistent and visible to other users.

For cases in which private modifications to methods in shared classes are useful, using special protocol allows you to compile methods that are only visible to a single session for the life of that session, and are unaffected by commit or abort.

The method must already exist, you cannot create an entirely new method this way. You must also have write permission for the object security policy of the class of the method; this avoids creating a security hole.

To create a transient method, use:

Behavior >> compileTransientMethod:dictionaries:environmentId:

This method is similar to compile:dictionaries:environmentId:, except that the compiledMethod is installed into the transient method dictionary for the receiver. The method created this way will exist in your image until logout. If you wish to remove them sooner, use one of the following:

Behavior >> removeTransientSelector:environmentId:
Behavior >> removeTransientSelector:environmentId:ifAbsent:

17.4 ClassOrganizer

ClassOrganizer provides useful methods to analyze your repository and perform operations such as searching for senders, receivers, or implementors, and string searches over method source. While usually you would perform these operations using GBS or another Smalltalk IDE, ClassOrganizer provide the ability to do customized analysis and reporting.

ClassOrganizer provides both reporting methods, which return formatted Strings, and query methods, which return collections of symbols or instances of GsNMethods that can be used for further analysis and reporting. See the image for the full set of protocol that ClassOrganizer understands.

For example, to get a report of all the senders of the #dayOfWeek: selector:

Example 17.2 Senders of #dayOfWeek:

ClassOrganizer new sendersOfReport: #dayOfWeek: 
%
Date >> next:
Date >> previous:
 

This example shows methods that are part of the GemStone kernel. In an application environment, it would also include your application senders. Application senders are usually of more interest than GemStone kernel class senders.

You can perform a search over the senders that are not from Globals using:

ClassOrganizer newExcludingGlobals sendersOfReport:#dayOfWeek:

If you want to perform more analysis on the methods or add additional reporting, send sendersOf:, which will return two arrays, the first an array of GsNMethods, the second the offset into the source code. For example:

Example 17.3 Senders of #dayOfWeek: with source offsets

ClassOrganizer new sendersOf: #dayOfWeek:
%
anArray( anArray( GsNMethod Date>>next:, GsNMethod Date>>previous:), anArray( 102, 137))
 

There are two senders of #dayOfWeek:; the method Date >> next:, at source code offset 102, and method Date >> previous:, at source code offset 137.

Examples

The following example relates to the upgrade to 3.7, in which the GsHostProcess class is renamed to ObsoleteGsHostProcess and moved to ObsoleteClasses. This code looks up the class object, finds all references to the class (excluding kernel methods), and recompiles each method from source code.

Example 17.4 Recompile methods referencing GsHostProcess

((ClassOrganizer newExcludingGlobals) referencesToObject:
   (ObsoleteClasses at: #ObsoleteGsHostProcess))
      do: [:aGsNMethod | aGsNMethod recompileFromSource]. 
 

The following code looks for all methods that are sent the message subclassResponsibility:, and makes sure all subclasses override that implementation. This example will return false positives, however, since it does not distinguish abstract classes.

Example 17.5 Verify subclasses override subclassResponsibility:

| clsOrg meths report |
clsOrg := ClassOrganizer new.
report := String new.
meths := (clsOrg sendersOf: #subclassResponsibility:) at: 1.
meths do:
   [:srMeth | 
   (clsOrg subclassesOf: srMeth inClass) do: 
   	[:subcls |
   	((srMeth selector ~= #subclassResponsibility) and:	[(subcls whichClassIncludesSelector: srMeth selector) =
		srMeth inClass]) ifTrue: [
   		   report 
   		      add: subcls name asString;
   		      add: ' does not override ';
   		      add: srMeth inClass asString;
   		      add: '>>';
   		      add: srMeth selector asString; 
   		      lf
   		      ].
   	]
   ].
report
 

17.5 Handling Deprecated Methods

As GemStone features change, some methods may no longer be appropriate, or the method names may be incorrect or misleading. To allow obsolete methods to continue to function and provide a gentle transition to new methods, these obsolete methods may be deprecated.

Deprecated methods may be removed in future major releases, although some deprecated methods may remain in the image for longer periods for the convenience of existing applications.

Usually, deprecated methods will continue to work exactly as they did in the previous releases. However, in some cases the old behavior may not be meaningful in a new version; the deprecated method will continue to work as similarly as possible, but there may be differences.

Behavior may also change for existing methods. With any new release, you should review the Release Notes for changes in behavior as well as for newly deprecated methods.

Deprecated methods in GemStone are indicated by:

Private methods, in a category with a name including 'Private', or which begin with an underscore, or which the method comment says private, may or may not be deprecated prior to removal. It is strongly recommended to avoid calling private methods.

Kernel methods that call deprecated: provide a string, which will generally include the class and selector, the version in which this method was deprecated, and the method that replaces it or some other indications of alternate action.

Since deprecated methods are subject to removal in major releases, it is important to keep your application updated so that no deprecated methods are called.

Deprecated handling

By default, nothing happens when a deprecated method is called; the call to deprecated: has no action. This is most convenient when you first upgrade or convert to a new release of GemStone.

After you have updated your application references to deprecated methods, you can enable Deprecation handling, which can be configured to error or to log all calls to any deprecated methods. By running with this setting, you can locate and fix calls you may have missed, or confirm that you have indeed fixed all calls.

Changing deprecation handling can only be done by a user with write permission for the DataCurator object security policy. Once committed, the setting affects all users of the repository.

There are several levels of action that can be taken when a deprecated method is called:

  • Do nothing — calls to deprecated methods are execute the same as any other method. This is the default.

To turn off any action on deprecation that you have previously enabled, execute:

Deprecated doNothingOnDeprecated
  • Raise an exception —calls to deprecated methods signal an exception.

To enable this, execute:

Deprecated doErrorOnDeprecated
  • Log the call — when a call to a deprecated method occurs, the call to the deprecated method is logged to the deprecation log file, and execution continues. There is no impact on the application, other than performance.

To enable this, execute:

Deprecated doLogOnDeprecated
  • Log the call stack — when a call to a deprecated method occurs, the call to the deprecated method and the call stack are logged to the deprecation log file, and execution continues. There is no impact on the application, other than performance.

To enable this, execute:

Deprecated doLogStackOnDeprecated

Deprecation log

When deprecations are configured to write to a log, a file named DeprecatedPID.log is created in the same location as a the gem log for an RPC login.

This file continues to grow and must be manually deleted. Logging methods or call stacks consumes resources and can noticeably affect performance, and use significant disk space. Methods called repeatedly, such as calls from within sort blocks, are particularly likely to impact the application.

Listing deprecated methods

You can find all currently deprecated methods in a particular version by executing :

ClassOrganizer new sendersOfReport: #deprecated:

Determining senders of deprecated methods

For each deprecated method, you can use development tools to determine if you have any senders within your application. In addition to GBS or other IDE tools, you can use ClassOrganizer methods.

For example, having determined that setSegmentId: has been deprecated, you can execute the following to find all senders of that selector within your application:

ClassOrganizer sendersOfReport: #setSegmentId:

Since deprecation only applies to a method associated with a specific class, and this search looks for all senders of the selector, you will have to examine the list to determine if the call is actually deprecated. This is the consequence of how typing is handled in Smalltalk. For example, String >>+ is deprecated, but Integer >>+ is not.

Also note that this technique will not find methods that are symbols sent to perform: statements, in code in client applications that is sent to GemStone for execution, or in topaz scripts.

17.6 File In and File Out of GemStone code

To archive your application or transfer GemStone classes to another repository you can file out GemStone Smalltalk source code for classes and methods to a text file. To port your application to another repository, you can file in that text file, and the source code for your classes and methods is immediately available in the new repository.

Fileout

Methods in behavior allow you to file out a class, category, or method. For example, to file out a single class named Customer:

| myFile | 
myFile := GsFile openWriteOnServer: 'CustomerClassFileout.gs'. 
myFile ifNil: [^GsFile serverErrorString].
Customer fileOutClassOn: myFile.
myFile close.
%

Using ClassOrganizer, you can file out all the classes and methods in a SymbolDictionary, ordered correctly for filein. For example, to file out UserGlobals:

| myFile | 
myFile := GsFile openWriteOnServer: 'UserGlobalsFileout.gs'. 
myFile ifNil: [^GsFile serverErrorString].
ClassOrganizer new fileOutClassesAndMethodsInDictionary:
	UserGlobals on: myFile.
myFile close.
%

File out can also be done using the topaz command fileout. See the Topaz User’s Guide for more information.

Filein

File in is done using topaz input command, facilities provided by GBS, or the GemStone class GsFileIn.

For example, to file in the fileout of UserGlobals from the previous example:

topaz 1> input UserGlobalsFileout.gs

GsFileIn

GsFileIn supports filing topaz-format GemStone source files into the image, without the use of topaz.

To use, send one of the from* class methods, specifiying the path and file; this reads the file and also performs the filein, and returns itself.

GsFileIn fromGemHostPath: '/mynode/users/gsadmin/myCode.gs'
GsFileIn fromGciClientPath: 'C:\sourceCode\myCode.gs'

The Gem Host is the server; this name clarifies that it is the host that the Gem is on, not the host for the Stone. The Gci Client is the path on the node on which the client is running on, including a Windows path when running topaz or a GBS Smalltalk environment on Windows.

Note that not all topaz commands are supported. The GsFileIn input file may include any of the commands that are normally present in GemStone's fileout results, and a few others. Input files may also be hand-coded provided they adhere to these limitations.

The following are supported and perform much the same function as in topaz:

doit printit run nbrun send

input

category:

classmethod classmethod:

commit abort

env

fileformat

method method:

removeallmethods removeallclassmethods

set specific options: compile_env class category sourcestring class enableremoveall project package (other set commands are ignored)

The following are acceptable in a file, but are ignored:

expectvalue expecterror iferr iferr_clear iferr_list errorcount

fileout output

display omit level limit

time remark status

login logout list

Other topaz commands will error.

Previous chapter

Next chapter