The Multivalent system is written in Java, and Java 2 is quite large, but as long as you are conversant with the core language and packages that should be enough of a base to learn the selected other particular Java packages used. There are many books on Java (most of them awful), but if you know a programming language, especially if it's object oriented, the Sun's online tutorial is enough.
When possible, the browser builds on Web standards, such as XML, HTML and HTTP, which are well documented by W3C.
It is helpful to have built a graphical user interface (GUI) to gain acquaintance with event-driven programming. Note that, despite the fact that the system is programmed in Java, it does not uses Java's event mechanism, beyond obtaining the mouse and keyboard events from the top-level window.
Once one is acquainted with the Multivalent system, the key intellectual task in adding a new behavior is determining how to map the desired functionality into protocols. Your behavior will be called by the system framework at the right time to have its effect, then relinquish control back to the framework in order to compose well with the other behaviors in the system. The Multivalent Architecture is event driven, and ordinarily behaviors wait for something to happen, such as a call to paint itself on the screen or a openDocument semantic event, take their usually small and quick action, then give other behaviors a turn.
Your behavior must extend the system class Behavior and override the methods relevant to accomplishing its effect. In addition to behaviors, the system is also extended by adding tree nodes types, as is often done by media adaptors.
Most of the drugery is taken care of in base classes, leaving new behaviors to focus on what's new. However, to take advantage of base classes when overriding a protocol method, you almost always want to invoke the superclass implementation as well, with a super.protocolBefore(same args) at the head of before methods, and super.protocolAfter(same args) at the tail of after mthods. Don't forget to check the return values for possible short-circuits.
Event propagation is not standard Java event propagation, neither Java 1.0 nor 1.1 styles. Low-level mouse, keyboard, and window events are taken from the standard event queue and generated high-level semantic events are queued there, but internally events flow through the document tree or round robin through behaviors. This propagation allows behaviors to filter and short-circuit other behaviors, and in general be part of the event chain by virtue of their fundamental place in the document without additional bookkeeping. For instance, hyperlinks receive mouse events when the mouse cursor passes through the relevant part of the document, because low-level events pass through the document tree and the hyperlink is part the tree in that area. Behaviors that are not logically part of document content can affect subtree behavior by registering interest (with Node.addObservor), as in the Java 1.1 event model, but they gain access to all tree protocols on that subtree, not just events.
Part of the motivation for Java's standard event propagation is to reduce the time spent propagating events. While the model probably provides ample flexibility for standard GUIs, I have found that the more extensive propagation necessary for the more powerful Multivalent protocols in no way taxes performance. Tree protocols (format, paint, low-level event) propagate the event to all nodes in the tree that contain the current cursor position. This is fast due since the deep hierarchy of the tree leaves most of it clipped away for any particular event. Usually only a few behaviors have registered interest in any node in the tree, so they have a minimal impact on performance. Round-robin protocols (build, save, restore, high-level semantic events) propagate the event to all system and document-specific behaviors. This is fast enough because these event types are infrequent enough that the several hundred method calls employed are easily amortized by today's processors.
Most protocols have before and after phases. In tree walk protocols, such as paint or clipboard, when the flow of control enters the subtree rooted at that node, the protocolBefore methods of the registered behaviors are invoked, then the tree walk proceeds on the node and its children if any, and finally the behaviors' protocolAfter methods are invoked. For round robin protocols, the before methods of all behaviors are invoked, then the after methods in reverse order. The before phase allows behaviors to establish conditions, which ordinarily means that they take their usual action largly independent of other behaviors. For example, media adaptors build up the document tree from the concrete document format, and behaviors with features controlled by GUI widgets create them. This gives a known state that behaviors in the after phase can tweak. For instance, span annotations wait for the document tree to be constructed during buildBefore by whatever media adaptor, then attaches itself on the tree known to have been constructed by that time. Also, before can prevent action by behaviors farther in the chain with a short-circuit, and an after short-circuit can prevent action by behaviors earlier in the chain on the way back (a behavior farther in the chain cannot short-circuit the before phase of a behavior earlier in the chain).
Whereas components in most systems are tighly bound together with function calls, Multivalent behaviors communicate with one another at a very level via semantic events. When a behavior is going to do something, or has done something, or wants some action performed by another behavior, it should announce this by sending out a semantic event. Since semantic events are passed through all behaviors, any behavior can participate. Menus are built by creating the root and some intermediate nodes for menu categories, then sending it around in a semantic event at which time any behavior can add entries to the menu in the appropriate category. Behaviors even call themselves through semantic events, announcing sending a semantic event and then catching it again, possibly mutated (possibly short-circuited), in semanticEventAfter. As a longer example, document loading can requested by requested by any behavior by sending out a semantic event with the message openDocument and URL in the data field. The SystemEvents behavior will transform this into a more elaborate data structure called CacheInfo with fields that hold the InputStream, document genre, HTTP return code, HTTP headers, and so on. Cookies are implemented with a behavior that checks the URL against its cookies and adds cookie headers as appropriate. Eventually the cache will be an ordinary behavior too, and will turn the URL into an InputStream, taking it from the cache on disk or if necessary opening a network connection. Eventaully, the automatic decompression of GNU gzip will inspect the URL and hook onto the InputStream here. When the document is loaded, a "openedDocument" announced a successful load. The cookies behavior collects any new cookies. The forward/back buttons add the document to their list. The history behavior adds the document to its list.
Behaviors can spontaneously form special interest groups for tighter, performance-intensive integration. Lenses, for example, combine effects where they overlap. This computation is processor intensive, and involves all lenses and only lenses. So all lenses coordinate around a lens manager, creating that behavior instance on demand and placing it at in the global data space under a name socially agreed upon by lenses. During the paint protocol, the lens manager computes intersections and invokes lenses. Only the manager directly registers interest in the document.
See SystemEvents.java, SemanticEvent.java, and event routing in Document.java.
When first learning how to translate abstract protocols into Java code, it's helpful to examine the source code of existing behaviors. In fact, it might be helpful to inherit from or otherwise reuse the code of an existing behavior. For example, a simple version of text redaction (which blacks out text, rendering it unreadable) was built in a couple hours by adapting the hyperlink code: hyperlinks ask for a destination URL, make the associated text blue and underlined, and show the detination when the mouse enters the affected area; redaction worked by asking for a redaction reason, made foreground and background black, and showed the reason when the cursor entered the region.
We have commented a few behaviors more heavily than usual to illustrate the abstract protocol-to-Java-method translation:
The source code for devel.MyBehavior.java is a good template.
Don't modify any class in the multivalent package! If you do, the hacks will need to be redone when the next version comes out. Ideally, you can do everything the existing general mechanisms — more than you'd think, if you'd think. I'm sure that there are uses that cannot presently be accomodated, but rather than tweaking the core classes for a special case, let me know and I'll see about making that part of the code more general.
Make sure the Java compiler can reference the Multivalent classes by putting the JAR file in your Java CLASSPATH or adding to your IDE's library path.
I break code and comment lines at logical places. Moreover, I like to see many functional lines of code in a screenful. That helps the examination of subtle interrelationships in an algorithm, without wasting time and losing context constantly scrolling back and forth. (I use a small font too.) This means that sometimes multiple statements can be found on line and that sometimes comment text goes past 80 characters on a line. The style of code formatting that puts every open brace and statement on its on its own line, with lots of whitespace, just increases LOC, which is a totally bogus productivity metric.
Another thing you'll discover is that some classes have a number of lines
of historical, obsolete code commented out. For all these cases, if
you simply use a development environment that doesn't wrap lines and that
color codes comments, which virtually all do, then you should have no trouble
ignoring it. When the documentation matures, most people won't read
the source much if at all, only about as much as java.*
classes.
As discussed in "Hubs", you don't link in your behavior with the rest of the system as one does with C libraries; in fact, the core system itself doesn't explicitly reference your or any other behavior. Instead, some hub document refers to it, and the system dynamically loads the referenced behavior.
Create a directory sys/hub in the same directory where your compiled classes are. Often you'll be augmenting another hub, such as one from the built-in system, in which case the file you create in that new local directory will be of the same name.
To distribute your behavior(s) and supporting classes and other files, make a Java archive, or JAR. Instructions can be found in Sun's Java documentation under the jar tool.
Supporting data can be stored anywhere in the JAR, but the sys directory is reserved by the system. In sys directory, Preferences.txt.
sys/ Preferences.txt hub/ Plucker.hub stylesheet/ Plucker.cssThe user just needs to copy this JAR into the same directory as the core browser, Multivalent.jar, which automatically detects it, its classes, sys/Preferences.txt, hubs, stylesheets, and so on.
I use this template when writing documentation.
Assertions are used for argument checking in public methods,
rather than exceptions as done in Sun's Java libraries.
Run with assertions on (java -ea -jar Multivalent.jar
).
The live document tree display [screen dump] has proven extremely useful in quickly locating errors in the central data structure. It displays the entire document tree (leaves optional), in a scrolling window, with children nested under their parents. Note that this corresponds to the current runtime tree, which is corrected from possibly buggy input, and is the result of possibly several behaviors (for example, one that builds the basic tree, another that adds annotations, others that add hooks for special interactive behavior). Each line corresponding to a node, with name, size (number of children for internal nodes, "addressable components" for leaves), child number with respect to its parent, (short) class name, bounding box and baseline, valid bit, non-zero margins/border/padding, attributes, and observers (which can be any behavior, but most often spans). Color coding shows errors (such as lack of bounding box nesting) and unusual conditions (such as a span transition that crosses a structural boundary). Alt-button on a node displays error messages and active behaviors (structural and span) at that point. Since document trees can grow large, one can make a selection in the area of interest before invoking Debug/Show Doc Tree, and the display will scroll to the selected node and display it with a gray background.
In addition to the live document tree display,
turning on the Debug Mode switch under the Help menu creates a new
Debug menu with numerous data structure display features.
Validate Document Tree traverses the document tree and
invokes the node-specific validate function,
which can be specialized by node subclasses. The
Always Validate Document switch invokes this check after every
document load. Debug Mode also creates an ABORT button, which does an
immediate System.exit(1)
, without saving history and annotation data.
General debugging tips: