Several documents on this website try to make you believe that KnownSpace has some special properties that make it easy to write complicated, collaborative, yet decoupled applications. The best way for us to prove that to you is to walk you through the way the Helium application works so as to demonstrate how this all happens in practice.
All of the action in Helium takes place inside Simpletons. When Helium is launched, a host of Simpletons are started by the system. We're not going to list them exhaustively now. Instead, we'll introduce each to you as things go along. The important thing to recognize is that, when the system starts, all of these Simpletons are running at the same time and are basically idle until the user makes something happen.
One of the windows Helium presents is the Email Configuration
Options window, which asks
for user email parameters (IMAP hostname, email address, password, etc.). This window
is produced by a Simpleton: EmailConfiguratorSimpleton. When this Simpleton was started
(that is, had its
process() method called), it produced this graphical
component and asked the system to display it. When you press the button labeled "Ok",
the EmailConfiguratorSimpleton packages up your email parameters into an
EmailParametersEntityValue, adds them to the pool, then fires an
EmailParametersEnteredCollectorEvent to indicate that email parameters have been added
to the pool. That's the end of EmailConfiguratorSimpleton's involvement.
Firing events into the pool is an act of faith on the Simpleton author's part. Unlike a method call in a conventional object-oriented programming environment, there's no guarantee that anyone is out there listening for your message. On the other hand, this is where flexibility comes from. There's nothing preventing a completely different (or, e.g., 18 different!) Simpleton than the one the author expected from hearing the event and doing something slightly different or more advanced with it.
In the case of Helium, the EmailRetrieverSimpleton happens to be listening for
EmailParametersEnteredCollectorEvents. And when it hears one (that is, when its
handle() method is called), it pulls the email parameters out of the
EmailParametersEntityValue and begins retrieving your email.
When the EmailRetrieverSimpleton begins downloading your mail, it fires a StartedRetrievingEmailEvent. When it finishes, it fires a StoppedRetrievingEmailEvent. As the system operates now, no one is listening for these events and so the user doesn't see anything. But because the events are fired, it would be easy to add a Simpleton that produced an animation of some sort to indicate when email was downloading and when it was fully downloaded.
When a message is retrieved from the IMAP server, the EmailRetrieverSimpleton creates an entity to represent that email and attaches to it as an attribute an entity whose name is "content-source" and whose value is the StringEntityValue "email". This sort of tagging can later be used, with a properly written EntityConstraint, to comb the pool only for entities that are email messages. (As we shall see, there are many other types of entities.)
EmailRetrieverSimpleton also creates an entity for each header on the email message. For example, corresponding to the From: header in an email message, EmailRetrieverSimpleton would create an entity whose name is "from" and whose value is a StringEntityValue matching the value in the From: header of the actual message. All of these entities are also attached as attributes to the original email message entity.
For the body and each attachment the message may have, EmailRetrieverSimpleton creates an entity whose value contains that portion of the message content. These entities are also attached as attributes to the original email message entity.
Finally, the message is added to the pool so that other Simpletons can find and work with the data within.
We left out one small detail about the email retrieval procedure: avoiding duplicate messages. Because email is not deleted from the server when it is retrieved by Helium, each time the user presses the "Ok" button, we begin again examining the same email we've already retrieved. In order to avoid having duplicate messages in the pool, we must compare each message we retrieve from IMAP with other messages in the pool to make sure it does not already exist. We do that using constraints. In this particular case, we write an EntityConstraint that combs the pool for any email message with a matching Message-Id. The code below shows how this is done.
EntityConstraint messageIdConstraint = new AndEntityConstraint( new NameEqualsIgnoringCaseConstraint("Message-ID"), new ValueEqualsConstraint(new StringEntityValue(messageId))); Entity duplicates = Pool.getDefaultPool().search(new AttributeExistsConstraint(messageIdConstraint));What's happening here? messageIdConstraint matches any entity whose NameEqualsIgnoringCase "Message-ID", and whose ValueEquals the string in messageId. We then search the pool for all entities which have an attribute that matches messageIdConstraint. The result of that search, stored in the array duplicates, will be any messages that have the same Message-Id as the message currently under consideration. We discard rather than download that message if there are any duplicates.
Events are, in some sense, like sounds. One Simpleton might fire an Event, essentially making an announcement to all the other Simpletons. We then speak of other Simpletons "hearing" certain events.
When entities are added to the pool, they make a sound of their own. A sort of splash that we call AddEntityPoolEvent. The BasicEmailViewerSimpleton is the Simpleton responsible for the window labeled "Email Entities Display". It's listening for the splash of new email messages, using an EventConstraint. When it hears such an event, it parses content out of that Entity and adds a line to its tabular display.
There's another Simpleton listening for new email messages. (Remember what we said earlier about flexibility and having a multiplicity of listeners?) The TouchgraphEmailDisplaySimpleton is responsible for the window that says Touchgraph Email Display, and when it hears about a new email message, it adds it to the onscreen graphical display.
When you double-click on a message in either the BasicEmailViewerSimpleton or the TouchgraphEmailDisplaySimpleton, an EntityDisplayRequestEvent containing a pointer to the email message's entity is fired. And that's where these Simpletons' involvement ends.
EmailDisplaySimpleton is listening for events of just this type. It has a specially-constructed EventConstraint that selects for EntityDisplayRequestEvents that point to email messages. When it hears one, it pulls out the message content and displays it in a window.
At the bottom of the EmailDisplaySimpleton's window is a Reply button. Press it and you will fire an EmailReplyRequestEvent. Here ends the EmailDisplaySimpleton's involvement.
BasicEmailReplySimpleton listens for EmailReplyRequestEvents, and upon hearing them constructs an appropriately quoted message addressed to the sender of the original. You may edit this message, and when you press the button labeled "Send", an SMTPSendRequestEvent is fired by the BasicEmailReplySimpleton.
The SMTPSendSimpleton is listening for events of this type, and when it hears them, it sends your reply to your SMTP server for delivery to its final recipient.
As you can see, joining together Simpletons with simple Events provides for a type of extremely weak coupling. Because these events may be heard by anybody and handled in any way, the applications constructed using KnownSpace end up being extremely flexible, and it becomes quite simple to modify portions of these applications without having to understand the application as a whole.