self myResetChangeSet.
self myOpenWindows.

## Filing out

Depending on how you manage your fileOuts, you may want to run programs like *diff* to find the differences between two versions. Unfortunately methods are filed out in a seemingly random order, which makes it difficult to discover the real differences. By changing `self selectors` to `self selectors asSortedCollection in ClassDescription>> fileOutMethodsOn:` you can have the methods file out in sorted order.

## Creating a production image

When you rely on people to do things there is always a chance of error. People are creative, but also prone to making errors, which is why *make* schemes are so popular<fnref n="1" />. They apply rules to determine what to build, and then automatically compile and link any files that are out of date. The process of building and saving a Smalltalk image is usually somewhat manual. We may have a fileIn that gets the right code in, but a person usually has to decide that an image needs building and then do the manual steps of invoking the fileIn and saving the image. Here is a scheme that allows the building of a Smalltalk image to be done automatically, possibly as part of a *make* scheme. In VisualWorks 2.0 it was difficult to create a production image and this process makes it a lot easier. VisualWorks 2.5 has a new facility, the ImageMaker, that makes it much easier to create a production image. Below are two schemes to automate the image creation, one for VisualWorks 2.0 and one for 2.5. The code can be found in the files `imcr20.st` and `imcr25.st`.

## Common to VisualWorks 2.0 and 2.5

The basic idea is that we start with a clean image that has either the *Stripper* or the *ImageMaker* code loaded and has a new *ImageCreator* class loaded. It does *not* have the application code loaded. The *Undeclared* dictionary should be cleaned up apart from the reference to your main application class (the *ImageCreator* will start your application, but since the application code is not yet loaded, the *ImageCreator* will be referring to a class that doesn't yet exist in the image). We then save the image under a descriptive name such as `makeIm`. When we run `makeIm`, it files in the application code, removes unnecessary classes, opens the application, closes the VisualLauncher, sets up a new emergency handler, and then saves the application image. We start by creating *ImageCreator*, a new subclass of *Object* with no instance or class variables.

In the *ImageCreator* class initialization, we register ourself as a dependent of *ObjectMemory*. This means that when *ObjectMemory* sends *changed* messages, the *ImageCreator* class will receive them. Since it is the class that is registered as a dependent, we write `update:with:from:` on the class side. In this method, the only thing we want to hear is that we have just returned from a snapshot — that the image has just come up. When this is the case, we create a new instance of *ImageCreator* and tell it to make a new application image by sending the `createImage` message.

ImageCreator class>>initialize

<footnote n="1">¹ Make is a standard UNIX utility for building programs from source, header, and object files. It checks dates on the various files to determine which object files need to be rebuilt and which executable files need to be relinked.</footnote>
"Register ourselves to receive an update message when the image comes up."

"self initialize"

(ObjectMemory dependents includes: self)

ifFalse: [ObjectMemory addDependent: self]

ImageCreator class>>update: anAspect with: arguments from: anObject
(anObject == ObjectMemory and: [anAspect == #returnFromSnapshot])
ifTrue: [self new createImage]

## VisualWorks 2.0

In VisualWorks 2.0 we need to first load in the Stripper code. We will create a subclass of Stripper called MyStripper where we will override a few methods to prevent dialogs coming up and to initialize variables. However, let's first look at createImage and its support methods. Most of the methods are short so it would be possible to put all the code in one method. However, by having several methods it makes it easier to modify and tailor. In this section we show the methods that are specific to VisualWorks 2.0; methods common to both 2.0 and 2.5 are shown later. Two things to note are that we run MyStripper after filing in the application since we need the compiler when filing in, and we install the application emergency handler at the end in case something goes wrong and we want a regular Notifier raised.

ImageCreator>>createImage
ObjectMemory globalGarbageCollect.
self myRemoveSelfAsDependent.
self myFileInApplication.
self myRunStripper.
self myOpenApplication.
self myCloseLauncher.
self myInstallEmergencyHandler.
self mySaveImageAndQuit

ImageCreator>>myRunStripper
"Run the stripper to remove any unnecessary system classes."
MyStripper new
doRemoveOfSubsystems;
stripSystem.

ImageCreator>>myCloseLauncher
"Close any open Launchers "
self myLauncherClass allInstances do:
[:each | each builder window controller closeAndUnschedule]

ImageCreator>>mySaveImageAndQuit
"Do a final garbage collect then save the image and quit.
Saving as a snapshot means that no change file is created.
The loadPolicy parameter specifies that objects in old space are loaded in perm space."
ObjectMemory globalGarbageCollect.
ObjectMemory
snapshotAs: self myImageName
thenQuit: true
withLoadPolicy: 1

Besides the methods that do the real work, we have some methods in the names protocol. These are the methods that should be modified for different applications because they provide the image name, the main
application class, etc. As mentioned above, only the methods that are specific to VisualWorks 2.0 are shown here; the common methods are shown later.

```
ImageCreator>>myImageName
    "Return the name of the image to save"
    ^'myApp' copy

ImageCreator>>myLauncherClass
    "We may be using a subclass of VisualLauncher as our Launcher"
    ^VisualLauncher
```

There are several changes you must make to the stripper code for it work in this environment. So that the stripping can run automatically, without human intervention, you'll need to override `doRemoveOfSubsystems` and `stripSystem` in `MyStripper` to remove the dialogs. Depending on which subsystems you want removed, you should set the appropriate options to have a value of `true` in `initialize`. For example, we want to also remove the database tools, the printing facility, and extra classes that we specified.

```
MyStripper>>initialize
    super initialize.
    doDbtools := true asValue.
    doPrinting := true asValue.
    doExtras := true asValue.
```

If there are classes that you know you want to strip, add them to `MyStripper class>>initExtras`, making sure you reinitialize the array. Alternatively, you could strip the basic image down before creating the `makeIm` image, keeping just enough to be able to file in the application code and save the image. You'll still need to strip the compiler later, but creating the application image would be faster if you start from a basic image that has already been stripped of a lot of classes.

If you want more than a VisualLauncher open in the builder image, you can add a message send of `myCloseOtherWindows` before filing in the application code.

```
ImageCreator>>myCloseOtherWindows
    "Close any open windows that aren't Launchers"
    ScheduledControllers scheduledControllers
    do: [:each | each model class ~~ self myLauncherClass
    ifTrue: [each closeAndUnschedule]]
```

## VisualWorks 2.5

In VisualWorks 2.5 you need to first load in the `ImageMaker` code. You then run the `ImageMaker` (`ImageMaker open`) and select the removal options you want. To remove other classes in addition to the subsystems specified, select **Set Additional Removals** from the **Classes** menu, and select the **Remove Additional Classes** button. Once you have made all your choices, select **File Out Choices** from the **File** menu. We'll use the choices file later in one of the `ImageCreator` methods.

As before, we'll start by looking at `createImage` and its support methods, showing the methods specific to VisualWorks 2.5 here, with methods common to both 2.0 and 2.5 shown later.

```
ImageCreator>>createImage
    self myRemoveSelfAsDependent.
    self myInitializeImageMaker.
    self myFileInChoices.
```
self myFileInApplication.
self myOpenApplication.
self myInstallEmergencyHandler.
self myRunImageMaker

ImageCreator>>myInitializeImageMaker
"Create a new ImageMaker and make it the current one"
MyImageMaker current: MyImageMaker new.

ImageCreator>>myFileInChoices
"File in the ImageMaker choices that we previously saved"
self myChoicesFile fileIn

ImageCreator>>myRunImageMaker
"Remove unneeded classes and save the new image"
MyImageMaker current makeDeploymentImage

ImageCreator>>myChoicesFile
"Return the filename that contains the ImageMaker choices"
^'choices.txt' asFilename

We've seen references to MyImageMaker above, so we now need to create it as a subclass of ImageMaker. In MyImageMaker we override a few methods to prevent dialogs coming up and to specify file paths. Override getZoneDirectory to remove the Dialogs and to set the directory to self myBaseDirectory. Override makeImageFile to remove the Dialogs and to set the imagePrefix to self myImageName. Override refreshView to be an empty method, and warnUser to return true.

MyImagerMaker>>myBaseDirectory
"The directory where the utils subdirectory resides"
^'c:\visual' copy

MyImagerMaker>>myImageName
^'myApp' copy

In VisualWorks 2.5 there is one additional step that needs to be taken. The first time your new application image is loaded, VisualWorks does some operations to optimize memory use and increase performance, then saves the image again. So if you are creating an image as part of an automatic process, your scripts should create the new application image, *and then start up the new application image*. The code above will automatically save the optimized application image then exit.

## Common to VisualWorks 2.0 and 2.5

The following methods are common to both VisualWorks 2.0 and 2.5.

ImageCreator>>myRemoveSelfAsDependent
"Remove ourself as a dependent of ObjectMemory.
We don't want this code to run every time the application is
started."
(ObjectMemory dependents includes: self class)
ifTrue: [ObjectMemory removeDependent: self class]

In myOpenApplication we assume we can send open to the application class. If you need another message sent, you could define a new method myStartMessage which returned a symbol. You would then start the application by self myApplication perform: self myStartMessage.
### ImageCreator>>myOpenApplication
"Open the application. We need to make sure that the user is not prompted for
the window position. When you accept this method, it will ask you to declare your
application class. Make it Undeclared. "
| prompt |
prompt := ScheduledWindow promptForOpen.
ScheduledWindow promptForOpen: false.
self myApplication open.
ScheduledWindow promptForOpen: prompt

### ImageCreator>>myFileInApplication
"File in the application code."
self myInstallFile fileIn.

### ImageCreator>>myInstallEmergencyHandler
"Install the application Emergency Handler."
Exception emergencyHandler:
[:ex :context | self handleException: ex context: context]

### ImageCreator>>myInstallFile
"Return the filename that files in the application code"
^'install.st' asFilename

### ImageCreator>>myApplication
"Return the class that starts up the application"
^MyApplication

In the method that sets up the new emergency handler, you will want to put something more sensible than the code shown here, but what you put will depend on how your application handles errors.

### ImageCreator>> handleException: ex context: aContext
"Do some error handling then quit"
Dialog warn: 'Error: ', ex errorString, ' in ', aContext
printString, ' Quitting application'.
ObjectMemory quit

We now have a mechanism for creating new images automatically, with little or no human intervention.

## Saving the mkIm image

After filing in either the Stripper or ImageMaker followed by the ImageCreator, we need to save the image under a name such as `mkIm`. This is the image that will create our deployment image using all the code we wrote above. The obvious way to save `mkIm` is to use the File menu to either save the image or exit VisualWorks and save. However, this will create a change file, which will grow every time `mkIm` files in the application code. By adding two methods to the class side of ImageCreator we can save the image without a change file.

### ImageCreator class>>saveImage
"Save the image that will be used to create the production image."
"self saveImage"
ObjectMemory globalGarbageCollect.
ObjectMemory
    snapshotAs: self myImageName
    thenQuit: true
    withLoadPolicy: 1
```
ImageCreator class>>myImageName
"Return the name of the image that will be used to create the
deployment image"
^'mkIm' copy
```

Rather than saving the image from the file menu, you can now evaluate `ImageCreator saveImage`, which will save the image with no change file, then quit. An alternative mechanism is to save the image with the name `snapshot`. VisualWorks treats this name specially and does not create a change file. After creating the image, you would then rename `snapshot.im` to `mkIm.im`.

## Runtime Packager

In Smalltalk method parameters are not typed, and polymorphism allows many classes to define the same method name. It is therefore very difficult to statically tell if classes are referenced and methods invoked. The consequence is that the stripping process can be rather hit and miss as you try to figure out what you can strip from the image. Runtime Packager™, from Advanced Boolean Concepts, provides additional capabilities that make it easier to determine what you can strip and what your application needs. It allows you to specify which classes and methods you want included, which ones you want removed, and which you are not sure about. It will then use both static and dynamic analysis to check your application to determine which classes and methods are safe to remove.

Runtime Packager will statically check all the message sends, starting at the message send that starts your application and following the message send tree until there are no further references. It also allows you to run the application in a special test mode where it records all the classes and methods that were previously marked for deletion but which are referenced in the running application. It automatically returns them to the image and continues running the tests. Once all the static and dynamic analysis is done, it creates a stripped image.

At the time of writing, Runtime Packager is available for $495 for a single user, and $1,200 for a site license.
For more information, contact:

**Advanced Boolean Concepts, Ltd.**

3704 Cameron Mills Road
Alexandria, VA 22305, USA
Phone: (703) 548-5073
Fax: (703) 548-6053
Email: advbool@advbool.com
Web page: http://www.advbool.com/advbool/
34
ENVY

When organizations start developing in Smalltalk, they often use the fileIn and fileOut mechanism to manage
changes to classes and methods. After a while, especially if the number of people working on an application
increases, most organizations look for a way to better manage source code and configurations. The dominant
player in this field for Smalltalk is Object Technology International, Inc (OTI), with their ENVY®/Developer
product. The part of ENVY/Developer that manages source code and configurations is called ENVY®/Manager,
and this is what most developers are referring to when they talk about ENVY. This chapter provides an overview
of ENVY/Manager and perhaps gives a few pointers to those already using it.

ENVY/Manager helps you manage code development. In brief, it provides a source code repository that stores and tracks all changes to methods and classes. It allows developers to modify and test their code in isolation from each other, possibly even working on parallel versions of the same code. Once developers are satisfied that their code works, they can release it for general use. Other developers now have the choice of loading the new code into their images or continuing with the older version until they are ready to integrate. Between the extremes of isolation and integrating all the code released by a developer, ENVY/Manager also provides a way for developers to view or load individual classes or methods that another developer is working on. At the project level, ENVY/Manager keeps track of product releases. By creating a configuration map that specifies the versions of the classes that comprise a release, you can easily recreate any release of your application. We'll look at each of these facets in the following sections.

Code sharing

ENVY/Manager keeps the source code in a central repository called a library, so all developers have access to the same code (it actually keeps both source code and byte-compiled code for faster access). ENVY/Manager is conceptually a client-server application, where developers work in client images which communicate with the library server. Thus, whenever anyone makes a change to a class or a method, all other developers have access to that change should they want to view it or load it into their image (subject to security, which we'll talk about shortly).
ENVY/Manager automatically keeps track of every revision to every class and method rather than using the more common manual check-out, check-in mechanism used by source code managers such as RCS and SCCS. The manual check-out, check-in mechanism works well with large source files where work can be fairly well compartmentalized and source files checked out for relatively long periods of time. However, the Smalltalk object-oriented paradigm means that changes are likely to affect many cooperating classes, and since methods are generally short, a developer is likely to make small changes to many methods. In this world, tracking all changes is a lot less intrusive than a manual check-out and check-in scheme.

Because all the versions of the code are stored, it becomes a very simple matter to go back to a previous version if necessary. Even if methods or classes are deleted from the local image, they still exist in the repository and can easily be found by looking for *available* methods or classes in the appropriate browser.

ENVY/Manager makes it very easy for developers to work on code in parallel. For example, one developer might be working on a class for the next release of the product, while another developer is making bug fixes to support a previous release, and another is trying out some optimization techniques. All three developers can work on their changes without affecting each other, then when it comes time to integrate the changes, ENVY/Manager provides tools to show the differences between versions and to load alternative versions.

## Applications and subapplications

In ENVY/Manager, everything is done within the context of an *application*, which is simply a collection or grouping of classes. Thus, within applications you have many classes, all pertaining to the application. To further organize the classes, you can create subapplications within the application. For example, a common way to organize an application (let's call it EmployeeApp), is to have three subapplications, one for the domain model classes, one for the user interface classes, and one for the data access classes. So we might call the subapplications EmployeeDomainApp, EmployeeUIApp, and EmployeeDataAccessApp. Subapplications are also commonly used to break out platform specific code because when an application is loaded it can use logic to determine which subapplications to load. (In what follows, we'll use the term *application* to mean either an *application* or *subapplication*, unless explicitly stated otherwise.)

When an application is created, it contains exactly one class, which has the same name as the application. This class serves several purposes. It starts off being a class template which developers can use to create new classes. It also provides an opportunity to write class side methods that will be invoked on pre-defined occasions (related to loading or unloading the application and starting and exiting the image). The most useful is the *loaded* method. Because source files are not filed in, class side *initialize* methods are not automatically invoked. Instead, you have to invoke them explicitly in the *loaded* method which is executed when the application is loaded into the image. (Another, less known method is *etoSystemMenu*, which allows you to easily extend the ENVY/Manager menu.)

In ENVY/Manager, every class is *defined* in a single application. However, you can *extend* a class in another application, which gives you the opportunity to add application specific behavior to that class (note, however, that you can't *modify* a base method in an extension). For example, suppose you are working on two products that share some but not all code. ProductOne needs a new method *foo* added to Object. You might create a new application called ProductOneSystemExtensions, then extend Object by adding *foo* to Object in this application.
## Version Control

Applications and classes can exist in one of two basic states: as *versions*, which cannot be modified, or as *editions* which can be modified. You can tell the state of an application because versions display with a version name and editions display with a timestamp between parentheses.

ENVY/Manager allows developers to modify any method of any class (subject to access control). To modify any component, its container must be modifiable — ie, must be an edition. So, while methods are always editions, to modify a method, the class has to be an edition. Similarly, to modify a class, the containing application or subapplication must be an edition. Fortunately, ENVY/Manager makes this all very easy. The developer simply has to modify and accept a method and the enclosing class will automatically be made an edition (if it's not already one). If the application containing the class is not already an edition, ENVY/Manager will also ask the developer if he wants to create a *scratch edition* of the application when he accepts the method (we'll talk about the difference between an edition and a scratch edition later).

## Visibility

All changes are recorded in the library — every change to every method. However, changes are not automatically propagated to other developers' images, so developers can work in isolation, unaffected by other people's work. However, if a developer chooses, he can view or load other people's changes. In a normal development environment a developer will load changes for two reasons: to integrate his code with that of others, and to load in a class or method to get an immediate fix from another developer. Lets look at the visibility of the different types of components, because the rules for viewing and loading them are different.

### Methods

A developer can browse all editions of an individual method and load any edition into his image (remember that all methods exist as editions in the library and every time you accept a method a new edition is recorded).

### Classes

A developer can browse all editions and versions of a class and can load any version of the class. However, editions of a class can be loaded only by the person who created the edition.

### Applications

A developer can browse all editions and versions of an application or subapplication. He can also load any edition or version. But which classes are loaded when he loads another edition or version of the application? Only those classes that have been *released* to the application. By definition, releasing a class makes it available to the application and denotes that is should be loaded when the application is loaded. A class must be versioned — made immutable — before it can be released to an application. Similarly, if an application consists of several subapplications, loading the application will load the released versions or editions of the subapplications.

### Products and subsystems

A developer will often want to load in the current baseline of the product under development, or of a subsystem. Loading in the current baseline gives the developer a chance to test his changes with all the released changes. Just as an application is a collection of classes that have meaning together, a *configuration map* (often known as a config map) is a collection of applications that have meaning together. By loading in a configuration
map, the developer loads in all the application versions or editions that have been specified in the configuration map. The order of loading of applications is controlled by specifying the prerequisites of each application.

Configuration maps provide a one-step way to load a specified set of applications. The main reasons to manage configurations are: to load the latest development build, to load end-user builds, and to give the ability to recreate releases of the product should it be necessary to track down a problem in a previous release. Typically an organization would have several different configuration maps: one for loading the latest development build, one for each major component or subsystem, and one for creating an end-user image.

## Scratch Editions

Scratch editions of an application are similar to editions but have the quality that they are automatically created and are therefore unintrusive. However, you cannot release classes into a scratch edition of an application, and scratch editions cannot be browsed or loaded by other developers. Thus, if you exit the image without saving it, you will not be able to load the scratch edition you were working on and will have to individually load all the appropriate editions or versions of the classes you modified. Most developers let ENVY/Manager create a scratch edition for them, then convert it to a “regular” edition at a convenient stopping point. Besides being a convenience, scratch editions are useful in their own right for adding debug code that will later be removed (such as `self halt`). Since you are only adding debug code and then removing it, you don’t really want an edition of the application to be permanently recorded in the library (although all the method changes are recorded). Scratch editions display with the version name surrounded by double angle brackets.

## Management of Applications and Classes

ENVY/Manager works on the premise that applications have *managers* and classes have *owners* (the concepts are the same; just the terminology differs). Each application has a manager who is responsible for understanding the application and the relationships between the classes in the application. Each class has an owner who is responsible for the integrity and consistency of the class. The idea is that a class is likely to be better encapsulated, with a more consistent public interface, if one person is responsible for it. Having one person responsible for the class provides both accountability and continuity of perspective.

Classes have to be versioned to make them available to other developers, and have to be versioned and released to make them available to their containing applications. Only the class owner can release a class to its containing application. Similarly, subapplications have to be released to make them available to their containing application. Only the subapplication manager can release a subapplication. Applications are always available to load and hence always released.

However, while only owners and managers can release components, any developer can make changes to classes and methods. If a developer realizes he needs to modify some code in a class he doesn't own, he simply create a new edition of the class, makes the change, and keeps working. When he is done with his changes he will typically create a new version of the class. Since he can't release the new version of the class, he tells the owner of the class about the new version and the class owner browses it. The owner can release the new version of the class, or he can modify it or rewrite it to maintain the internal class consistency, then version it and release the new version.
Perhaps this sounds too permissive, with anyone allowed to view or change any code. ENVY/Manager allows you to control access to code by setting privileges. Associated with each application is an application group, which consists of the class owners (only members of the group can own classes in the application) and developers who have an interest in or a need to know about the workings of the classes. You can restrict access to the classes in the application to just owners of the classes (by having no other group members), to group members, or to the world. Among other things, access can be separately restricted for looking at public methods, looking at private methods, and creating editions of a class for modifying code.

## Application manager diagram

Figure 34-1 shows an application manager, illustrating many of the points we have covered. In the application and class windows there are examples of versions, editions, and scratch editions. Applications and classes with a greater-than sign to the left have not been released to their containers. In the Group Members box, the highlighted developer is the class owner, while the developer with a greater-than sign is the application or subapplication manager.

<figure><img src="image_58.png" /><figcaption>Figure 34-1. An Application Manager window.</figcaption></figure>

## An example of controlling parallel development

Since ENVY/Manager allows multiple developers to create editions of the same class and to modify the same methods, let's see how the process of releasing this code would work. Suppose two developers are working on the same class, which neither owns. They both create editions of the class and scratch editions of the application. After making their changes, they each version the class, each giving it a different version ID. To make things
easier for the class owner, they might name the version with their name and the date. That's as far as they can go
since neither is the owner of the class. Now they tell the owner of the class the ID of the version they created.

The owner of the class can browse these new versions and can also browse differences between his version
and either of the new versions, or between the two new versions. The owner can then choose how to integrate the
new changes, whether by loading in entire methods, by cutting and pasting, or by rewriting. Once he has created
a new edition and incorporated the changes, the owner then versions and releases the class, then asks the
application manager to version the application.

## Code reviews

ENVY/Manager makes it possible to do on-line code reviews. The general process is that once a developer is ready to have his code reviewed, he versions all the classes and applications then informs the reviewers that the code is ready for review. The reviewers then load in the code and review it in their images. They may add comments, rewrite methods, or even restructure methods within classes. Once done with their review, they inform the original developer, who looks at the changes and decides which changes to integrate. This process also makes it easier to stagger the reviews. Rather than have three developers simultaneously review the code and make changes, a better option is to have one person review the code, integrate any proposed changes, have the next developer review the results of the integration, integrate his proposed changes, then have the last developer review this integration.

In describing the mechanics of doing on-line code reviews, I am assuming that you are using the security
provided by ENVY/Manager and that developers cannot simply become other users. I am also assuming that
anyone asked to review code is a member of that application group.

The code author versions and releases all his classes and the application managers version the containing
applications. The application managers then create new editions of the applications and re-version them with a
name such as "For Review, 96/03/20". There are now versions of the applications for the reviewer, and editions
of the applications which can be used to continue development or can be re-versioned. Next, the application
managers change the application manager of the "For Review" version of each application. This is a key point:
each edition or version of an application can have a different manager and different class owners (the default is
to keep the same manager and owners as the previous version). Finally, the code author creates a new
configuration map of the applications that will be reviewed, again naming it something like "For Review,
96/03/20", and changes the manager of the configuration map to be the reviewer.

The author then tells the reviewer that the code is ready for review and gives him the name of the
configuration map. The reviewer loads the map and starts browsing all the code, making code changes where
appropriate. Once the reviewer is done, he versions the classes he has changed, giving them a name such as
"Reviewed by Bill, 96/03/24". At this point he can't release the classes since he is not the owner, so he simply
makes himself the class owner and releases the class. (He can do this as he has already been made the manager
of the review version of the application. It seems a little confusing, but by becoming the owner of the class in
this edition of the application, he is *not* changing the ownership of the class in the main-line code.) The reviewer
then versions the application with a similar name. Lastly, he creates a new version of the configuration map with
a similar name.

The author then looks at the new configuration map to see which applications have been changed. He does
not load the configuration map; instead he goes to each application that has been modified and browses changes.
Depending on how much work he has done in the meantime, he will either browse changes between the "For Review" and "Reviewed" versions of the application or between the current and "Reviewed" versions. If the application has subapplications, he will have to also browse changes for each subapplication. As he looks at the changes, he can load in the alternative code if he chooses. Figure 34-2 illustrates the process of reviewing a single application.

<figure><img src="image_59.png" /><figcaption>**Figure 34-2.**
The code review process.</figcaption></figure>

For additional information on using ENVY/Manager to manage code reviews, see *Implementing Peer Code Reviews in Smalltalk*, by S. Sridhar, in the July/August 1992 issue of The Smalltalk Report. Note, however, that this article describes a process where there is little security and developers can simply become another user to accomplish the described tasks.

## ENVY/Developer 3.01 enhancements

Most of the above describes the 1.43 release of ENVY/Manager. Release 3.01 provides some additional features and I've included information from the release notes here.

* Nested configuration maps.

* Browse changes between two configuration maps.

* Browse changes between application and all its subapplications.

* Export differences for configuration maps and applications.

* A more flexible component ownership model for releasing classes into applications and applications into configuration maps.

* Better support for distributed development teams.

* Version control for C and other non-Smalltalk languages. This includes using the browsers for non-Smalltalk development, using make files, and being able to version the files created during the build.
* Version control for other files such as bitmaps and help files.

## Additional Information

OTI can be reached in the following ways:

Object Technology International, Inc
2670 Queensview Drive
Ottawa, Canada K2B 8K1
Voice: (613) 820-1200
Fax: (613) 820-1202

Object Technology, Inc
301 East Bethany Home Road
Suite A-100
Phoenix, AZ 85012, USA
Voice: (602) 222-9519
Fax: (602) 222-8503
e-mail: info@oti.com
Web page: http://www.oti.com

For a product review of ENVY/Manager version 1.43, see the review by Jan Steinman and Barbara Yates of Bytesmiths in the October 1992 issue of the Smalltalk Report. The review, with some updated information, can also be found at the web address:

http://wwwbytesmiths.compubs/9209Envy.html.
35

Public Domain Code and
Information

To the best of my knowledge, the following information was correct at the time I wrote it. However, things
change, so there are no guarantees that the information is still correct.

Magazines

The main magazine for Smalltalk developers is *The Smalltalk Report*. There is also a regular Smalltalk column in *The Journal Of Object-Oriented Programming*. Both magazines are published by SIGS publications.
At the time of writing, the subscription information, addresses, and phone numbers are:

The Smalltalk Report
SIGS Publications
PO Box 5050
Brentwood, TN 37024-5050
Current price: $89 a year (9 issues)

*The Journal Of Object-Oriented Programming*

SIGS Publications
PO Box 5049
Brentwood, TN 37024-9737
Current price: $79 a year (9 issues)

SIGS subscriptions

E-mail: subscriptions@sigs.com
Voice: 1-800-361-1279
Fax: 615-370-4845

SIGS information

Web: http://www.sigs.com
E-mail: info@sigs.com
Voice: 212-242-7447

Copyright © 1997 by Alec Sharp

Download more free Smalltalk-Books at:

- The University of Berne: http://www.iam.unibe.ch/~ducasse/WebPages/FreeBooks.html

- European Smalltalk Users Group: http://www.esug.org
Back Issues: 212-242-7447

Reprints: 717-560-2001

# Products and Information

## The Smalltalk Store

The Smalltalk Store provides one-stop shopping for a variety of Smalltalk products, usually at a price slightly lower than the vendor price. It publishes *The Smalltalk Gazette* a few times a year, a magazine that combines technical articles, general information, and the product catalog.

### *The Smalltalk Store*

565 Esplanade, Suite 402
Redondo Beach, CA 90277
Voice: 1-800-431-2226
310-543-1530
Fax: 310-540-6908
e-mail: stsales@smalltalk.com
Compuserve: 71361,1636
Web page: http://www.smalltalk.com

## Public Domain Code

The main archives for public domain Smalltalk code are at the University of Manchester in England, and at the University of Illinois (UIUC). UIUC mirrors the Manchester archives and also has its own archives. You can get code from UIUC using the *World Wide Web* or by *anonymous ftp*. ParcPlace also has some source code available from the World Wide Web.

## Usenet

On the Internet, the newsgroup `comp.lang.smalltalk` usually contains a lot of good material on Smalltalk programming. The *Smalltalk Frequently Asked Questions* (FAQ) is periodically posted to `comp.lang.smalltalk` and `newsAnswers`. Ralph Johnson's compilation of classic bugs is periodically posted to `comp.lang.smalltalk`.

## The World Wide Web

Here are some Smalltalk-related Web pages. Some of them lead to other interesting Smalltalk Web pages, so if you start here, you can end up at a lot of other sites with Smalltalk information.

* UIUC Smalltalk Archive
http://st-www.cs.uiuc.edu
* Smalltalk FAQ
http://xcf.berkeley.edu/pub/misc/smalltalk/FAQ
* Smalltalk FAQ (plain text):
http://xcf.berkeley.edu/pub/misc/smalltalk/FAQ/FAQ.txt
* Another Smalltalk FAQ:
http://www.cis.ohio-state.edu/hypertext/faq/usenet/smalltalk-faq/faq.html

Patterns Home Page

http://st-www.cs.uiuc.edu/users/patterns/patterns.html

Portland Patterns Repository

http://c2.com/ppr/titles.html

Jeff McAffer's home page

http://www.oti.com/jeffspg/smaltalk.htm

The Smalltalk Developer's Site

http://www.rwi.com/smalltalk/smalltalk.html

Yahoo Smalltalk page

http://www.yahoo.com/Computers_and_Internet/Programming_Languages/Smalltalk/

ParcPlace-Digitalk

http://www.parcplace.com/

VisualAge

http://www.software.ibm.com/software/ad/vastub.html

Anonymous FTP

If you don't have a Web client, you can get to the Smalltalk archives at st.cs.uiuc.edu using anonymous ftp. If your ftp program can't find this address, the IP address is currently 128.174.241.10.

If you've never used anonymous ftp before, here are some basic instructions. Start by ftp'ing to the site (ftp st.cs.uiuc.edu). When asked for your login, enter anonymous and give your e-mail address as the password. You can use cd to change directory and ls to list the directory. To get a file, use get (for example, get speed.st). To get multiple files you can use mget (eg, mget s*.st).
A

# Source Files

This book comes with an accompanying diskette containing code and examples. Neither the author nor the publisher warranties the code or examples in any way. There is no warranty that they are correct, or that they will fulfill their stated purpose. In particular, you include any additions or changes to system classes at your own risk.

On the diskette are three different types of code. First, the `.ex` files in the `examples` directory contain example code that can be highlighted and evaluated. The files contain code matching the examples in the text. These files should *not* be filed in. Instead, you can look at them using a File List or a File Editor, and evaluate the code directly in these tools. Or, if you prefer, you can cut and paste the code from these tools into a Workspace and evaluate it there.

Second, the files in the `classes` directory contain classes that can be filed in. These classes illustrate points that are made in the text. Normally there is an `examples` protocol on the class side that contains methods to execute.

Third, the files in the `sysadds` and `sysmods` directory contain additions and modifications to system classes. Note that there is always a risk in making changes to system classes, so incorporate these changes at your own risk.

The source code and examples are licensed to only one person per copy of this book. That person is free to use the code and examples in their own development environment or applications as they see fit. However, the licensed person may not distribute the code or examples to other people.

## Source files mentioned in chapters

This section shows which source files are referenced in which chapters.

<table><tr><td>16</td><td>Processes</td><td>psq.st, process.st</td></tr><tr><td>18</td><td>Cleaning up at Termination</td><td>cleanup.st</td></tr><tr><td>19</td><td>The Dependency Mechanism</td><td>depend.st</td></tr><tr><td>20</td><td>Error Handling</td><td>errormsg.st</td></tr><tr><td>21</td><td>Debugging</td><td>debug.st, role.st</td></tr><tr><td>22</td><td>Common Errors</td><td>literal.st</td></tr></table>

Copyright © 1997 by Alec Sharp

Download more free Smalltalk-Books at:

- The University of Berne: http://www.iam.unibe.ch/~ducasse/WebPages/FreeBooks.html

- European Smalltalk Users Group: http://www.esug.org
<table><tr><td>25</td><td>Hooks into the System</td><td>copydemo.st, framwrk.st, hooks.st</td></tr><tr><td>26</td><td>ChangingWidgets at Runtime</td><td>focus.st, listdemo.st, widgets.st</td></tr><tr><td>29</td><td>Meta-Programming</td><td>metademo.st</td></tr><tr><td>30</td><td>Testing</td><td>testing.st</td></tr><tr><td>31</td><td>Customizing your Environment</td><td>launch20.st, launch25.st</td></tr><tr><td>33</td><td>Managing Source Code</td><td>custom.st, imcr20.st, imcr25.st</td></tr><tr><td></td><td></td><td>install.st, install2.st, local.st</td></tr></table>

## New classes and subclasses

These files can be found in the directory `classes`. Each file contains a class or classes for which the code is shown and discussed in the book. The files can be directly filed into the image.

<table><tr><td>cleanup.st</td><td>Class to close files, terminate processes, etc., at termination.</td></tr><tr><td>copydemo.st</td><td>Example of copying, both with and without using postCopy.</td></tr><tr><td>custom.st</td><td>Class to customize your development environment.</td></tr><tr><td>debug.st</td><td>Three classes: a class that logs to a file or to the Transcript; a class that intercepts messages being sent to an object; an example for using the inspector to change values.</td></tr><tr><td>depend.st</td><td>Examples illustrating the dependency mechanism.</td></tr><tr><td>errormsg.st</td><td>Example showing the use of error message classes and subclasses.</td></tr><tr><td>focus.st</td><td>Example of determining which widget has requested the focus.</td></tr><tr><td>framwrk.st</td><td>Extensions to the Extended Framework.</td></tr><tr><td>hooks.st</td><td>Examples of using various system hooks.</td></tr><tr><td>imcr20.st</td><td>Classes to automatically create deployment image for VisualWorks 2.0.</td></tr><tr><td>imcr25.st</td><td>Classes to automatically create deployment image for VisualWorks 2.0.</td></tr><tr><td>launch20.st</td><td>Subclass of VisualLauncher for VisualWorks 2.0.</td></tr><tr><td>launch25.st</td><td>Subclass of VisualLauncher for VisualWorks 2.5.</td></tr><tr><td>listdemo.st</td><td>Example of setting up code to intercept double-clicks and keyboard input.</td></tr><tr><td>literal.st</td><td>An example of modifying literal strings.</td></tr><tr><td>metademo.st</td><td>Example showing methods and variables in a class and its metaclass.</td></tr><tr><td>process.st</td><td>Subclass of Process that includes a name variable.</td></tr><tr><td>psq.st</td><td>Priority Shared Queue.</td></tr><tr><td>role.st</td><td>An example showing an object assuming different roles.</td></tr><tr><td>testing.st</td><td>Test Manager and Test case classes and subclasses.</td></tr><tr><td>widgets.st</td><td>Examples of modifying widgets and menus at runtime.</td></tr></table>

## Files for installing applications

These files can be found in the directory `manage`. Each file illustrates some aspect of managing application or development environment code, and filing code into the image.

<table><tr><td>install.st</td><td>Installs modifications to system classes, files in application code, and customizes the environment.</td></tr><tr><td>install2.st</td><td>Shows how to discover the directory from which the file is being done.</td></tr><tr><td>local.st</td><td>Local development environment changes.</td></tr></table>

## System class additions

These files can be found in the directory `sysadds`. Each file adds methods to a system class. Use at your own risk.
<table><tr><td>behavior.st</td><td>Additions to Behavior to return all instances of a class and its subclasses, and to change instances into empty strings.</td></tr><tr><td>blockcl.st</td><td>Additions to BlockClosure to fork processes with names.</td></tr><tr><td>c-browse.st</td><td>Additions to class side of Browser for looking at all methods in a specified protocol.</td></tr><tr><td>c-para.st</td><td>Addition to class side of ParagraphEditor to return the keyboard.</td></tr><tr><td>char.st</td><td>Addition to Character to return character as a string.</td></tr><tr><td>chararr.st</td><td>Additions to CharacterArray to trim white space, to capitalize the first letter, and to return an array of substrings.</td></tr><tr><td>class.st</td><td>Additions to Class to copy a class and to create accessors when accepting a class definition.</td></tr><tr><td>inspect.st</td><td>Additions to Inspector for inspecting owners of and reference paths to an object.</td></tr><tr><td>keyproc.st</td><td>Addition to KeyboardProcessor to help track which widget has requested the focus.</td></tr><tr><td>object.st</td><td>Additions to Object which add functionality for halting, displaying objects, printing to the Transcript, and marking methods.</td></tr><tr><td>stream.st</td><td>Addition to Stream to read a line and strip comments.</td></tr><tr><td>sequence.st</td><td>Additions to SequenceableCollection to append an object, and to find the first occurrence of an object starting at a specified position.</td></tr></table>

## System class modifications

These files can be found in the directory `sysmods`. Each file modifies existing methods in a system class.
Use at your own risk.

<table><tr><td>behavior.st</td><td>Modification to Behavior to provide a different method template.</td></tr><tr><td>inspect.st</td><td>Modifications to the Inspector menu to allow you to browse owners of and reference paths to the object being inspected.</td></tr><tr><td>keyproc.st</td><td>Modification to KeyboardProcessor to track which widget has requested the focus. You must add a new instance variable by hand.</td></tr><tr><td>standard.st</td><td>Modification to StandardSystemController to allow the VisualWorks</td></tr></table>
# B

## Source Code for
Chapter 24

This appendix shows the code for the above examples in full. To save space I've done some minor things such as only show the class names and the instance variable names in the class definition.

### Superclasses

ApplicationModel subclass: #MyInput
instanceVariableNames: 'person '

MyInput class>>open: aModel
^self openOn: (self new initialize: aModel)

ApplicationModel subclass: #MyView
instanceVariableNames: 'person '

MyView class>>open: aModel
^self openOn: (self new initialize: aModel)

Model subclass: #MyPerson
instanceVariableNames: 'name age '

### Example One

MyInput subclass: #MyInput1
instanceVariableNames: 'name age '

MyInput1>>initialize: aPerson
person := aPerson.
name := String new asValue.
name ONChangeSend: #nameChanged to: self.
age := 0 asValue.
age ONChangeSend: #ageChanged to: self

MyInput1>>age
^age

MyInput1>>name
^name

MyInput1>>ageChanged
person age: self age value
MyInput1>>nameChanged
person name: self name value

MyView subclass: #MyView1
instanceVariableNames: 'name age '

MyView1>>initialize: aPerson
person := aPerson.
person addDependent: self.
name := String new asValue.
age := 0 asValue.

MyView1>>age
^age

MyView1>>name
^name

MyView1>>update: aSymbol with: aValue from: anObject
aSymbol == #name ifTrue: [self name value: aValue].
aSymbol == #age ifTrue: [self age value: aValue]

MyPerson subclass: #MyPerson1
instanceVariableNames: ''

MyPerson1>>age: aValue
age := aValue.
self changed: #age with: age

MyPerson1>>name: aValue
name := aValue.
self changed: #name with: name

## Example Two

MyInput subclass: #MyInput2
instanceVariableNames: 'name age '

MyInput2>>initialize: aPerson
person := aPerson.
name := String new asValue.
namedre onChangeSend: #nameChanged to: self.
age := 0 asValue.
age onChangeSend: #ageChanged to: self.

MyInput2>>age
^age

MyInput2>>name
^name

MyInput2>>ageChanged
person age value: self age value

MyInput2>>nameChanged
person name value: self name value

MyView subclass: #MyView2
instanceVariableNames: 'name age '

MyView2>>initialize: aPerson
person := aPerson.
person name onChangeSend: #nameChanged to: self.
person age onChangeSend: #ageChanged to: self.
name := String new asValue.
age := 0 asValue.

MyView2>>age
^age

MyView2>>name
^name

MyView2>>ageChanged
self age value: person age value.
MyView2>>nameChanged
self name value: person name value.

MyPerson subclass: #MyPerson2
instanceVariableNames: ''

MyPerson2>>age
^age isNil
ifTrue: [age := 0 asValue]
ifFalse: [age]

MyPerson2>>name
^name isNil
ifTrue: [name := String new asValue]
ifFalse: [name]

## Example Three

MyInput subclass: #MyInput3
instanceVariableNames: "

MyInput3>>initialize: aPerson
person := aPerson.

MyInput3>>age
^(AspectAdaptor subject: person sendsUpdates: true)
forAspect: #age.

MyInput3>>name
^(AspectAdaptor subject: person sendsUpdates: true)
forAspect: #name.

MyView subclass: #MyView3
instanceVariableNames: "

MyView3>>initialize: aPerson
person := aPerson

MyView3>>age
^(AspectAdaptor subject: person sendsUpdates: true)
forAspect: #age.

MyView3>>name
^(AspectAdaptor subject: person sendsUpdates: true)
forAspect: #name.

MyPerson subclass: #MyPerson3
instanceVariableNames: ''

MyPerson3>>age
^age

MyPerson3>>age: aValue
age := aValue.
self changed: #age

MyPerson3>>name
^name

MyPerson3>>name: aValue
name := aValue.
self changed: #name

## Example Four

MyInput subclass: #MyInput4
instanceVariableNames: "

MyInput4>>initialize: aPerson
person := aPerson asValue

MyInput4>>age
| adaptor |
adaptor := AspectAdaptor subjectChannel: person sendsUpdates: true.

adaptor

accessWith: #yearsOld

assignWith: #yearsOld:

aspect: #age.

^adaptor

MyInput4>>name

| adaptor |

adaptor := AspectAdaptor subjectChannel: person sendsUpdates: true.

adaptor

accessWith: # called

assignWith: # called:

aspect: #name.

^adaptor

MyView subclass: #MyView4

instanceVariableNames: ""

MyView4>>initialize: aPerson

person := aPerson asValue

MyView4>>age

| adaptor |

adaptor := AspectAdaptor subjectChannel: person sendsUpdates: true.

adaptor

accessWith: # yearsOld

assignWith: # yearsOld:

aspect: #age.

^adaptor

MyView4>>name

| adaptor |

adaptor := AspectAdaptor subjectChannel: person sendsUpdates: true.

adaptor

accessWith: # called

assignWith: # called:

aspect: #name.

^adaptor

MyPerson subclass: #MyPerson4

instanceVariableNames: '"'

MyPerson4>> yearsOld

^age

MyPerson4>> yearsOld: aValue

age := aValue.

self changed: #age

MyPerson4>>called

^name

MyPerson4>> called: aValue

name := aValue.

self changed: #name

Example Five

MyInput subclass: #MyInput5

instanceVariableNames: 'trigger '

MyInput5>>initialize: aPerson

person := aPerson.

trigger := false asValue

MyInput5>>accept

trigger value: true

MyInput5>>age

| adaptor |

adaptor := AspectAdaptor subject: person sendsUpdates: true.

adaptor forAspect: #age.

^BufferedValueHolder subject: adaptor triggerChannel: trigger.
MyInput5>>name
| adaptor |
adaptor := AspectAdaptor subject: person sendsUpdates: true.
adaptor forAspect: #name.
^BufferedValueHolder subject: adaptor triggerChannel: trigger.

MyView subclass: #MyView5
instanceVariableNames: "

MyView5>>initialize: aPerson
person := aPerson

MyView5>>age
^(AspectAdaptor subject: person sendsUpdates: true)
forAspect: #age.

MyView5>>name
^(AspectAdaptor subject: person sendsUpdates: true)
forAspect: #name.

Model subclass: #MyPerson5
instanceVariableNames: "

MyPerson5>>age
^age

MyPerson5>>age: aValue
age := aValue.
self changed: #age

MyPerson5>>name
^name

MyPerson5>>name: aValue
name := aValue.
self changed: #name

## Example Six

MyInput subclass: #MyInput6
instanceVariableNames: "

MyInput6>>initialize: aPerson
person := aPerson asValue.

MyInput6>>person
^person

MyView subclass: #MyView6
instanceVariableNames: "

MyView6>>initialize: aPerson
person := aPerson asValue.

MyView6>>person
^person

Model subclass: #MyPerson6
instanceVariableNames: '"

MyPerson6>>age
^age

MyPerson6>>age: aValue
age := aValue.
self changed: #age

MyPerson6>>name
^name

MyPerson6>>name: aValue
name := aValue.
self changed: #name
Example Seven

MyInput subclass: #MyInput7
instanceVariableNames: 'trigger '

MyInput7>>initialize: aPerson
person := aPerson asValue.
trigger := false asValue.

MyInput7>>person
^person

MyInput7>>trigger
^trigger

MyInput7>>accept
trigger value: true.

MyView subclass: #MyView7
instanceVariableNames: "

MyView7>>initialize: aPerson
person := aPerson asValue.

MyView7>>person
^person

MyPerson subclass: #MyPerson7
instanceVariableNames: "

MyPerson7>>age
^age

MyPerson7>>age: aValue
age := aValue.
Transcript cr; show: 'Age changed'.
self changed: #age

MyPerson7>>name
^name

MyPerson7>>name: aValue
name := aValue.
Transcript cr; show: 'Name changed'.
self changed: #name

Example Eight

MyInput subclass: #MyInput8
instanceVariableNames: "

MyInput8>>initialize: aPerson
person := aPerson.

MyInput8>>age
^(PluggableAdaptor on: person)
  getBlock: [:model | model age]
  putBlock: [:model :aValue | model age: aValue * 3]
  updateBlock: [:model :aspect :parameter | aspect == #age]

MyInput8>>name
^(PluggableAdaptor on: person)
  getBlock: [:model | model name]
  putBlock: [:model :aValue | model name: aValue asLowercase]
  updateBlock: [:model :aspect :parameter | aspect == #name]

MyView subclass: #MyView8
instanceVariableNames: "

MyView8>>initialize: aPerson
person := aPerson.

MyView8>>age
^(PluggableAdaptor on: person)
  getBlock: [:model | model age * 10]
putBlock: [:model :aValue | ]
updateBlock: [:model :aspect :parameter | aspect == #age]

MyView8>>name
^(PluggableAdaptor on: person)
getBlock: [:model | model name asUppercase]
putBlock: [:model :aValue | ]
updateBlock: [:model :aspect :parameter | aspect == #name]

Model subclass: #MyPerson8
instanceVariableNames: ''

MyPerson8>>age
^age isNil
ifTrue: [age := 0]
ifFalse: [age]

MyPerson8>>age: aNumber
age := aNumber.
self changed: #age

MyPerson8>>name
^name isNil
ifTrue: [name := String new]
ifFalse: [name]

MyPerson8>>name: aString
name := aString.
self changed: #name