15. 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.

15.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: 0

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 Ruby implementations; it is normally 0 for Smalltalk applications. You can omit this keyword, and methods within Smalltalk will default to an environmentId 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: aSelectorSymbol 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'

15.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

15.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:

15.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.

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

ClassOrganizer new sendersOfReport: #asDecimalFloat
%
DecimalFloat >> integerPart
DecimalFloat >> rem:
DecimalFloat >> _coerce:
FixedPoint >> asDecimalFloat
Fraction >> asDecimalFloat
ScaledDecimal >> asDecimalFloat
SmallFloat >> asDecimalFloat

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

(ClassOrganizer new sendersOf: #asDecimalFloat) printString
%
anArray( anArray( aGsNMethod, aGsNMethod, aGsNMethod, 
aGsNMethod, aGsNMethod, aGsNMethod, aGsNMethod), anArray( 161,
102, 309, 104, 215, 1052, 85))

See the image for the full set of protocol that ClassOrganizer understands.

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

| clsOrg meths report |
clsOrg := ClassOrganizer new.
report := String new.
meths := (clsOrg sendersOf: #subclassResponsibility:) at: 1.
meths do:
   [:srMeth | 
   (clsOrg subclassesOf: srMeth inClass) do: 
   	[:subcls |
   	(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
 

15.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.

Previous chapter

Next chapter