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