CompUnit Tutorial

advertisement
Version 5
11/12/2009 11:30 AM
1. CompUnit 2.0 Tutorial #1 Unix-style components
1.1.
Preliminaries
This tutorial assumes you are familiar with the following:


1.2.
Java 1.5 (Note that CompUnit 2.0 depends upon generics)
CompUnit introduction.
Instructions
Be sure that you complete at least the first part of the tutorial as described in Section 2; if you are interested
in the final resolution of the ideas, then please go ahead and complete the tutorial in Section 3.
You must retrieve the following projects from the following SourceForge location:
https://compunit.svn.sourceforge.net/svnroot/compunit:

CompUnit
https://sourceforge.wpi.edu/svn/repos/classcode509

CompUnitEnvironment
Note that once you have retrieved the code from CompUnitEnvironment, you must disconnect it from the
repository. To do this, right-click on it and select “Team  Disconnect…” Have no fear. You will never
write code within this project; rather, it is a CompUnit workspace (similar to an Eclipse workspace) in
which Foundation can execute.
The code is retrieved using SVN within Eclipse. If you have trouble using SVN, contact the professor.
Once downloaded, you will likely need to configure the JDK used within CompUnit so it can properly
compile. To do this, view the properties for the CompUnit project and open the “Build Path” information,
where you can configure it to use one of your installed JDK’s (at least 1.6).
1
Version 5
11/12/2009 11:30 AM
2. Part I :: Pipe and Filter
Task: Write a CompUnit application composed of a small set of blocks that perform
simple Unix-style processing; show how they can be composed in interesting ways.
Initial target: cat, grep, tee, wc, tr, head, tail, sort, and uniq,
So this seems to be a big challenge! First, we need to set the interfaces to enable components to
communicate with each other. Naturally, we want to follow the Unix standard as much as possible. Launch
Eclipse and create a new Java Project called “Tutorial”. To make this a CompUnit-enabled project, you will
need to:

Make sure that the Java compiler has its Compiler Compliance Level is set to at least JDK 1.5;
edit the properties “Properties  Java Compiler” for the Project.
Figure 1: Setting Java compliance level for new project

Alter the Java build path to include project CompUnit (for core interfaces); edit the properties
“Properties  Java Build Path” to add the “CompUnit” project to the build path.
Create a package ‘unix’ in your new project. This will be the location where all development will take
place. Our first goal is to develop the interface that will be used to connect components. Since we follow
Unix, let’s assume components communicate with each other using standard string-based output. We also
need a way to signal the end of input. This leads us to our first decision:
package unix.interfaces;
public interface IOutput {
/** Output a string on a line by itself. */
void output (String s);
/** Terminate output (an effective End-of-File). */
void terminate();
}
Figure 2: IOutput Description
You should create this interface in Eclipse by right-clicking on the ‘unix’ package and requesting new 
interface and fill in the form as shown in Figure 3.
2
Version 5
11/12/2009 11:30 AM
Figure 3: Create Java Interface unix.interfaces.IOutput
Modify the interface contents for IOutput as described in Figure 2. Now we are ready to write our first
two components and we will start with cat (which simply outputs information) and wc (which simply
counts characters, words, or line numbers).
2.1.
Creating cat CompUnit Component
When creating a CompUnit component, the simplest decision is to decide to create a package and place all
implementation code for that component in the package; this is not a firm requirement, but it makes the
development and debugging of components a lot easier. Create a class Cat in the package unix.cat and
make sure that the class implements org.compunit.interfaces.IComponent (we will no longer
show screenshots from Eclipse showing how to add classes and interfaces).
Now looking at the automatically generated template from Eclipse, you will need to implement the
following methods: activate, deactivate, and connect. The purpose of cat is to retrieve input
from the user (presumably using keyboard input) and output it to the next component. Thus cat will require
the use of IOutput to be able to pass its information on to a component that will come after it. More than
that, cat will need to maintain a reference to that other component so that it can properly invoke the
output() method call. This reference will be provided by Foundation as part of the lifecycle process
where components are connected to each other as specified in a Component Application (CA) file. We are
now ready to begin. Modify Cat as follows:
package unix.cat;
import org.compunit.Require;
import org.compunit.interfaces.IComponent;
import org.compunit.interfaces.IResourceRetriever;
import unix.interfaces.IOutput;
@Require({IOutput.class})
public class Cat implements IComponent {
/** The component to whom output is to be directed. */
IOutput next;
/** Default constructor required by CompUnit */
public Cat () {
}
@Override
public boolean activate(IResourceRetriever handler) throws Exception {
return false;
}
3
Version 5
11/12/2009 11:30 AM
@Override
public boolean connect(IComponent unit, String iName) throws Exception {
if (iName.equals (IOutput.class.getName())) {
next = (IOutput) unit;
}
return false;
}
@Override
public void deactivate() throws Exception { }
}
Figure 4: Cat Implementation
Let’s break this down. We create an instance attribute (next) that stores a reference to the object to whom
cat will direct output. We annotate Cat with an @Require({IOutput.class}) clause to state that Cat can
only function if it has been connected to a component that provides the IOutput interface. Finally, we
provide a connect method to store a reference to the next component whenever Foundation connects cat
to another component.
Since cat is intended to be placed in a linear chain of invocations (as with Unix) we observe that cat must
also provide IOutput so it can be chained together. To make this possible, we must first alter the
definition of Cat to insure that the cat component provides IOutput. We do this by making the following
changes to Cat.
import org.compunit.Provide;
@Provide({IOutput.class})
public class Cat implements IComponent, IOutput {
/** These methods are implemented for IOutput. */
public void output (String s) { }
public void terminate() { }
}
Figure 5: Cat Extension
We note in passing a point that must be made clear; as a CompUnit component, cat does not provide the
interface IComponent even though the class Cat implements IComponent. The reason is that no other
component can connect to Cat by means of IComponent. Nor is it possible for a component to require
the interface IComponent on another component. We do not need to modify connect because that
method is only invoked on the target component during a connect request by Foundation for required
interfaces. Note that the cat component will not work properly as it has been written so far since its
activate method returns false.
We are now ready to proceed with the important implementations. Component cat is responsible for
reading input from the user by keyboard and delivering that input to the next component by means of the
output method. When the keyboard input is done, then the terminate method is to be invoked.
Warning: This tutorial is educational in nature, so some of the decisions that are about to be made may not
be optimal and may even have serious shortcomings. Please continue (even if you have misgivings) until
the end of the tutorial.
The final step in the lifecycle for a component is its activation. So let’s modify the activate method
within Cat to process keyboard input and deliver it to output as it comes in.
/** Delay Activate until all input is processed. */
public boolean activate() throws Exception {
Scanner sc = new Scanner (System.in);
while (sc.hasNext()) {
String s = sc.nextLine();
// make sure we output on a single line by itself
4
Version 5
11/12/2009 11:30 AM
if (next != null) { next.output(s + "\n"); }
}
// once done, terminate
if (next != null) { next.terminate(); }
return true;
// successful activation.
}
Figure 6: Revised activate() method for cat component
The activate method has been subverted to be responsible for reading all input and passing it along to
the next component until there is no more input, at which time cat calls the terminate method on the next
component. This solution is not a viable long term solution because we are abusing the use of activate,
as defined by the CompUnit Component model.
2.2.
Creating wc CompUnit Component
The wc component is programmed to output the number of lines of input it receives. This component is
noticeably simpler than cat. First, create the unix.wc package and create the class unix.wc.Wc within
this package. Make sure that you add IComponent and IOutput as interfaces implemented by the class,
as shown below:
package unix.wc;
import org.compunit.Provide;
import org.compunit.interfaces.IComponent;
import org.compunit.interfaces.IResourceRetriever;
import unix.interfaces.IOutput;
@Provide({IOutput.class})
public class Wc implements IComponent, IOutput {
/** Number of lines seen so far. */
int numLines = 0;
/** Required by CompUnit. */
public Wc () { }
public boolean activate(IResourceRetriever handler) throws Exception {
return true;
}
public boolean connect(IComponent unit, String interfaceName)
throws Exception {
return false;
}
public void deactivate() throws Exception {}
/** Receive 'output' from prior component and count. */
public void output(String s) {
int idx = 0;
while ((idx = s.indexOf('\n', idx)) != -1) {
numLines++;
}
}
/** Output num lines seen once terminated. */
public void terminate() {
System.out.println (numLines);
}
}
Figure 7: Initial wc component implementation
5
Version 5
11/12/2009 11:30 AM
The wc component provides IOutput which means it must have an implementation of output and
terminate, which simply count the number of carriage returns and, upon termination, outputs the value
to the screen. The astute reader may notice the defect in the output method; for now, we continue.
2.3.
Packaging wc CompUnit Component
To package the wc component, we must launch FoundationGUI and execute the packager.ca
application which is found within the system folder. To Launch FoundationGUI, you must create a run
configuration that:
 Runs in the CompUnit project
 Has org.compunit.FoundationGUI as the Main class
 Has program arguments -f "${workspace_loc:CompUnitEnvironment}" -w "${workspace_loc}"
 Runs in Working directory ${workspace_loc:CompUnitEnvironment}
This run configuration must be created just a single time. Within Eclipse, select “Run  Run
Configurations…”. Then create the configuration as shown in the following screen shots:
Figure 8: Sample Run Configuration for FoundationGUI
Figure 9: Set Arguments for Run Configuration
6
Version 5
11/12/2009 11:30 AM
Our first task is to create a new Component Definition (CD) file for cat and wc components. We take
advantage of the existing packager component to complete this task. Once you have created the required
Run configuration, launch the FoundationGUI to see the GUI as shown in Figure 10.
Figure 10: Sample FoundationGUI
Double-click on the “packager.ca” file within the system folder to launch the CompUnit packager
component, whose initial graphical interface is shown in Figure 11. Your first step is to create a new CD
file. Select “File  New” from the Packager menubar. Note that the combination box at the top of the
window shows “* NEW CD FILE ”.
Figure 11: Packager GUI
We need to create two CD files. Let’s first create the wc component’s CD file. If you select the “General”
tab, you will be able to enter information about the component, as shown in Figure 12 .
Figure 12: General information about a component
Some of the fields for a component are automatically generated (such as FileSize) and you are not able to
manually update the information; other attributes are mutable. To edit a value, click in the “Value” cell for
7
Version 5
11/12/2009 11:30 AM
the row whose attribute you wish to change. For example, you should at least change the “Author” and
“Email” attributes to reflect the developer who created the component.
Now left-click on the “Provided” icon in the left panel to review the provided information about the wc
component. No interfaces are currently shown since this is a new component definition; to specify that the
component provides an interface, right click the “Provided” icon and a pop-up menu appears asking to
“Add provided interface”. When you select this option, a new “untitled’ interface appears in the listing as
shown in Figure 13. To change the name of the provided interface, double-click on the “untitled” word and
change it to be unix.interfaces.IOutput. Once you have changed the “untitled” interface to this
new interface name, the only way to change it in the future is to remove it (right click on the interface name
and select “Remove provided interface”) and reinsert it as before.
Figure 13: Adding Provided Interface
The wc component only provides this one interface. We now must select the classes which implement the
wc component. Select the “Classes” icon in the left panel to review that there are (as of yet) no classes
selected for this component. Right-click on the “Classes” icon to choose to add either an individual Class
file or an entire directory (which represents all classes within a Java package). For this simple example, we
need only add a single class, so right-click on “Classes” and select “Add Class file…”. A standard file
chooser dialog will appear, and you should browse to select the appropriate “.class” file of the Java class
whose implementation you wish to add to the component. Note that you must browse to the compiled file,
not the Java source file. When the dialog box appears, you will be in the “CompUnitEnvironment”
directory, so go up one directory to the “Tutorial” project. You will need to locate the class file in
Tutorial/bin/unix/wc/Wc.class.
Figure 14: Adding class to the wc component
Figure 14 shows the resulting information. Note how the file name is automatically converted into a
relative path name using the “$workspace” variable as the base while the fully qualified name of the
component appears as it should.
8
Version 5
11/12/2009 11:30 AM
Because the wc component depends upon unix.interfaces.IOutput, we must take extra care to
package this interface description with the component implementation. This point was mentioned in the
CompUnit white paper on the technology. It is imperative that interfaces be immutable once they are
published, in which case it doesn’t matter how many versions are floating around. By providing the
interface specification with the component, we make it possible for the component to be considered as a
stand alone component. Thus you must, once again, select “Add Class file…” and browse to the location of
IOutput.class on disk so it can be added to the wc component as well.
When adding classes, you must take care to identify the primary class which “anchors” the component
implementation. This is the class which implements IComponent and acts as a façade for the other
classes within the component. To designate a primary class from among the classes in the list, right-click
your mouse on the actual class name in the table on the right (i.e., unix.wc.Wc) and select menu option
“Designate Primary.”
You are now ready to build the installation file for your first wc component. On the application toolbar is a
large button labeled “Build” which you can now select. If this is your first time saving this CD file (which
it is) a file chooser dialog will appear asking you to select a directory into which to save the installed files.
The directory can be one of your choosing, but we recommend that you browse to the source directory
where the Wc class is found. This directory is “Tutorial/src/unix/wc”. By placing the output in
this visible location, you can always retrieve it easily later.
When you go back to Eclipse, right click on the Tutorial project and select “refresh”. You will see two
new/modified files under the wc package: (a) The Component Definition (CD) file unix.wc.Wc.cd; (b)
installunix.wc.Wc.jar. This file is a single JAR file used to properly install this component; (c)
code.jar file, which contains the actual implementation of the component; (d) interfaces.jar, which contains
the interfaces that are either provided or required by this component; and (e) unix.wc.Wc.html, an
HTML file that can be used to publish your component via the internet using a downloader component
provided by CompUnit.
The generated CD file appears in Figure 15.
<?xml version="1.0" encoding="UTF-8"?>
<Component type="unix.wc.Wc" version="1.0" installer="$workspace/Tutorial/src/unix/wc">
<PropertyList>
<Author>George Heineman</Author>
<ComponentURL></ComponentURL>
<DigitalFingerPrint>0a09f37687059199d35023f1d9482bdf</DigitalFingerPrint>
<Email>heineman@cs.wpi.edu</Email>
<FileSize>1223</FileSize>
<LicenseRequired></LicenseRequired>
<ReleaseDate>200811082150</ReleaseDate>
<Threaded></Threaded>
<VendorName>Heineman Software, Inc.</VendorName>
<ProvidedInterface>unix.interfaces.IOutput</ProvidedInterface>
</PropertyList>
<Contents>
<Entry sourceFile="$workspace/Tutorial/bin/unix/wc/Wc.class"
qualifiedName="unix.wc.Wc" />
<Entry sourceFile="$workspace/Tutorial/bin/unix/interfaces/IOutput.class"
qualifiedName="unix.interfaces.IOutput" />
</Contents>
</Component>
Figure 15: Generated WC CD file
If you inspect the generated output, you will see that several entries for the Component Definition file have
been automatically set. These include (a) ReleaseDate (which is of the form YYYYMMDDHHMM),
Digital Fingerprint (an md5-encoding of the JAR file to ensure that the component deployment file is not
maliciously updated); and (c) FileSize (which reflects the size of the component deployment Jar file).
9
Version 5
2.4.
11/12/2009 11:30 AM
Packaging cat CompUnit Component
Repeat the above procedure for the cat component but:




Add the unix.cat.Cat class to a new component definition
Add unix.interfaces.IOutput to be both a Provided and Required interface
Add the unix.interfaces.IOutput class to the component definition
Designate the Cat class to be the primary class for the cat component
Build the component and save its files to the unix.cat directory.
2.5.
Installing wc CompUnit Component
To install a CompUnit component you need to execute the installer. Within FoundationGUI under system,
double-click on the installer.caf file. You will see a window for installer as follows:
Figure 16: Installing CompUnit components
Make sure that “Install from Jar” radio button is selected and click on “Browse…”. Locate the
installunix.wc.Wc.jar file located under the Tutorial/src/unix/wc directory and click on
“Open”. You will see in the “Version Information” section of the installer GUI four lines saying:
The installation file contains:
Unix.wc.Wc with version 1.0
Existing Versions include:
{}
The above statements show that this component is not installed in the CompUnitEnvironment workspace.
Click on the “Install” button to install.
2.6.
Installing cat CompUnit Component
Now repeat the procedure in above section for cat.
installunix.cat.Cat.jar file and install the component.
2.7.
Browse
and
locate
the
file
Write the CA Application
There is a CompUnit tool that has not yet been discussed that aids developers in writing CA files. It is
known as CAFÉ, the CA file editor. To execute this component, double-click the cafe.ca entry in the
system area in FoundationGUI. You will be presented with the GUI shown in Figure 17.
10
Version 5
11/12/2009 11:30 AM
Figure 17: CAFE editor main screen
Expand the unix folder and you will see two sub-folders cat¸and wc. Expand each of these one more level.
We will be constructing an application by connecting components. The GUI interface provided by CAFÉ
looks nice, but its control structure is rather non-intuitive. Once you see “Cat” and “Wc”, double click on
the “Cat” label next to the small circle icon in the left-hand panel; do the same for “Wc”. You will see the
following two icons appear in the right-side panel (though they will be jammed into the upper left corner):
Figure 18: Component Icons within CAFÉ
The image in Figure 18 identifies a component cat with a required interface (shown on the right) and a
provided interface (shown on the left). You can move this component icon in the canvas by dragging it with
the left mouse button. Note that wc only has a provided interface. If you pass the mouse over the interfaces
circle or diamond, you will see the text unix.interfaces.IOutput hover. Your job is to properly
connect component cat so its required interface is properly provided by wc. Do this by pressing and
holding the mouse button on the required diamond on the IOutput interface required by cat. Note that the
other provided interfaces immediately are colored red to show the user the possible match. Drag to the wc’s
provided interface and a connection is made. To complete the process, choose File  Save and save the
application to a file unix.ca that you will place in the CompUnitEnvironment/
applications/tutorials directory that stores all applications to be executed.
Go back to Eclipse and select the CompUnitEnvironment project and select “Refresh”. You will see
the unix.ca file appear there; go to the FoundationGUI window (which should still be executing) and
right-click on the applications list in the left panel and a pop-up menu will appear containing the menu item
“refresh application list”; select this menu item and the new unix.ca file is added to the CA applications
list on the left side.
The CA file might look like the document shown in Figure 19.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<APPLICATION SCHEMA="1.0" name="unix">
<COMPONENTS>
<COMPONENT class="unix.cat.Cat" file="unix.cat.Cat.jar" name="Cat">
<PROPERTIES name="CAFE">
<PROPERTY name="x" value="111"/>
<PROPERTY name="y" value="139"/>
</PROPERTIES>
11
Version 5
11/12/2009 11:30 AM
</COMPONENT>
<COMPONENT class="unix.wc.Wc" file="unix.wc.Wc.jar" name="Wc">
<PROPERTIES name="CAFE">
<PROPERTY name="x" value="226"/>
<PROPERTY name="y" value="93"/>
</PROPERTIES>
</COMPONENT>
</COMPONENTS>
<CONNECTIONS>
<CONNECT destination="unix.interfaces.IOutput" source="Cat" target="Wc"/>
</CONNECTIONS>
<ACTIVATION>
<ORDER name="Cat"/>
<ORDER name="Wc"/>
</ACTIVATION>
</APPLICATION>
Figure 19: Sample CA file
Double-click on unix.caf in FoundationGUI to launch the application.
Whoops. The following error appears:
Unable to connect "Cat" (class:unix.cat.Cat) to "Wc" (class:unix.wc.Wc)
using interface "unix.interfaces.IOutput".
What went wrong? Since Foundation claims to be unable to connect the two components together, we first
check the cat component to make sure it requires IOutput (it does). We then make sure it has a block of
code in connect that processes the connect request (it does). HOWEVER, note that in the code example, the
connect method still returns false. AHA! We forgot to include a ‘return true’ statement inside the if
block. This is an important lesson. Don’t do this again! Modify the connect method in cat as follows:
/** Manage connections by remembering references. */
public boolean connect(IComponent unit, String iName) throws Exception {
if (iName.equals (IOutput.class.getName())) {
next = (IOutput) unit;
return true;
}
// never heard of that interface.
return false;
}
Now, the true power of packager, installer, and Foundation come to play. You are likely still running the
packager (if not just run it again and choose File  Open to load up the CD file for the cat component);
from the pull-down menu at the top, select unix.cat.Cat and click on the “Build” button which will
repackage the component in the location in which it was previously saved. We can simply repackage
because Eclipse naturally recompiles all Java files as they are modified. Now we need to reinstall. Bring up
the Installer GUI window which is still running (if you closed it, run installer.ca again). Browse to
installunix.cat.Cat.jar and you will now see the information has changed, and reads:
The installation file contains:
Unix.cat.Cat with version 1.0
Existing Versions include:
{ 1.0 }
I leave it to you to decide when you feel it is necessary to upgrade the version of a component. In this case,
we haven’t done it so we are updating a version. To update the version of the component, you need only
click on the “Update” button. It will properly replace the older component version with the new one.
If you wanted to uninstall an older version of a component, select the “Uninstall tab” and click on the
“Refresh component list from CompUnit”. Find unix.cat.Cat, select it, and click on the “uninstall selected
component” button; don’t forget to reinstall this component if you intend to complete this tutorial!
12
Version 5
11/12/2009 11:30 AM
Warning: The uninstall feature doesn’t (yet) let you determine which version of the component is to be
uninstalled. It might only uninstall the last version; it might uninstall all versions. This hasn’t been tested.
Warning: Because of the nature of the Java Virtual Machine, the classes that were loaded earlier (Cat and
Wc) are still in memory, thus you won’t be able to access the newer versions until you shutdown
Foundation entirely. You must do that now. Simply click the close window button on FoundationGUI and
confirm. All existing applications are deactivated and shutdown cleanly.
Now, within the restarted FoundationGUI, double-click unix.ca to launch the application. Nothing will
happen. What is going on? Well, I have a hunch that cat is not loading because of our implementation.
Namely, that the activate method doesn’t complete until all input has been received by cat. NOTE that
this is a bad decision! But let’s try to work within this context. Go to the Eclipse console and type the
following:
The whisper of wind
Here today, here tomorrow
Always Everywhere
Nothing seems to be happening. You can see that something bad has happened because the FoundationGUI
window is unresponsive, indicative of an infinite loop somewhere in the program. How are we going to
figure this out? First, we need to terminate FoundationGUI (which you could do by closing the window).
You can switch to the Debug perspective in Eclipse (Window  Open Perspective  Debug) and right
click on the process labeled “FoundationGUI” (found in the Debug panel in the upper left window) to
select the menu item “Terminate.”
2.8.
Debugging CompUnit applications
It is imperative that we be able to debug CompUnit applications, otherwise we will never be able to resolve
problems that occur when different components are inconsistent with each other. Now that you have
executed FoundationGUI at least once, you can simply select “FoundationGUI” from the Debug Choice list
(next to the small green bug).
Select unix.ca and click on Execute. In the console window, type the following as “input” and it will
appear in green letters.
The whisper of wind
Here today, here tomorrow
Always Everywhere
Once again, the process will hang. This time switch to the Debug perspective (Window  Open
Perspective  Other …  Debug) and you will see something like the following:
13
Version 5
11/12/2009 11:30 AM
These are the threads that make up the Java program. Right click on “Thread [AWT-EventQueue]
(running)” and select “Suspend” (or press the icon with double-yellow boxes representing a “pause” on the
toolbar). You will now see additional information:
Now the text file will likely show the following information:
To enable source-code debugging of the components you are using, you will need to tell Eclipse where the
source is located. The simplest solution (and recommended!) is to add project Tutorial to the source code
path. Click on the “Edit Source Lookup Path” button, and select “Add…” and click on “Java Project” and
then add a check box next to the Tutorial project. Now the source code will become visible.
It turns out that the program is hanging within the output method of the wc component. Looking at the
output method more closely, the defect is easy to detect (idx will be stuck at the first index position
where ‘\n’ is found). To resolve the problem, we can make use of Eclipse’s capacity to change code “on
the fly” which is a great productivity enhancer. The output method should be visible in a separate panel
on the screen. Modify the output method as follows:
/** Receive 'output' from prior component. */
public void output(String s) {
int idx = -1;
while ((idx = s.indexOf('\n', idx+1)) != -1) {
numLines++;
}
}
Then after making the small changes, save the file (control-s); after a brief pause, Eclipse will recompile
the code and restart the method (the highlighted line in the file will be “int idx = -1;”). Click on the Resume
icon in the header (the green triangle) that looks like a VCR play button.
Now the program will still appear to be frozen, but that is because Eclipse is waiting for us to signal the end
of the input. To do this, click the mouse towards the end of the console window and type “control-z”
(which is the End-of-file signal used by eclipse to terminate the input from System.in). Once you do this
a number should be output; it won’t be the proper value of “3” but we are making progress. Now we need
to repackage the wc component to include these code changes.
2.9.
Repackaging a CompUnit component
The repackaging of a CompUnit component is made possible because of the CD files. Launch the
packager and select “File  Open” and browse to the location of unix.wc.Wc.cd within the unix/wc
14
Version 5
11/12/2009 11:30 AM
directory. All you need to do is click on the “Build” button. Now launch installer by selecting
“installer.ca” from FoundationGUI. Browse to the location of the installunix.wc.Wc.jar file in the
‘wc’ directory. Click on “Update”.
Now, go to FoundationGUI and shut it down. We have to shut it down because the standard Input has been
shut (remember typing control-z?). Relaunch it and select unix.ca to execute. Type the desired input
you wish to enter, then press Control-z. The number of lines of input will be printed out. It looks like we
have succeeded! However, there are two problems. First, only after the output appeared did the right-side of
FoundationGUI show the executing “unix.ca” application. Second, if you launch a new unix.ca you
will see that the output will immediately read “0”. We clearly haven’t shut down our application properly,
because the FoundationGUI shows “unix.ca-XXXX” and “unix.ca-YYYY” under the list of executing
applications. Now we can’t simply have the cat be responsible for shutting down the application, because it
is too low level and cannot possibly be responsible for knowing the status of the application; however, it
looks as if we simply need to close down our access to the keyboard input by calling the close method on
the Scanner object. However, it is worse than that, I am afraid. Since we are using “Control-Z” as the
termination for the input we have closed the standard input. Once you have run unix.ca once and
terminated input, no other program can be run that reads from standard input! Obviously this is a major
design defect and there is no good solution except to say that we should avoid standard input!
2.10. Manual Execution of Foundation
Since we (apparently) can only run a single execution at a time, then we must execute the CA file within a
stand-alone Foundation container, without the enclosing FoundationGUI that enables multiple programs to
be run at the same time. To launch the manual Foundation, select the “CompUnitEnvironment” project,
then select Run  “Run …”. Select “Java Application” and click on “New launch configuration”. A wizard
will appear for you to enter in the relevant information. Change the name of the configuration to
“Foundation”. Within the Main tab, enter “CompUnit” as the Project and “org.compunit.Foundation” as the
Main class. Select the Arguments tab and enter the following information:
-f
"${workspace_loc:CompUnitEnvironment}"
-w
"${workspace_loc}"
"${workspace_loc:CompUnitEnvironment}\applications\tutorial\unix.ca"
-ca
Finally, enter "${workspace_loc:CompUnitEnvironment}" in the Working Directory text field
(you may have to select the “Other” working directory radio button). Once done, click on “Run” button.
Now you can safely enter in the input, press “Control-Z” when done, and see the results. This completes the
first part of this tutorial.
3. Part II :: Fixing weaknesses of the Pipe & Filter
Reviewing the solution so far, there are three distinct weaknesses:
1.
The activate method of the cat component is just poorly designed. It should return true or
false immediately to Foundation, which is trying to assemble an application from a set of
components. We must move this code out of activate, somehow. Note that the reason cat is
poorly designed is the same reason that the following constructor would be poorly designed:
public class Point {
/** x,y values. */
int x;
int y;
public Point () {
Scanner sc = new Scanner (System.in);
System.out.println ("X value:");
this.x = sc.nextInt();
System.out.println ("Y value:");
this.y = sc.nextInt();
}
15
Version 5
11/12/2009 11:30 AM
}
The constructor for a class must do as little as possible; and it certainly should not be involved in
I/O operations.
Once this code is moved out of the activate method, however, we must provide for some way to
determine the entry point into the application. For example, a C program always has a main
function; should the same be true of a CompUnit application?
2.
The wc component is hard-wired to have its output method write to System.out.println;
this means you can’t combine another Unix-style component after wc.
3.
In Unix, when a command “cat file.c | grep this | head -10” is executed, the individual filters all
execute in their own process, thus they can each make progress. In the current CompUnit version
of Unix-style components, there are no multiple threaded programs, and there may likely be
bottlenecks in the execution.
4.
To chain Unix-style components together, the output of one component must become the input of
the next component; resolving this problem will make some progress towards the problems
identified in point 1 above.
We can fix problems 1 & 2 by observing that we need to have an abstraction to represent standard input
and one to represent standard output; if we developed components to represent these abstractions, then each
unix-style component would strictly be responsible for its input and output as expected. We rely on a
CompUnit feature to designate one of the components in a CA file as being the Main Component1, to which
any command-line arguments are sent and which becomes the entry point into the application. To address
problem 3, we need to develop a standard means by which Unix-style components can execute in their own
thread. To address problem 4, we must clean up the way input is delivered into a component
3.1.
Resolution
What is needed is a standard entity representing standard input (the stdin component) and one for standard
output (the stdout component). Stdin is responsible for retrieving input from System.in and delivering
it to the next component by means of the IOutput interface; in this way, it is very much like the cat
component we have already worked on. Similarly, stdout simply takes its input and prints it to
System.out.
Because we know that Unix enables input to be extracted from a file (by means of the ‘<’ command) and
piped out to a file (by means of the ‘>’ command) we want to enable stdin and stdout to be customizable
to either retrieve input from System.in (or post output to System.out) or process a file.
3.1.1. Unix-style component executing in its own thread
To support numerous Unix-style components, we designed a common abstract class that will form the basis
for any threaded Unix-style component. This class, unix.core.ThreadedComponent, forms the
basis for any Unix-style component (other than stdin and stdout). The essence of this class is to provide
synchronized access that receives input from the prior component in the chain, and makes output available
to the next component in the chain. It is clear that ThreadedComponent must both implement
IOutput (to receive input) and require the next component in the chain to provide IOutput.
Note that ThreadedComponent assumes there is only one ‘downstream’ component to whom it
communicates with, and that communication is governed by the IOutput interface. To provide the proper
infrastructure, we create a thread for each ThreadedComponent instance. This thread uses the
1
For now, the café editor is not able to designate such a component; this must be done by hand.
16
Version 5
11/12/2009 11:30 AM
capability provided by the java.util.concurrent.LinkedBlockingQueue class to synchronize
access between different threads. The given abstract class is shown below:
package unix.core;
import java.util.concurrent.*;
import org.compunit.interfaces.IComponent;
import org.compunit.interfaces.IResourceRetriever;
import unix.interfaces.IOutput;
/** Forms basis for any threaded CompUnit component.
* Subclasses must implement processOutput () and processTerminate () */
public abstract class ThreadedComponent implements IComponent, IOutput {
protected IOutput next; /** Component that comes next in the chain. */
boolean active;
/** Has this component become active yet? */
Thread thread;
/** Our active thread. */
/** Buffer where input is stored until it is processed. */
private final BlockingQueue<String> queue =
new LinkedBlockingQueue<String>();
/** Expect output to go to next component. */
public boolean connect(IComponent unit, String iName) throws Exception {
if (iName.equals(IOutput.class.getName())) {
next = (IOutput) unit;
return true;
}
return false; // never heard of you
}
/** Determine how to process each line of output. */
public abstract void processOutput(String s);
/** Determine action to take when no more input appears (i.e., EOF). */
public abstract void processTerminate();
/** Activating the component sets thread to go. */
public boolean activate(IResourceRetriever irr) throws Exception {
active = true;
thread = new Thread() {
public void run() {
while (active) { // while active, process from queue.
String s;
try {
s = queue.take();
} catch (InterruptedException e) {
active = false;
break;
}
processOutput(s); // Process output
}
processTerminate(); // Deal with terminate.
}
};
thread.start();
return true;
}
/** Buffer all output that comes into this component. */
public void output(String s) {
try {
queue.put(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/** Disable and terminate thread. */
public void deactivate() throws Exception {
// wait until empty, before passing along the termination.
17
Version 5
11/12/2009 11:30 AM
while (queue.size() > 0) {
Thread.yield();
}
active = false;
if (thread != null) {
thread.interrupt();
// We may be stuck in run()
thread = null;
}
}
/** Respond to terminate request. */
public void terminate() {
try {
deactivate();
} catch (Exception e) {
e.printStackTrace();
}
}
}
This thread for each component will pull Strings from a LinkedBlockingQueue queue and invoke
processOutput(s) for each String retrieved. Note that each String is guaranteed to be terminated
by a carriage return (‘\n’)2. This queue is populated whenever the ThreadedComponent receives a line
of input from the prior component (because it implements IOutput); the output(String s) method
synchronizes access to the queue and inserts the new input at the end of the queue. When the input is
complete (because the terminate method was invoked) then the thread halts 3.
3.1.2. Simplicity of cat component
Now that we have formed the common code for any Unix-style component, the implementations of the
individual components have truly become simple. The following is the full implementation for cat.
package unix.cat;
import org.compunit.*;
import org.compunit.interfaces.IComponent;
import unix.core.ThreadedComponent;
import unix.interfaces.IOutput;
@Require({IOutput.class})
@Provide({IOutput.class})
public class Cat extends ThreadedComponent implements IComponent,IOutput {
/** Process String input arrives from prior component. */
@Override
public void processOutput(String s) {
if (next != null) {
next.output (s);
}
}
/** Process terminate from prior component. */
@Override
public void processTerminate() {
if (next != null) {
next.terminate();
}
}
}
2
Not sure about what happens when there is no carriage return at the end of input.
It is certainly plausible that this interruption leaves the thread in inconsistent state. There needs to be some
means of “draining” all input, processing it, then terminating the thread. As it stands right now, I can
envision some input could be left dangling in the component..
3
18
Version 5
11/12/2009 11:30 AM
All logic for processing raw input is provided by the base class ThreadedComponent. Here, cat simply
passes the output along to the next component (which is visible through the superclass by means of the
“next” class attribute). When the input terminates, cat ensures the next component is contacted.
To package the cat component, we will have to do some additional steps, but only because of the way in
which we extended ThreadedComponent. Specifically, packager must be told of the existence of the
superclass files so it can properly include them in the deployment package (otherwise, they won’t be found
as needed during instantiation). Launch the packager.ca from FoundationGUI and open the
unix.cat.Cat.cd description file. Select the Classes icon and choose “Add Directory…” to select the
“core” directory found in Tutorial/bin/unix/core. This option states that all Java class files within
the source directory are to be packaged with the component. Click the “Build” button. Note that two
additional classes have been added to the component package. These are ThreadedComponent.class
and the anonymous Thread class ThreadedComponent$1. Using the directory feature simplifies what
you need to specify when declaring the elements of the component.
Naturally, once packaged, you will need to reinstall the cat component. Launch installer, browse to the file
installunix.cat.Cat.jar and click on “Update” (unless if you have never installed cat before, in
which case you click on Install).
Note: The astute reader will note that ThreadedComponent has no @Provide and @Require annotations.
This is not an omission. Annotations are not inherited by subclasses, so they must be provided anew by the
Cat and Wc subclasses.
3.1.3. Simplicity of wc component
The following is the full implementation for the modified wc. To expose more of the features of CompUnit,
we decide to create version 1.1 of the wc component. First, modify the code of Wc.java to be:
package unix.wc;
import
import
import
import
import
org.compunit.Provide;
org.compunit.Require;
org.compunit.interfaces.IComponent;
unix.core.ThreadedComponent;
unix.interfaces.IOutput;
@Provide({IOutput.class})
@Require({IOutput.class})
public class Wc extends ThreadedComponent implements IComponent, IOutput {
int numLines = 0;
/** Number of lines seen so far. */
/** Process String input arrives from prior component. */
@Override
public void processOutput(String s) {
int idx = -1;
while ((idx = s.indexOf('\n', idx+1)) != -1) {
numLines++;
}
}
/** Process terminate from prior component. */
@Override
public void processTerminate() {
if (next != null) {
next.output ("" + numLines);
next.terminate();
}
}
}
19
Version 5
11/12/2009 11:30 AM
wc simply processes all output and, upon termination, reports its final results and propagates the
termination on to the next component. To package wc, follow the same steps as you had done with cat,
namely, to add unix/core as a sourceDir to the unix.wc.Wc.cd definition file. Since we want to make
this component version 1.1, click on the Version icon to change the version from 1.0 to 1.1.
Once done, repackage and use the Installer to install the new version of the wc component in
CompUnitEnvironment. When selecting the installation Jar file, you will see that Installer will report:
The installation file contains:
Unix.wc.Wc with version 1.1
Existing Versions include:
{ 1.0 }
Thus when you install, there will be two versions of the component installed in CompUnitEnvironment.
You can see that this is the case within installer by clicking on the “CheckVersion” button to see that both
versions are installed. Under normal operating conditions, applications formed by components will always
choose the most recent version of the component in the CompUnit workspace.
3.1.4. Implementing grep component
The grep component searches through input strings for matches based upon regular expressions. Since
JDK 1.4, the java.lang.String class has provided the capability to match based upon a regular
expression. grep need only load up a regular expression from the properties tag and apply the regular
expression to the input that it sees. The grep component has several customizable features that can be used:
 Expression – The actual Regular Expression to match against input (see java.util.regexp
package for details on the format of the Regular Expressions supported by Java).
 CountOnly – A boolean value equivalent to the Unix “-c” option which outputs only the count of
the lines that matched the regular Expression [Default: false]
 CaseSensitive – a boolean value equivalent to the Unix “-i” option which makes all matches
ignore case [Default: true]
 Negative – a booleam value equivalent to the Unix “-v” option that outputs only if the match fails.
The code for the grep component is nearly identically to cat, and its implementation is shown below:
package unix.grep;
import
import
import
import
java.util.Properties;
java.util.regex.*;
org.compunit.*;
org.compunit.interfaces.ICustomizableComponent;
import unix.core.ThreadedComponent;
import unix.interfaces.IOutput;
/**
* Process Input based upon regular expressions.
*
CountOnly
True/False
(-c option)
*
CaseSensitive
True/False
(-i option)
*
Negative
True/False
(-v option)
*
Expression
e
(-e option)
*/
@Provide({IOutput.class})
@Require({IOutput.class})
@Property({Grep.propCountOnly,
Grep.propNegative,
Grep.propCaseSensitive,
Grep.propExpression})
public
class
Grep
extends
ThreadedComponent
ICustomizableComponent {
/* Define properties. */
public static final String propCountOnly = "CountOnly";
public static final String propNegative = "Negative";
20
implements
Version 5
11/12/2009 11:30 AM
public static final String propCaseSensitive = "CaseSensitive";
public static final String propExpression = "Expression";
/** Customizations for grep (with defaults). */
private Boolean countOnly = false;
private Boolean caseSensitive = true;
private Boolean negative = false;
/** Expression to search. */
Pattern expression;
/** Count if countOnly used. */
private long count = 0;
/** Customize from metadata stored with CA file. */
public void customize(Properties props) throws Exception {
String s = props.getProperty(propCountOnly);
if (s != null) { countOnly = Boolean.valueOf(s); }
s = props.getProperty(propNegative);
if (s != null) { negative = Boolean.valueOf(s); }
s = props.getProperty(propCaseSensitive);
if (s != null) { caseSensitive = Boolean.valueOf(s); }
s = props.getProperty(propExpression);
if (s != null) {
if (caseSensitive) {
expression = Pattern.compile(s);
} else {
expression = Pattern.compile(s, Pattern.CASE_INSENSITIVE);
}
}
}
@Override
public void processOutput(String s) {
if (next == null) return;
// if we don't match, leave now...
Matcher m = expression.matcher(s);
// deal with NEGATIVE options.
if (negative) {
if (m.find()) return;
} else {
if (!m.find()) return;
}
// if we get here, we match regular expression.
// if tally only, do so. Otherwise, output.
if (countOnly) {
count++;
return;
}
if (next != null) {
next.output (s);
}
}
@Override
public void processTerminate() {
// if only tallying, then output here.
if (countOnly) {
if (next != null) {
next.output("" + count);
}
}
if (next != null) {
next.terminate();
21
Version 5
11/12/2009 11:30 AM
}
}
}
Launch packager and create a new CD file. Add the Grep.class file to the component and designate it
as the primary class. Make sure to also add the unix.core directory. This component both provides and
requires unix.interfaces.IOutput so make sure you add this interface to both the provided and
required interfaces.
The grep component has four customizable properties. For this reason, the Grep class must implement
ICustomizableComponent. You should add the four properties to the component definition by
clicking on the “Properties” icon in packager and adding the following properties by name:




CountOnly
Negative
CaseSensitive
Expression
Build the installation files within the Tutorial/src/unix/grep directory.
3.1.5. Create Stdin component
The stdin component processes input from the keyboard as well as from a file (this is analogous to the
Unix ability to say “head -5 < someFile” which redirects the standard input to retrieve characters from the
given file ‘someFile’). The stdin component is different from the ThreadedComponent subclasses we
have been working with; specifically, it has no provided interfaces since no one invokes methods on it. It
would have been awkward (and a nightmare!) to maintain if we had tried to shoe-horn stdin to be a
subclass of ThreadedComponent. So we create it anew.
Stdin processes input line-by-line, and forwards them to the next component through the IOutput
interface. Once all input has been processed (determined by end-of-file) then the terminate method is
called. Note that stdin enforces the constraint that each call to output is a single string terminated by a
single carriage return. The code for Stdin is shown below:
package unix.stdin;
import
import
import
import
import
import
import
java.io.File;
java.util.*;
org.compunit.*;
org.compunit.interfaces.IComponent;
org.compunit.interfaces.ICustomizableComponent;
org.compunit.interfaces.IResourceRetriever;
unix.interfaces.IOutput;
/**
* Represents stdin that initiates output. If customized
* with a "File" string, then input is retrieved from there
*/
@Require({IOutput.class})
@Property({Stdin.fileProp})
public class Stdin implements ICustomizableComponent, Runnable {
/** Properties */
public static final String fileProp = "File";
/** File to read input (or null if from System.in). */
File inputFile = null;
/** Default constructor Required by CompUnit */
public Stdin () { }
22
Version 5
11/12/2009 11:30 AM
/** Next component in chain. */
protected IOutput next;
public void customize(Properties props) throws Exception {
String fileName = props.getProperty(Stdin.fileProp);
if (fileName == null) return;
// ok: all input comes from this file.
File f = new File (fileName);
if (!f.exists()) {
throw new java.io.FileNotFoundException (fileName +
": No such file or directory.");
}
inputFile = f;
}
/** Expect output to go to next component. */
public boolean connect(IComponent unit, String iname) throws Exception {
if (iname.equals (IOutput.class.getName())) {
next = (IOutput) unit;
return true;
}
// never heard of you
return false;
}
public boolean activate(IResourceRetriever irr) throws Exception {
new Thread(this).start();
return true;
}
/** We are responsible for processing. */
public void run () {
Scanner sc;
// either load from a File or take from System.in
if (inputFile == null) {
sc = new Scanner(System.in);
} else {
try {
sc = new Scanner (inputFile);
} catch (Exception e) {
e.printStackTrace();
return;
}
}
// process sc until it is drained dry
while (sc.hasNextLine()) {
String s = sc.nextLine();
if (next != null) {
next.output (s + "\n");
}
}
// all done!
if (next != null) {
next.terminate();
}
}
public void deactivate() throws Exception { }
}
The stdin component executes within its own thread of control. Once active, it brings input in from the
Scanner (which either takes input from System.in or the File customized by the component in the CA
file). Each line is sent to the next component via the IOutput interface.
The stdin component has:
23
Version 5



11/12/2009 11:30 AM
Classes: Stdin, IOutput
Properties: File
Required interface: IOutput
Package this component as before, using packager and install it with the installer:
3.1.6. Create Stdout component
The stdout component processes output to be printed to System.out or stored into a file for later use
(this is analogous to the Unix ability to say “ls > outFile” which redirects the standard output store
characters into the given file ‘outFile). Stdout processes input line-by-line, and either outputs using
System.out.print or stores into the pre-selected file. Once all input has been processed (determined
by the invocation of terminate method), then the component is complete. The code is as below:
package unix.stdout;
import
import
import
import
import
import
import
import
java.io.*;
java.util.*;
org.compunit.Provide;
org.compunit.Property;
org.compunit.interfaces.IComponent;
org.compunit.interfaces.ICustomizableComponent;
org.compunit.interfaces.IResourceRetriever;
unix.interfaces.IOutput;
/**
* Represents stdout that posts to System.out or to a file.
*/
@Provide({IOutput.class})
@Property({Stdout.fileProp})
public class Stdout implements ICustomizableComponent, IOutput {
/** Properties */
public static final String fileProp = "File";
/** Output interface (Assume to System.out). */
PrintWriter pw = new PrintWriter (System.out);
/** Output to stdout? or to a file? */
boolean useSystemOut = true;
/** Default constructor is required. */
public Stdout () { }
public void customize(Properties props) throws Exception {
String fileName = props.getProperty(fileProp);
if (fileName != null) {
// ok: all output goes into this file.
File outputFile = new File (fileName);
pw = new PrintWriter (outputFile);
useSystemOut = false;
}
}
/** Print available output. Already has <cr>. */
public void output(String s) {
pw.print (s);
}
/** Close output. */
public void terminate() {
pw.flush();
if (!useSystemOut) { pw.close(); }
}
// Never close System.out
public boolean activate(IResourceRetriever irr) throws Exception {
return true;
}
24
Version 5
11/12/2009 11:30 AM
public void deactivate() throws Exception { }
@Override
public boolean connect(IComponent unit, String interfaceName)
throws Exception {
return false;
}
}
Stdout does not execute in a separate thread. It takes output via the output method call and processes it
accordingly. Use the following unix.stdout.Stdout.cd file when packaging the component.
<?xml version="1.0" encoding="UTF-8"?>
<Component>
<PropertyList>
<Author>George T. Heineman</Author>
<ComponentName>unix.stdout.Stdout</ComponentName>
<ComponentURL>http://compunit.org/unix/stdin/Stdin</ComponentURL>
<DigitalFingerPrint></DigitalFingerPrint>
<Email>root@compunit.org</Email>
<FileSize>3196</FileSize>
<LicenseRequired>false</LicenseRequired>
<ReleaseDate>200711291059</ReleaseDate>
<Threaded>false</Threaded>
<ComponentVersion>1.0</ComponentVersion>
<VendorName>Heineman Software, Inc.</VendorName>
<RequiredInterface>unix.interfaces.IOutput</RequiredInterface>
</PropertyList>
<Contents>
<Entry sourceDir="$workspace/Tutorial/unix/stdout"
qualifiedName="unix.stdout" />
<Entry sourceFile="$workspace/Tutorial/unix/interfaces/IOutput.class"
qualifiedName="unix.interfaces.IOutput" />
</Contents>
</Component>
Once packaged, you should install using installer.
Warning: When CAFÉ executes, it caches the set of installed components; this means that newly installed
components are not available to the CAF editor. If it is currently running, you should exit CAFÉ. .
3.1.7. Create some CompUnit Applications
Now that all components are packaged and installed – stdin, cat, grep, wc, stdout – it is time to prepare
some applications assembled from these components. We will use CAFÉ to produce several applications,
represented here in Unix syntax
3.1.7.1.
App1: cat | wc
Within CAFÉ, expand the left panel to locate the unix folder; within are folders
representing the five components. Double click on Cat and Wc (within their
respective folders). You will also need to add Stdin and Stdout. You will note that
each of the icons in the CAFÉ screen is labeled by name and contains a number (in
this example, in the range 1..4). This number represents the activation order of the
component within the Application. If the activation order matters, then you can use
CAFÉ to adjust the ordering by selecting one of the components (with the left mouse
button) and using the ↑ (up arrow) or ↓ (down arrow) to adjust the activation
ordering. Note that changing an individual activation order will necessarily force
another to change value as well.
Once all components have been added, you might see something like this:
25
Version 5
11/12/2009 11:30 AM
Next, you connect components with each other by dragging connections between interfaces. You can do
this either by clicking on the provides interface (shown as a line with a circle tip) or the requires interface
(shown as a line with a diamond tip). For example, if you press down the left mouse button when the cursor
is on stdin’s required interface, the three provided interfaces of cat, wc, and stdout are highlighted,
showing that either is a valid connection; drag and release to cat’s interface. Similarly, drag from cat’s
required interface to wc’s provided interface. Finally, connect wc to stdout to produce the following
model:
The figure on the above right is semantically equivalent but easier to “parse”. You can create this figure by
CAFÉ’s ability to move interface lines. You can press with the right button over an interface (either a
required or provided) and move its location on the component by dragging it “around” the component; it
will always retain either a horizontal or vertical orientation.
Save the application as App1.ca in the CompUnitEnvironment/applications directory; click on refresh
within the FoundationGUI window and you can immediately execute the application by selecting App1.ca
and click “Execute”. To see the application in action, enter the following into the Eclipse input console
pane:
Tiger, tiger, burning bright
In the forests of the night,
What immortal hand or eye
Could frame thy fearful symmetry?
In what distant deeps or skies
Burnt the fire of thine eyes?
On what wings dare he aspire?
What the hand dare seize the fire?
And what shoulder and what art
Could twist the sinews of thy heart?
And when thy heart began to beat,
What dread hand and what dread feet?
What the hammer? what the chain?
In what furnace was thy brain?
What the anvil? What dread grasp
Dare its deadly terrors clasp?
When the stars threw down their spears,
And water'd heaven with their tears,
Did He smile His work to see?
Did He who made the lamb make thee?
Tiger, tiger, burning bright
In the forests of the night,
What immortal hand or eye
Dare frame thy fearful symmetry?
Once entered, press Control-z to see the number “29” appear, which reflects the number of lines of input
(including the blank lines). Recall that we must now restart Foundation because the Control-z terminates
the System.in input in the console, and no other text-based application can work.
26
Version 5
3.1.7.2.
11/12/2009 11:30 AM
App2: wc
Restart Foundation and run CAFÉ to create application App2.ca
(shown on the right) which is nothing more than a simpler version of
App1. It is simpler, because there is no need to use cat to produce the
same effect. Indeed, the cat component is nothing more than the identify component since the output it
produces is identical to its input.
3.1.7.3.
App3: grep “from” README.txt | wc
Restart Foundation and run CAFÉ to create application
App3.ca (shown on the right) which passes input through
grep and into wc before being output. Now, we need to
explain how stdin is to retrieve its input from the file README.txt and then we must explain how to
customize grep to look for the string “CAF”.
Observer that the stdin component has a defined property “File” which reflects the path name of the file
from which characters are used as input. Recall that Foundation executes within the CompUnit project, so
the file name must either be relative to this directory or be an absolute path name. We can customize stdin
by selecting it in CAFÉ to observer the component properties (shown in the lower left). Click on the “add”
button and then a new row will appear. Click in the left box and enter the string “File”. Click in the right
box and add “README.txt”. To complete the process, click on the “Save” button. Your screen should look
like the following:
Similarly, select the grep component and click on “Add” to enter the property “Expression” with value
“from”. Save the CA file and place it in CompUnitEnvironment/applications. The resulting CA file is
shown below:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<APPLICATION SCHEMA="1.0" name="App3">
<COMPONENTS>
<COMPONENT class="unix.stdout.Stdout"
file="unix.stdout.Stdout.jar" name="Stdout">
<PROPERTIES name="CAFE">
<PROPERTY name="unix.interfaces.IOutput P bounds" value="4,0,23"/>
<PROPERTY name="x" value="293"/>
<PROPERTY name="y" value="183"/>
</PROPERTIES>
</COMPONENT>
<COMPONENT class="unix.stdin.Stdin"
file="unix.stdin.Stdin.jar" name="Stdin">
<PROPERTIES name="meta">
<PROPERTY name="File" value="README.txt"/>
</PROPERTIES>
<PROPERTIES name="CAFE">
<PROPERTY name="unix.interfaces.IOutput R bounds" value="2,50,7"/>
<PROPERTY name="x" value="11"/>
<PROPERTY name="y" value="180"/>
</PROPERTIES>
</COMPONENT>
<COMPONENT class="unix.grep.Grep" file="unix.grep.Grep.jar" name="Grep">
<PROPERTIES name="meta">
<PROPERTY name="Expression" value="from"/>
</PROPERTIES>
<PROPERTIES name="CAFE">
<PROPERTY name="unix.interfaces.IOutput P bounds" value="4,0,23"/>
27
Version 5
11/12/2009 11:30 AM
<PROPERTY name="x" value="105"/>
<PROPERTY name="y" value="182"/>
</PROPERTIES>
</COMPONENT>
<COMPONENT class="unix.wc.Wc" file="unix.wc.Wc.jar" name="Wc">
<PROPERTIES name="CAFE">
<PROPERTY name="unix.interfaces.IOutput P bounds" value="4,0,23"/>
<PROPERTY name="x" value="199"/>
<PROPERTY name="y" value="182"/>
</PROPERTIES>
</COMPONENT>
</COMPONENTS>
<CONNECTIONS>
<CONNECT destination="unix.interfaces.IOutput"
source="Stdin" target="Grep"/>
<CONNECT destination="unix.interfaces.IOutput"
source="Grep" target="Wc"/>
<CONNECT destination="unix.interfaces.IOutput"
source="Wc" target="Stdout"/>
</CONNECTIONS>
<ACTIVATION>
<ORDER name="Stdin"/>
<ORDER name="Grep"/>
<ORDER name="Wc"/>
<ORDER name="Stdout"/>
</ACTIVATION>
</APPLICATION>
Note how each COMPONENT tag can have any number of PROPERTIES tags associated with it. Each
properties has a name. The “meta” tags are used by Foundation when executing the component. The
“CAFÉ” tags are used by CAFÉ when graphically depicting the CA file.
When executing this application, the proper output should be “2”.
3.1.8. Properly shutting down a Unix-style application
When executing the App3.ca and other example CA files, you may have noticed that the “Executing
Applications” (on the right side of the FoundationGUI) shows an increasing set of entries. In other words,
the applications do not clean up after themselves. Foundation is waiting for the application to tell it that it
has completed.
We make the arbitrary choice that once stdout has completed all of its responsibilities, the application is
done. We know when this happens, since stdout provides IOutput which supports the terminate
method invocation upon completion of input. On terminate, then, we must properly shutdown. Essentially,
we need to ensure that stdout requires org.compunit.interfaces.IShutdown, and then we
invoke the shutdown() method on that interface once we are complete. The revised Stdout.java is shown
as follows:
package unix.stdout;
import java.io.*;
import java.util.*;
import
import
import
import
import
import
import
org.compunit.Property;
org.compunit.Provide;
org.compunit.Require;
org.compunit.interfaces.IComponent;
org.compunit.interfaces.ICustomizableComponent;
org.compunit.interfaces.IResourceRetriever;
org.compunit.interfaces.IShutdown;
import unix.interfaces.IOutput;
/**
* Represents stdout that posts to System.out or to a file.
28
Version 5
11/12/2009 11:30 AM
*/
@Provide({IOutput.class})
@Require({IShutdown.class})
@Property({Stdout.fileProp})
public class Stdout implements ICustomizableComponent, IOutput {
/** Properties */
public static final String fileProp = "File";
/** Output interface (Assume to System.out). */
PrintWriter pw = new PrintWriter (System.out);
/** Output to stdout? or to a file? */
boolean useSystemOut = true;
/** Shutdown agent. */
IShutdown ourShutdown = null;
/** Default constructor is required. */
public Stdout () { }
public void customize(Properties props) throws Exception {
String fileName = props.getProperty(fileProp);
if (fileName != null) {
// ok: all output goes into this file.
File outputFile = new File (fileName);
pw = new PrintWriter (outputFile);
useSystemOut = false;
}
}
/** Print available output. Already has <cr>. */
public void output(String s) {
pw.print (s);
}
/** Close output. */
public void terminate() {
pw.flush();
if (!useSystemOut) { pw.close(); }
if (ourShutdown != null) {
ourShutdown.shutdown();
}
}
public boolean activate(IResourceRetriever irr) throws Exception {
return true;
}
public void deactivate() throws Exception { }
@Override
public boolean connect(IComponent unit, String iname) throws Exception {
if (iname.equals(IShutdown.class.getName())) {
ourShutdown = (IShutdown) unit;
return true;
}
return false;
}
}
Now to take advantage of this new functionality, we must repackage and reinstall the stdout component.
First, the change seems significant so we would like to increase the version number for stdout. Launch
packager and open up the CD file for stdout (unix.stdout.Stdout.cd) and click on “Version”;
increment the version to “1.1”. Add “org.compunit.interfaces.IShutdown” to be a required interface for the
component. Reinstall the newly packaged component using installer.
29
Version 5
11/12/2009 11:30 AM
If you still have FoundationGUI executing from the last time App3.a was loaded, you will need to exit,
since the classLoader keeps these classes around 4. Exit from FoundationGUI and relaunch it.
Then you can re-open the App3.ca CA file in CAFÉ and connect stdout to the Shutdown interface exposed
by CompUnit. First you must double click on the “CompUnit” entry in the component list; this will add a
“component” to the CA file that represents the CompUnit interfaces exposed to the component executing
within the Foundation container. Make a connection on the org.component.interfaces.IShutdown
interface from stdout to CompUnit.
The result of this connection (together with the extra code in the stdout component) is that CompUnit will
now properly shutdown the App3 application upon completion. The following are sample applications that
you could develop now using the Unix tutorial components.
UnixExample
UnixExampleWithFileIOGrep
UnixSerialCat
UnixExampleWithFileIO
3.2.
cat | wc -l
grep "regExpr" < README.txt | wc –l > outFile
cat | cat | cat
grep "regExpr" < README.txt > outFile
Restarting Foundation During Development
As an aside, there are moments when you know that you have repackaged and reinstalled code, yet
executing the application within Foundation still seems to use the old code. This happens occasionally
because of the way that the Java Virtual machine uses a ClassLoader to cache previously loaded code. If
you find yourself in this situation, the best thing to do is simply ‘shutdown’ FoundationGUI and restart it;
this will clear the memory cache of any files. A bit of a pain, but I have no good solution and this
workaround actually works.
4
I am looking into solutions that would remedy this problem. For now, you must restart FoundationGUI.
30
Download