body { margin:0; padding:0; font-family:times; font-size:9pt; } .intro { padding:5; font-weight:plain; font-family:serif; font-size:9pt; } p { margin-top:5; margin-bottom:5; font-weight:plain; font-family:serif; font-size:9pt; } h1 { margin-top:5; font-size:16pt; font-weight:bold; text-align:center; font-family:sans-serif; } h2 { margin-top:20; font-size:10pt; font-weight:bold; text-align:left; font-family:sans-serif; } pre { color:#993333; font-size:9pt; } code { color:#993333; } .section { text-align:justify; padding:5; } .section .console { background-color:#003300; color:#22FF22; padding:5; } .section h1 { margin-top:20; margin-bottom:3; font-size:12pt; font-weight:bold; text-align:center; background-color:#555555; color:#BBBBBB; border:1; padding:5; border-color:#333333; border-style:groove; } .section h2 { margin-top:20; font-size:12pt; font-weight:bold; text-align:left; } .anecdote { margin-top:20; line-height:150%; font-family:Serif; font-size:8pt; background-color:#F9EC59; color:#555555; border:2; padding:2; border-style:groove; } .anecdote h2 { margin-top:0; font-size:10pt; font-weight:bold; text-align:left; } .anecdote code { color:#993333; line-height:100%; } .anecdote pre { color:#993333; line-height:100%; text-align:left; }
This document attempts to take you on a journey that explains the rationale behind the Cut The Crap software, and how it makes significant advances in a number of software areas.
"Cutting The Crap" is not just about making things simpler.
It is about addressing and solving the right problem at the right time. Understanding the nature of the systems we are developing.
In the same way that end users need a consistent application model for them to be effective, so developers need consistent, powerful and applicable models that they can utilise to build effective systems.
The Cut The Crap software packages are related at a number of different levels.
Whilst it may be possible - and is attempted elsewhere on this site - to explain each in isolation the best appreciation is gained through an understanding of the full set of technologies.
It therefore seems reasonable to attempt a single sequential presentation. Providing a more guided introduction to the software.
It is hoped that anyone - from interested school kids and journalists to professional IT people - should be able to follow this guide - this Odyssey - and discover some understanding of the technologies available.
It is further hoped.... that some will.
Sections like this will discuss more technical issues that may not be of interest to all.
This is an interactive journey. There are some things that you will have to do. You should accept right now that there may be some problems that will take you some time to think through before you can move on to the next stage.
Setting systems up - configuration - is often a pain, and few people, if any, enjoy it.
But, do not worry. There is nothing here deliberately to catch you out, and if you follow the instructions and leads given - and stay relaxed - it is hoped that the journey will be tolerable at least, and enlightening at best.
The journey has already begun since you are reading this document.
The next stage is to download and setup the Cut The Crap software and a number of other packages. It is important that all this is done correctly otherwise you will face unnecessary problems later on.
You will then be lead through a set of interactions with the different Cut The Crap technologies, from the storage packages through to the latest InterActor graphical tools.
If you have any problems - or comments - please email me.
You'll need to download the latest Cut The Crap software. This download includes all required packages for this document.
Create a target directory and unzip the downloaded file into it.
Now is a good time to set the environment variable CTC_ENV to refer
to the install directory, eg F:/test/cutTheCrap
The Cut The Crap software is all written in Java - a cross platform environment. You need to see whether your system has a good version installed.
Open a console window - on Windows this will be from the menu :
Start-Programs-Accessories-Command Prompt
Now enter :
java -version
You should see one of two responses.
'java' is not recognized as an internal or external command
..or
java version "1.4.1_01"
where the version number reported could be anything from "1.0" onwards.
If you have any response other than a reported version number greater than or equal to "1.4", you must download and install a new version of java.
If you continue with an older version of java there will be unpredictable consquences and horrible error messages.
Downloading and installing java is free, but may be time consuming on slower connections. The latest versions can be downloaded from here.
Although you can initially get by with the JRE - Java Runtime Environment, you will eventually need the full java SDK - Software Development Kit.
If you are not already a java developer it is unlikely that you will have the development kit installed. So save yourself some pain later on and download and install the latest development kit now. You will have to fill in a simple registration form with Sun to download the software, but this is quite straightforward.
The Cut The Crap software requires three other jar files. The internal logging system
uses log4j, a number of elements a sax parser, and the
Alchemist utilizes the Apache regexp package. The three
required jars are included in the distribution, but latest versions, with
full documentation and source code can be downloaded from :
Okay, so you have a valid java environment, right?
Locate the directory where you extracted the Cut The Crap zip file and make this the current directory in your command window.
On windows you can change disk drives by entering the drive letter followed by
a ':' (colon). For example D:. You can then "change directory" using the cd
command to navigate to the chosen directory.
Enter :
ctctest
You should see some message confirming that all is okay. If not, then confirm again that you have the correct java version and that you have entered the command correctly .. and that you are in the right directory.
On a unix system you should find a ctctest.sh file. You can rename this
ctctest and ensure it is executable :
mv ctctest.sh ctctest;chmod +x ctctest
This applies similarly for other commands later on.
The best kind of environments in which to learn are those where you can make mistakes and
try things again really easily. For this journey the Jython environment is used.
Jython is a version of the Python programming language written in Java
that makes it very easy to try out interactions with Java programs - precisely what we need here.
So, if you don't already have Jython installed you'll need to download and install it following the instructions provided. You can get it from here. Don't worry, it is a pretty small download - compared with the Java SDK in any case.
Once installed, you may need to add the jython directory to your path. If unsure about
how to do this, then check out the howTos section for either
windows or
unix section.
If you have changed the path variable, you will have to open a new
command window in order for it to have access to the modified variable.
In the distribution, along with the jar file and the javadocs are a few
other files which are included to help you.
A couple of these, jytest.bat and ctcenv.py are specifically to help
with jython experiments.
Enter
jytest
Jython should start up and leave you with a >>> prompt. If you get an error
you should check your current directory and confirm that jython is on your path
- just enter jython to confirm that it can start up.
The jytest command file simply loads jython and executes the
ctcenv.py script that imports the various Cut The Crap packages. Have a look at the
files to see what they are doing. They are quite straightforward.
The ctcenv.py includes a line import sys;, allowing the
interactive call to sys.exit() as the best way to exit the jython environment
tidily.
Enter
sys.exit();
You should now be back at the standard command prompt. Its always good to know how to quit a system.
Okay, if you've got this far you are ready to start exploring the Cut The Crap software properly. If you've never done anything like this before, then well done! Everything else should be a breeze.
First things first, start up the test environment by re-entering
jytest
Remember, that if you want to exit at anytime enter sys.exit();. The ';'
character is optional but personally I always use it to indicate the end of the statement.
The first package to investigate is the storage package.
Computers are fundamentally about the storage and manipulation of data. However data is retrieved and manipulated, at some point some data has been accessed in some format from permanent storage. Computer programs themselves are simply data that has been retrieved by some system and then used to provide instructions to a computer processor.
So storage and retrieval is a fundamental aspect of computing.
Right, at the >>> prompt enter :
>>> store = RWStore("/ctc/test/store.rw", 0);
If you are a Java programmer new to Jython then you should know that
there is no need for the new keyword in Jython. If you are not a Java
programmer - don't worry.
You should be able to find the created file on the file system using a file browser. If necessary, the software will have created the required directories to build a valid path.
You will find that a lock file has also been created. This is
used to prevent multiple java applications trying to update the same file.
The store package includes several simple helper classes, one of these
is the StringStore that helps save and retrieve
simple strings.
>>> sstore = StringStore(store); >>> s1 = sstore.save(0, "Some Text");
To store a string, and:
>>> sstore.read(s1); 'Some Text'
... to retrieve it.
The variable s1 is set to a reference returned by the save
method that can then be used to retrieve the original string using the read method.
If you've not done any programming before, or feel the syntax is a bit strange, just take a while to look
at what has been written - hopefully it will start to make a little sense. Remember that
x=y; sets the variable x to the value of y
An equally fundamental feature to data storage and retrieval, is storage reallocation.
This means that some data value needs to be updated. In the simplest scenario, the same data area that stored the original values, might simply be overwritten. But this is not sufficient in general.
Consider the case that our original string "Some Text" should be replaced by "Some More Text". With the emphasis on the "More".
Since the second string is a different size to the first it is not sufficient to simply overwrite it. This is the difference between "reallocation" and simple "update".
You may have noticed that the save method took two parameters,
sstore.save(0, "SomeText");. The first parameter was a data reference to be
reallocated, and '0' was a special value that indicated no existing reference.
If we want to update - reallocate - our string we should enter the following:
s2 = sstore.save(s1, "Some More Text");
The new variable s2 now contains the reference of the updated string and the
original reference s1 can be reused for some other data later on.
When using a word processor and working on a document, every now and then - if you are sensible - you'll save the file. This hopefully will ensure that a system failure of some kind won't mean that you'll lose all your hard work. But what you wouldn't do, is save your document after every key stroke.
A Cut The Crap store file might be several gigabytes in size. So it is simply not
a scalable approach to hold the whole store in memory and save it all to disk every now and then.
Instead the storage structures allow for incremental changes to the store to be made efficiently and in a constant time independent of the size of the store file.
With such a system comes the idea of a Transaction. A Transaction is
an "update unit", in other words, the store file will be modified in chunks, and these "chunks" are
called Transactions.
A Transaction provides a scope, or context, within which a number of separate changes
can be made together.
Transaction processing is a fairly developed concept in computing and comes with
some additional terminology. Transactions are started and then either
commited or rolled back, which is a bit like "revert to saved"
in a word processor.
In any case, this is a good place to think about what Transactions are.
The Cut The Crap storage package defines a set of functions to control a
transaction.
These are simply startTransaction(), commitTransaction() and
rollbackTransaction().
The utility StringStore class already used, internally started and
committed transactions whenever it saved a new string.
The Cut The Crap transaction implementation allows for nested startTransaction and
commitTransaction requests. Only performing a "real" commit when the
transaction has been "unwrapped" completely.
You can create your own transaction scopes by entering:
>>> store.startTransaction();
...save some strings, and then enter:
>>> store.commitTransaction();
This probably doesn't seem very exciting just now. But the relevance of the
Transaction model will become apparent later.
The StringStore was a useful little helper to introduce the idea of storing
and retrieving simple - string - values.
Another helper class is the ObjectStore demonstrating that the store
can be used to contain different kinds of data.
Trying out the ObjectStore will also introduce some ideas that are developed
much further by the Cut The Crap Generic Persistant Object system - GPO - later on.
Enter the following:
>>> obstore = ObjectStore(store);
It turns out that the obstore appears to do pretty much the same as the
StringStore, it just does it more generically.
It achieves this by storing all data as Java objects, where the StringStore only
dealt with Java Strings.
>>> s3 = obstore.save(0, "A Java String Object"); >>> obstore.read(s3); 'A Java String Object'
But the ObjectStore can be a little more useful. For example, it could store
a java HashMap object.
>>> hm = java.util.TreeMap();
>>> hm.put("value", s3);
>>> r1 = obstore.save(0, hm);
A HashMap object allows values to be stored against a specified
"key". In the example above, the key specified is the string "value" and the
value is the reference to the string object saved earlier.
We shall now use a new method. setRootAddr on the store object
allows us to save a single reference with the store. When we re-access the store
later on, we can retrieve this value using getRootAddr, here is how it works:
>>> store.setRootAddr(r1);
Now, we can write:
>>> root = store.getRootAddr();
>>> rootOb = obstore.read(root);
>>> obstore.read(rootOb.get("value"));
'A Java String Object'
Some of you will think this is all pretty straightforward, whilst others will probably already be really confused. If you are confused, don't worry, just go over the document and take a while to think about it.
The point of this last example is to show how a simple object store can be built over the
basic storage package. Shortly you will examine the Cut The Crap
Generic Persistant Object system which takes things on a few levels, but is still built
on the same storage system.
The two utility objects we have used so far have made it easy to demonstrate that data can be saved to and retrieved from the store.
These utilities use an underlying IStore object.
This interface uses stream objects and this is a good point to have a look
at what this means.
A stream in computing is some object that supports the reading or
writing of a sequence - a stream - of bytes. For example, the string "Hello World"
would be written to a stream by writing each character in turn, 'H', 'e', 'l', etc.
The IStore object will provide an output stream when a request is
made to reallocate an address. Data is then written to the stream and the stream
is saved to the store. Here is a code fragment from the StringStore.
public int save(int oldAddr, String str) {
m_store.startNativeTransaction();
byte bytes[] = str.getBytes();
PSOutputStream psout = m_store.realloc(oldAddr);
psout.writeInt(bytes.length);
psout.write(bytes, 0, bytes.length);
int retval = psout.save();
m_store.commitNativeTransaction();
return retval;
}
The PSOutputStream and PSInputStream objects provide additional
utility methods to support the direct writing of other stream objects.
Suppose we have an MP3 file "/music/fav.mp3", in our jython environment :
>>> store.startTransaction();
>>> os = store.realloc(0);
>>> os.write(FileInputStream("/music/fav.mp3"));
>>> fav = os.save();
>>> store.commitTransaction();
and we can retrieve the data and write to another file like this:
>>> is = store.getData(fav);
>>> is.read(FileOutputStream("/music/fav2.mp3"));
You are now going to take a quick look at the Cut The Crap "Write Once" Store.
First let's start a nice clean session:
>>> sys.exit();
and
jytest
The write once store is accessed using a different object, previously you used
RWStore - for "Read Write Store", this time you'll use the WOStore.
>>> wos = WOStore("/ctc/test/store.wo", 0);
"Write Once" means that it does not overwrite already stored data, enter the following:
>>> os = ObjectStore(wos); >>> s1 = os.save(0, "First String"); >>> s2 = os.save(s1, "Second String"); >>> s3 = os.save(s2, "Third String");
You can now read each of the previous values back as usual, using s1, s2 and s3 since
no data has been overwritten. Try it and see.
This is maybe not so interesting in itself. More useful is that because the call to
save - and more importantly the underlying call to realloc passed in the
old address, this chain of values can be retrieved.
>>> os.read(wos.getPreviousAddress(s3)); 'Second String'
This may seem like a simple idea - and it is - but it is extremely powerful.
Using the RWStore the method getPreviousAddress will always return 0.
Hopefully, with experience of the storage package you will have some appreciation
that data, for it to persist, must be stored and retrieved in some way.
With the GPO model, the persistance mechanisms are hidden from the programmer. The programmer
does not make calls to the underlying storage package. Instead a new object, the
Object Manager provides services to manage object references.
The Object Manager is not magik. It stores special data structures in the
store that allow it to match object references against storage references, maintaining
a crucial property - Object Identity. Let's see how this works.
First, exit the current jython session:
>>> sys.exit();
..and start a new one by entering:
jytest
So we have a nice new session without any old values lying around that might confuse us.
The first object we will use is OMClient, this helps us to initialize the
system, and provides us with an Object Manager. Enter :
>>> client = OMClient("", "/ctc/test/omstore.rw");
>>> om = client.getObjectManager();
These two lines have first of all initialized a system that uses the specified file as
the storage file for a new Object Manager and then returned that Object
Manager, setting the variable om.
From now on you forget about the store, only the Object Manager
is important to you.
The next important object you will use is the GPOMap. Its importance cannot be
overstated so it is probably best not to discuss it. Let's create one:
>>> g1 = GPOMap(om); >>> g1 1
Note that to create a GPOMap object we needed the om variable that
reference the Object Manager, another option would allow another GPOMap
object to be passed instead, indicating that the new object should be stored with the
existing object. This is not something that should concern you for now however.
Okay, you can set some values:
>>> g1.set("name", "first object");
>>> g2 = GPOMap(om);
>>> g2.set("name", "second object");
>>> g1.get("name");
'first object'
>>> g1.set("friend", g2);
>>> g1.get("friend").get("name");
'second object'
Do you understand what happened here? If it all seems a bit much, just try to understand each individual statement and think about what is being requested.
You have created two objects - g1 and g2 - given each a "name", and
made g2 the "friend" of g1.
The final request was to get the "friend" of g1 - (g2) - and
then to get its "name".
If Travis Bickle had been a GPOMap object he would have known for sure.
If a GPOMap object references another, then that object knows about it.
>>> ls = g2.getLinkSet("friend");
>>> ls.size();
1
This tells you that the set of GPOMap objects that reference g2
with the "friend" property has one member.
>>> ls.getFirst().get("name");
'first object'
You don't believe me? Try this.
>>> om.remember("G1", g1);
>>> sys.exit();
Now start the session as before:
jytest
>>> client = OMClient("", "/ctc/test/omstore.rw");
>>> om = client.getObjectManager();
>>> g1 = om.recall("G1");
>>> g1.get("name");
'first object'
>>> g2 = g1.get("friend");
>>> g2.get("name");
'second object'
>>> g2.getLinkSet("friend").getFirst().get("name");
'first object'
The remember and recall methods should only
be used for a very few objects. In many systems only a single object needs to be
remembered in this way, all others can then be navigated from this one.
When one object has a reference to another, this is described as an object
Association.
The previous example indicates that for every property there is a set of objects that
might use that property to reference another object. Therefore any property can be used
to define a many-to-one association.
All associations have a "direction", "I'm your friend, will you be mine?".
>>> g3 = GPOMap(om);
>>> g3.set("name", "third object");
>>> g3.set("friend", g2);
>>> g2.getLinkSet("friend").size();
2
It would seem that g2 is starting to get popular. The LinkSet
can be examined using an iterator object, here is some Jython that does this.
>>> iter = g2.getLinkSet("friend").iterator();
>>> while iter.hasNext() :
>>> print iter.next().get("name");
>>>
'first object'
'third object'
You have to be careful here. After the while statement you use a
':' colon character. Use 2 (two) spaces before the print line, and then hit enter
twice after completing the line. This is needed because the python syntax is based
on indentation to define blocks of code, and the ':' character indicates a following code block.
Clearly a one-to-many association structure will support a requirement for a
one-to-one association. But what about many-to-many? From our
example, it would appear that although g2 can be a "friend" of many objects each
object may only set a single object as their "friend".
To setup a many-to-many association an intermediate object is required.
This may seem like its getting complicated, but that is the nature of this problem.
To help manage the configuration issues, a utility class ManyManyLink is
provided.
>>> ManyManyLink("aFriendOf", g1, "aFriendTo", g2);
Which can be read as :
g1 is "aFriendOf" g2 g2 is "aFriendTo" g1
Let's add a couple more:
>>> ManyManyLink("aFriendOf", g3, "aFriendTo", g2);
>>> ManyManyLink("aFriendOf", g1, "aFriendTo", g3);
Okay, it would appear that we have managed to stitch the objects together, but how can they be accessed?
>>> ls = g1.getLinkSet("aFriendOf");
>>> ls.size();
2
>>> ls.getFirst().get("name");
'second object'
Okay, so what happened there? The LinkSet recognized the ManyManyLink
object and automatically "resolved" the set members through the links it managed. So you
can consider access to a "many to many" association in the same way as a "one to many"
association.
Since the ManyManyLink object is used in this special way, it is declared as
final so that its use cannot be modified.
To make things even easier, there is a utility method on IGPOMap to create
the ManyManyLink implicitly, we could have achieved the same by:
>>> g3.addManyMany("aFriendOf", "aFriendTo", g2);
>>> g1.addManyMany("aFriendOf", "aFriendTo", g3);
You'll be glad to know that that completes everything on object associations.
The LinkSet objects that result from object associations are now addressed.
Suppose the LinkSet contains ten million members. How is such a set managed?
If it is necessary to be able to access specific members of a set, traditionally this would
be done by some kind of lookup function.
"Find the member of set A with the name 'Blah'."
This is managed using an object called a Classifier, used like this :
>>> om.registerClassifier("link", "value");
>>> p = GPOMap(om);
>>> c1 = GPOMap(om);
>>> c1.set("value", "C1");
>>> c1.set("name", "just another object");
>>> c1.set("link", p);
>>> ls = p.getLinkSet("link");
>>> lkup = ls.getClassifier("value");
>>> lkup.getValue("C1").get("name");
'just another object'
The classification structure maintained is very efficient and can be used to index extremely large sets of objects.
Classifiers know about ManyManyLink objects and process them transparently.
Relational database experts might like to think what the equivalent of this would be.
A recent significant enhancement to GPO is the ability to define spreadsheet-type
formulas. By "spreadsheet-type" I mean that values are kept uptodate by recalculations
triggered on values on which they depend changing. This will be an extremely brief introduction.
Firstly, a formula is always calculated within the context of some object. Property values
of the object are accessed using a lookup function. Have a look at this example:
>>> g = GPOMap(om);
>>> g.set("value", 10);
>>> g.define("vat", "(* 0.175 (-> value))");
>>> g.get("vat");
1.75
Note that the expression (-> value) returns the value of the property "value".
Now if we modify the value, the "vat" will be re-calculated:
>>> g.set("value", 20);
>>> g.get("vat");
3.5
Formulas can be combined quite simply:
>>> g.define("total", "(+ (-> value) (-> vat))");
>>> g.get("total");
23.5
A number of formular primitives - such as * and -> are provided as
standard, but mechanisms are available to define new primitives in a straightforward
way. You can see the current list of built-in primitives here.
There has not been much explanation here. Each section demonstrates a direct solution to a specific problem or requirement. The examples used have been relatively simple but be in no doubt that the techniques demonstrated have been designed from the beginning with scalability in mind.
There are many other aspects that could be presented and discussed, but the essential information is here.
The Alchemist generates java code that exploits the GPO model from a minimal system specification.
"Minimal" means just that. The "minimal" amount of data needed for the Alchemist to infer the intended system is entered. This is not magik, but it is smart.
We have already seen in the storage and GPO packages that
significant aspects of computer systems can be reduced to a number of relatively simple
patterns. The next stage is to determine higher level system characteristics and see
whether these follow deterministic (predictable) patterns.
The Alchemist generates Java source code that must be compiled to provide a working
system. To help in this process, files are also generated that can be used by the ant
tool to manage the compilation of the system.
You will therefore need to to have installed on your computer not only the java SDK (rather
than just the JRE), but also ant. This can be downloaded from
here, it about 7Mb in size.
The download is a zip archive that should be expanded, the next task is simply
to add the ant bin directory to you path system variable. Once this is
done you should be able to enter ant -version at a new command window, and
get a meaningful response.
Before we go any further, you'll need to setup a system environment variable CTC_ENV.
This should be set to the fullpath of the cutthecrap directory downloaded. It
should include the name of the directory itself - in case you have changed the name, for example
F:\test\cutthecrap.
In this directory you will find a src directory and within that an alchemy folder.
This contains a single build.properties file and a contacts
directory. The contacts directory contains a file - contacts.orm.
The console generation process will be driven by the Ant tool, and for this
we will need a gen.xml file. Since it is always error prone to write this
kind of thing for yourself, the Alchemist provides a utility to generate the file that
will be used to generate the system.
From the current directory enter :
..\gengen contacts
This should have created the required gen.xml file. If you're interested you
could take a look at it.
The contacts.orm xml file contains all the xml definitions needed for this
exercise, but you will "unwrap" the functionality by "uncommenting" areas of the file as
we proceed. Another option would have been to include a number of copies of the file in
different states, but I thought if anyone got this far they would be happy to edit a file.
The system that will be generated can be used to maintain a list of contacts, like an address book.
Each contact will correspond to a single individual and will have a number of simple properties: name, email, phone number ... and so on.
We will then add some more structure by introducing the concept of a group
that a contact may be a member of.
After each stage, you will be encouraged to examine the generated code, and to
start a jython session to play with the system.
Open a command window and make the contacts directory the current directory.
Enter :
ant -f gen.xml
You should see some status output as first any existing generated code is removed, new code generated, and then compiled.
If you now enter :
ant docs
The javadocs for the generated system will be produced. You will find them
in the directory products/contacts/docs under the directory referred to by the
CTC_ENV environment variable.
Startup a new jython test environmemt:
jytest
and use the first generated system:
>>> from alchemy.contacts.client import *;
>>> client = ContactsClient("", "/ctc/test/contacts.rw");
>>> book = client.getAddressBook();
This is in fact all that you can do at this stage. The only behaviour described
in the orm file is that there is some class AddressBook that is
the root object of the contacts system. You can confirm this by entering
the following:
>>> book.getClass().getName(); 'alchemy.contacts.AddressBook'
Okay, that is enough of that, quit the test environment with sys.exit();.
Open the contacts.orm file in an editor - notepad is fine if you
insist - and have a peek.
If you are unfamiliar with <xml> syntax do not worry. It is
a little ugly but does make sense after a while. For this exercise you just
need to know that the <!-- and --> characters are
used to delimit a comment. You will see these characters throughout this file and it should
be apparent what the intention is.
The only active - uncommented - part of the file at the moment is the orm
element that defines the package name to be generated, and the class
element defining the root object.
You should now 'activate' stage two, by removing the comment character sequences
that delimit the next two elements - the class and assoc
elements.
Save the file, and rerun the generator. It might also be interesting now to take a proper
look at the javadocs generated by :
ant docs
Locate the projects/contacts/docs directory and open the index.html
file. Your package is contacts and you'll be interested in the AddressBook
and Contact classes. You might checkout the ContactsClient class in the
client package also.
Things can get a little more interesting now:
jytest
First you need to import the ContactsClient so we can get started:
from alchemy.contacts.client import *;
Now you should be able to get into the system proper:
>>> client = ContactsClient("", "/ctc/test/contacts.rw");
>>> book = client.getAddressBook();
>>> c1 = book.createContact("First");
Okay, to explain.
Why does the client have a method getAddressBook?
Because the AddressBook object was defined as the root of the
system, and the client's task is mainly to provide access to the root
object.
Why does the AddressBook have a createContact method, and why
does it take a single argument?
Because the AddressBook object was defined as the owner of
the Contact objects and the 'Name' attribute of Contact was
defined as required, so must be provided in order to create the object.
If you look in the javadocs or the source code directories, you will see
a class - IContactIterator. The Alchemist generates typed
iterators that provide additional methods to the standard Iterator interface.
In IContactIterator you will find a nextContact method that will
return a Contact object.
If you look more closely you will also see that it extends the GPO
class IStriterator which supports a number of interesting features - explained
elsewhere.
Exit the test environment again, and re-edit the contacts.orm file to uncomment
the last block of definitions.
This last block defines the Group class and its associations with both the
AddresBook and Contact classes.
Save the file and re-generate the system from the command line.
Now you are able to createGroups and add Contacts to a group.
You are also able to getGroups from the AddressBook,
getGroups from a Contact - to see which Groups a
Contact is in, and also getContacts from a Group to
see which Contacts are withing each Group.
Checkout the regenerated javadocs and have a play in a new test environment.
I hope that this small demonstration has convinced you of the possibilities.
The following interactive demonstration of InterActor should be a nice break from the earlier activities.
The demonstration of the InterActor model is made by controlling a display structure
interactively using both the jython console and the generated graphical
interface.
So exit any existing session and start a new one with jytest.
Start up an InterActor window by entering:
>>> ia = Helper.startNew(0);
This should have created a new system window titled "InterActor". The 0
parameter indicated that the java system should not exit when the window is closed - since
we are controlling this from jython. Let's carry on :
>>> root = ia.getRootShape(); >>> root.getClass().getName(); 'cutthecrap.ia.IAShape'
The display structure of InterActor is defined by two types of objects,
IAShapes and IASlots. As the exercise progresses their use
should become clear.
>>> w1 = ia.createWindow("Tester", 170, 100);
>>> w2 = ia.createWindow("Other", 170, 100);
These windows can now be displayed by adding them to the root shape.
>>> root.addChild(300, 30, w1); IASlot >>> root.addChild(300, 150, w2); IASlot
You should see each window appearing in the InterActor window as you
add them. Note that addChild returns an IASlot object.
addChild is a utility method that creates an IASlot
object at the position given, and then sets the contents of the slot to the IAShape
provided.
We'll now create another slot directly:
>>> slot = root.createSlot(50, 50);
and we'll create and set a Transition.
>>> trans = ia.createVenetian(15, 500); >>> trans.addPhase(ia.makeColor(120, 120, 120)); >>> slot.setTransition(trans);
Now we'll set an alpha transparency value that will be applied to anything
that is placed in the slot.
>>> slot.setAlpha(0.6);
See what happens when you enter the following:
>>> slot.setContents(w1);
That was a bit of a distraction, we'll now create some "radio" buttons, using
a generic 2State class. A utility method create2StateLink
defines buttons that when actioned will set the contents of some IASlot
to some IAShape.
>>> tlink = ia.create2StateLink("Tester", slot, w1);
>>> olink = ia.create2StateLink("Other", slot, w2);
>>> elink = ia.create2StateLink("Empty", slot, None);
Cluster them with each other so that they ensure only one is on.
>>> tlink.clusterWith(elink); >>> tlink.clusterWith(olink);
Now we'll sprinkle them around a bit, this'll look a bit odd but will demonstrate a few things.
>>> w2.addChild(20, 20, tlink); >>> w1.addChild(20, 20, olink); >>> root.addChild(20, 270, tlink); >>> root.addChild(100, 270, olink); >>> root.addChild(180, 270, elink);
Okay, click away and see what you see.
The Alchemist generates a functional model at the system level. A
programming interface is generated that allows programs to be written that interact
with the generated model.
By itself this is a huge step forward.
But once we have begun to gain a generic appreciation for the way that systems can be used, it seems we can go further.
One of the elements generated by the Alchemist is a method called
getMetaSpec.
The MetaSpec returned by this method produces a description of the object.
And this description is sufficient to consider the generation of a "user level" graphical
interface. One that is usable, scalable, reliable and consistent.
Java programmers might consider this similar to the BeanInfo
and specifically the FeatureDescriptor classes. Further down the road it
may be possible to use BeanInfo classes but for now it is important that
the protocol can be modified as needed.
Can this be achieved?
We can but try.
This objective has been behind the development of InterActor to provide
a more generically structured basis for the required interfaces, and one where it was
possible to modify underlying protocols in any generalization process.
You are toward the end of your Odyssey, and are now in step with Cut The Crap on its own Odyssey. In a while you will get a glimpse of the direction that we are travelling, and any and all feedback is appreciated.
The next immediate stage is the continuing refinement of the generated InterActor
based interfaces, and refinement of GPO and MetaSpec protocols
to support it.
Work will continue on the development of the spreadsheet type programming support. Integration with Alchemist generated code will increase the sophistication of the generated object models.
But for now, how far have we got?
We will revisit our Alchemist generated model.
If you locate the "contacts" directory using a file explorer, you will find a "bin" folder has been generated. Within the bin folder, you will find three files - browse.bat browse.sh and contacts.desc.
On windows you should be able to simply drag the contacts.desc file onto the browse.bat file, on unix you should first ensure that the browse.sh file is executable.
You should see a window open. On the left hand panel, at the top should be an
element with a small red ball beside it, labelled "AddressBook". The "red ball" will
become familiar to you, it indicates a GPO element. Click, drag and release
the element over the main window area.
A new internal "Inspector" window should popup smoothly.
An Object Inspector can be created by dragging any GPO object element
onto a clear part of the desktop.
References to a GPO object may be "saved" by dragging into a clipboard "slot"
on the left panel.
If you click on a GPO element it will replace the current object in the local
inspector.
On the left pane, are the "view" options. If the object has any "attributes", then an "attributes" option will display them. Similarly for single "associations".
If the object can provide access to any "sets" these too are available, but each "set" is listed separately. If there are more than two "sets", a "popup" will provide access to the full list.
If the object can "create" any other objects, these "creation methods" are similarly accessible.
Lastly, if any "business" methods have been specified, they too can be accessed and invoked.
Have a play. Let us know what you think about it. All ideas and suggestions are welcome.
Nope, but we have at least begun.