2.5. Going Further with the Example

At this point we've covered the basic syntax and semantics of the JavaSpaces API. Other than the more advanced topics of leases, remote events, and transactions, which we will cover in later parts of the book, that's all there is—it is a small and simple API. What we haven't yet covered are the principles, patterns, and common practices of space-based programming, and that topic will consume much of the remainder of the book.

As a first step on the journey, we are going to take our spaceship example a bit further. Our goal in this section is to get across the nature of entries as they flow into and out of spaces—while in a space, entries are passive objects that can be read or taken from the space (but we can't invoke their methods). Once an entry has been read or taken from a space, a process can do whatever it likes with the object. For instance, we've already seen one example of reading an entry (in our getScore method) where the purpose was to obtain the entry's state. In the example just ahead, we will take an object from the space, alter its state, and then return it to the space, where the changes can be seen by all processes in the distributed application. We will also cover a few other topics such as subclassing entries.

To start, we are going to have some fun with our spaceship example by creating a new form of spaceship called BattleSpaceShip—based on the standard SpaceShip—that holds “ammunition.” In the end, we'll have a very simple distributed application that behaves a bit like a game: we'll have one standard spaceship and one battle spaceship in a space, and the battle spaceship will have the ability to “shoot at” the other. When the battle spaceship shoots, its ammunition decreases, its score increases, and the target ship's score decreases. The important point of this game is that to implement this behavior (and make appropriate state changes to the spaceship entries) we have to remove the objects from the space and simulate the firing within a process before returning them to the space. Our intention is to get across the flavor of working with entries, not to suggest strategies for developing real games.

Let's get on with the example.

2.5.1. Subclassing an Entry

We can create a specialized version of our SpaceShip class by subclassing it. Here is a new entry class called BattleSpaceShip that we've created in this way:

public class BattleSpaceShip extends SpaceShip {
  public Integer ammunition;

  public BattleSpaceShip() {
  }

  public BattleSpaceShip(String name, int score, int ammunition)
  {
    super(name, score);
    this.ammunition = new Integer(ammunition);
  }

  public void decreaseAmmo() {
    ammunition = new Integer(ammunition.intValue() - 1);
  }
}

Our subclass contains a new field, ammunition, that holds a count of how many rounds of ammunition the ship has (battleships carry ammunition, while regular spaceships don't). Next we supply the mandatory no-arg constructor and then a new convenience constructor, which takes a ship name, an initial score and an initial count of ammunition. In the constructor, we first call the parent (SpaceShip) class's constructor to set the initial values of name and score for us, and then we set the ammunition field to its initial value.

Our BattleSpaceShip class also has a new method called decreaseAmmo, which takes no arguments and decreases the ship's ammunition count by one. We'll call the decreaseAmmo method to simulate the use of ammunition when the ship is firing its weapon.

Now that we have a subclass of SpaceShip, let's add a few new methods to our application that make use of it.

2.5.2. Adding a Few More Methods

Here we'll add three new methods to our application: getAmmo to determine the ammunition level of a ship, retrieveShip to remove a ship from the space (the opposite of writeShip), and shoot to simulate firing ammunition at another ship.

The getAmmo method takes the name of a ship as an argument and returns the ship's current ammunition count. You'll see that the code follows the same pattern as the getScore method we defined previously, except that it uses a BattleSpaceShip template, instead of a standard SpaceShip template, to find a ship in the space:

public int getAmmo(String name) {
  BattleSpaceShip template = new BattleSpaceShip();
  template.name = name;

  try {
     BattleSpaceShip ship = (BattleSpaceShip)
       space.read(template, null, Long.MAX_VALUE);
     return ship.ammunition.intValue();
  } catch (Exception e) {
     e.printStackTrace();
     return -1;
  }
}

If we pass getAmmo the name of a regular ship (say, “enterprise”), instead of a BattleSpaceShip, the method won't be able to find a battle spaceship by that name, and the read operation will block indefinitely.

Next, we are going to add a method retrieveShip that makes use of the take method. This method does the opposite of writeShip—rather than writing a ship into a space, it takes a ship name and removes a matching ship from the space:

public SpaceShip retrieveShip(String name) {
  SpaceShip template = new SpaceShip();
  template.name = name;

  try {
     return (SpaceShip)
        space.take(template, null, Long.MAX_VALUE);
  } catch (Exception e) {
     e.printStackTrace();
     return null;
  }
}

In retrieveShip, we first create a template to match the requested ship. Next we call the space's take method, supplying our template, a null transaction, and a time-out value of Long.MAX_VALUE, which means to wait indefinitely. When a ship is returned from the take operation, we return it as the result of the method. Note that by using a SpaceShip template, retrieveShip is able to retrieve both standard spaceships and battle spaceships from the space (according to our rule 1 of matching).

Our third and final new method is called shoot, which takes source and target ship names, and simulates the source ship firing at and hitting the target. Here is the code:

public void shoot(String source, String target) {
  // error checking omitted

  BattleSpaceShip sourceShip =
    (BattleSpaceShip)retrieveShip(source);

  if (sourceShip.ammunition.intValue() <= 0) {
      System.out.println(source + " can't fire, it has no ammo");
      writeShip(sourceShip);
      return;
  }
  SpaceShip targetShip = retrieveShip(target);
  System.out.println(source + " firing at " + target);
  sourceShip.decreaseAmmo();
  targetShip.decreaseScore();
  sourceShip.increaseScore();

  writeShip(sourceShip);
  writeShip(targetShip);
}

Let's step through this code. We first try to retrieve the source ship, using the retrieveShip method. Recall that this method was defined to look for a ship using a SpaceShip template, which means it can find matching SpaceShip entries, or BattleSpaceShip entries (or any other entry subclassed from SpaceShip). Once the retrieveShip method returns a ship, as type SpaceShip, we cast the returned entry into a BattleSpaceShip.

There is a bit of danger in this casting, because we could potentially retrieve a ship from the space that's a matching SpaceShip (or of some other subclass of SpaceShip that isn't BattleSpaceShip), and then casting would cause a runtime exception. In this example, we will be controlling the space, so this shouldn't happen. To eliminate the danger in a more open environment, we could do some checking on the returned entry, or we could write a new version of retrieveShip that takes and returns exactly the type of entry we're seeking.

Anyway, let's continue with the code. For simplicity, some error checking code has been omitted (refer to the source code). Once retrieveShip returns our source ship, we first make sure it has some ammunition so it can shoot, and if it doesn't we return it to the space. If the source ship does have ammunition, then we retrieve the target ship. Once we have both source and target ship entries, we call decreaseAmmo on the source ship to simulate the firing of ammunition. We make an assumption that every call to the shoot method results in a strike on the target ship: to simulate the “hit,” we decrease the score of the target ship and increase the score of the shooting ship. Finally, we write both ships back into the space.

2.5.3. Trying the Game

With these new methods, let's return to our main method and add some more functionality:

public static void main(String[] args) {
  JavaSpace space = SpaceAccessor.getSpace();
  SpaceGame game = new SpaceGame(space);

  SpaceShip enterprise = new SpaceShip("enterprise", 10);
  game.writeShip(enterprise);
  System.out.println(enterprise.name +
    " written into space");
  System.out.println("The " + "enterprise's score is " +
    game.getScore("enterprise"));

  BattleSpaceShip klingon =
    new BattleSpaceShip("klingon", 10, 10);
  game.writeShip(klingon);
  System.out.println(klingon.name + " written into space");
  System.out.println("The " + "klingon's score is " +
    game.getScore("klingon"));
  System.out.println("The " + "klingon's ammo count is " +
    game.getAmmo("klingon"));

  game.shoot("klingon", "enterprise");
  game.shoot("klingon", "enterprise");
  game.shoot("klingon", "enterprise");

  System.out.println("The " + "enterprise's score is " +
    game.getScore("enterprise"));
  System.out.println("The " + "klingon's score is " +
    game.getScore("klingon"));
  System.out.println("The " + "klingon's ammo count is " +
    game.getAmmo("klingon"));
}

The first several lines of main are the same as before. Beyond that, we create a new BattleSpaceShip called “klingon” and write it into the space. We then call getScore and getAmmo within println to report the current score and ammunition count of the “klingon” ship. We then simulate the klingon battle spaceship shooting and hitting the “enterprise” ship three times (since the enterprise isn't defined as a battle spaceship, it can't shoot back). Last, we print out the ending scores of both ships and the ammunition count of the “klingon” ship.

When you run this example, you will see:

enterprise written into space
The enterprise's score is 10
klingon written into space
The klingon's score is 10
The klingon's ammo count is 10
klingon firing at enterprise
klingon firing at enterprise
klingon firing at enterprise
The enterprise's score is 7
The klingon's score is 13
The klingon's ammo count is 7

2.5.4. Post-Game Analysis

We probably would not ever implement a game like this for real, but it has been a fun way to explore the basics of working with entries and space operations. Along the way, we've learned quite a few important points about space-based programming. We've seen how to define an entry by implementing the Entry interface, or by subclassing an existing entry. We've seen that to get the state of an entry, we simply read the entry and leave it in the space. We've also seen that, to change the state of an entry, we first need to pull it out of the space, update it, and then put it back—we can't change an entry while it's in the space. We've simulated one ship shooting another, but it is all done outside of the space. We've also seen how entries are a means of shipping behavior through a space: a process that reads or takes a ship entry from the space can invoke the methods that come along with it.

We are now going to take a look at a low-level detail of a space: how entries are sent, stored and retrieved from a remote space. There are several reasons for doing so. First, there are several subtleties involved in using entries that are likely to cause unexpected behavior if you aren't aware of them. Second, many of the requirements and conventions for using entries and spaces will become clearer (and easier to remember) when you understand these details. In addition, as with any technology, the more you know about spaces, the more powerful things you can do with them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset