2.4. Reading and Taking Entries

The JavaSpace interface provides read and take methods that allow you to retrieve a copy of an entry in a space (read) or to retrieve a copy and at the same time remove an entry (take). To read or take an entry we first have to locate it within the space, and this is done via associative lookup. Rather than specifying the address of the entry or an identifier that points to the entry, we create a template that can be used to match entries in a space based on content.

As we will see in later chapters, this content-based method of retrieving entries allows us to uncouple distributed components and to support anonymous forms of communication. Both of these qualities lead to more robust, flexible, and scalable code.

2.4.1. Associative Lookup

A template is an entry that specifies enough content to identify the kind of entry we're looking for. To create a template, we instantiate an entry and selectively specify values for some fields and leave others as null. An entry and a template match if every specified field in the template “matches” the corresponding field in the entry. All null fields in the template act as wildcards and match any value in the corresponding field of an entry.

For instance, to look for any SpaceShip entry in a space, we first create a template out of a SpaceShip entry as follows:

SpaceShip anyShipTemplate = new SpaceShip();
anyShipTemplate.name = null;
anyShipTemplate.score = null;

Due to the two null fields, the anyShipTemplate will match any spaceship in the space, regardless of its name and score.

If we'd like to create a template to find our “enterprise” ship and not just any old spaceship, we need to specify the name “enterprise” in our template and leave the score field as a wildcard:

SpaceShip enterpriseTemplate = new SpaceShip();
enterpriseTemplate.name = "enterprise";
enterpriseTemplate.score = null;

Now this template will match any ship in the space that has a name of “enterprise,” independent of its score.

As another example, we could set up a template that looks for any ship with a score of zero, regardless of its name:

SpaceShip zeroScoreTemplate = new SpaceShip();
zeroScoreTemplate.name = null;
zeroScoreTemplate.score = new Integer(0);

Now that we know how to create templates, let's do something with them.

2.4.2. The Basics of read and take

Let's create a new method in our application called getScore, which takes a ship's name as an argument and returns that ship's score. To accomplish this, we need to read a copy of a SpaceShip entry from the space, and then access its score field. The method looks like this:

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

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

In getScore, we first create a template by instantiating a SpaceShip entry and setting its name field to the name of the ship we are looking for (the one passed into the method). Unlike our template examples from the last section, note that we don't set the score field to null. It turns out we don't have to explicitly set object fields to null to act as wildcards, since they are set to null by default when an object is created. We can just set the fields that we want to match against (in this case just the name field).

Wrapped inside a try / catch clause, you will see that we call the read method on our space object. The read method takes a template, a transaction (we again supply null), and a time-out value (specified in milliseconds). If the read does not immediately find a match for a template, it will wait up to the given number of milliseconds for a matching entry to be added to the space. By convention, a time-out value of Long.MAX_VALUE means that the read will wait indefinitely.

When a matching entry is found, read returns a copy of the entry, which we assign to the variable ship. Since read returns an object of type Entry, we have to explicitly cast the object to ShapeShip in the assignment.

Let's return to the main method of our application and add one line of code to make use of our new getScore method:

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"));
}

Here we've added a call to getScore within the second call to println to print the current score of the “enterprise” ship. Running our applet now gives us:

enterprise written into space
The enterprise's score is 10

2.4.3. The Rules of Matching

Now that we've informally seen how template matching works, let's take a look at the definitive rules of matching.

A template matches an entry if these two rules hold true:

  1. The template's type is the same as the entry's, or is a supertype of the entry's.

  2. Every field in the template matches its corresponding field in the entry, where:

    • If a template's field has a wildcard (null) value then it matches the entry's corresponding field.

    • If a template's field is specified (non-null) then it matches the entry's corresponding field if the two have same value.

The first rule says that a template and an entry can potentially match only if they are from the same class. Alternately, the entry may be from a class that was subclassed from the template's class. Let's look at some examples to make the typing rule clear. Consider the following classes:

// fields and no-arg constructors omitted below

public class Vegetable implements Entry {
}

public class Fruit implements Entry {
}

public class Apple extends Fruit {
}

public class Orange extends Fruit {
}

Here we have a class Vegetable, with no relation to the other classes, and a class Fruit, which has two subclasses, Apple and Orange. If we place a Vegetable entry into a space, only a template created from the Vegetable class can potentially match it. The same is true of a Fruit entry; we can only match it with a Fruit template (a Vegetable template, for instance, will never match it).

However, if we place an Apple entry into the space, then either an Apple template or a Fruit (Apple's superclass) template could be used to match it. An Orange entry is similar in that both Fruit and Orange templates have the potential to match it.

If you think about matching a Fruit template against an Apple entry, you might wonder how matching is handled for the fields in Apple that are not present in the Fruit superclass. For instance, the Apple subclass might have a boolean field worm, which indicates whether or not the apple has a worm in it. In this case, when the matching of an entry (of some class) and a template (of a superclass) is attempted, any additional fields in the entry are treated as if they were wildcards in the template.

Returning to the second rule of matching, given an entry and a template that satisfy the typing conditions of rule 1, the two will match if, for all fields that aren't wildcards in the template, each field has the same value as its corresponding field in the entry. The meaning of “same value” is closely tied to lower level details of how spaces operate, and we'll return to the topic briefly in Section 2.6.

2.4.4. Dealing with null-valued Fields in Entries

Entries generally have fields with non-null values, but you can certainly write an entry with null-valued fields into a space. The problem is that you can't match directly on the null values. For many fields, this limitation is not a big issue, since many fields just hold data and are not used in matching.

For fields that will be used in matching, and for which you want to be able to match on null values, there is a workaround to the limitation. Suppose you want to create a template that matches explicitly on the existence of a null value in a particular field of an entry; the obvious thought would be to create a template that has null for that field. The two fields will certainly match, but not because they are both null; rather, they will match because a null field in a template acts as a wildcard that will match any field value in an entry, null or otherwise.

There is no way of creating a template that can distinguish between an entry that has a null value in a certain field and one that doesn't. This is a slight limitation of the spaces system: the designers had to select some value to represent a wildcard, and null—which works for all object types—was the obvious choice. However, since null is used as the wildcard value, it ceases to be a value that we're able to match against.

Fortunately, this limitation is simple enough to work around. If you need to design an entry field such that you can distinguish between the case when it is null and when it isn't, then all you need to do is add a boolean field as an indicator of whether the field is set to null. As an example, let's take a look at the following class:

public class NodePtr implements Entry {
  public Boolean ptrIsNull;
  public Node ptr;
}

Let's assume you have a cache of NodePtr entries in a space and you need one that isn't pointing to any Nodes yet—whose ptr field is still set to null. You can construct a template that will match only NodePtr entries with ptr set to null like this:

NodePtr template = new NodePtr();
template.ptrIsNull = new Boolean(true);
template.ptr = null; // for completeness; null by default

2.4.5. Primitive Fields

As we have seen, only object fields are used in entry matching, and primitive types are not. The reason for this has to do with wildcards—as we saw in the last section, the designers of JavaSpaces technology chose to use null as a wildcard value for matching against fields with object references. The choice of a wildcard for primitive types is less obvious. For instance, for an integer we might choose 0, or -1 or Integer.MIN_VALUE as reasonable wildcards. However, what wildcard value would we use for a primitive such as a boolean? Would we use true or false? Obviously, neither is acceptable.

For this reason, the designers of JavaSpaces technology made the decision that all fields of entries should be object references—in the case where you are inclined to use a primitive field, you must instead use the primitive's corresponding wrapper class within an entry.

2.4.6. A Closer Look at read and take

Now that you have a better idea of how matching works, let's return to the read and take operations and look at them a little more closely.

The JavaSpace interface actually provides two versions of the read operation: read and readIfExists. Here are their definitions from the JavaSpaces specification:

Entry read(Entry tmpl, Transaction txn, long timeout)
  throws TransactionException, UnusableEntryException,
      RemoteException, InterruptedException;

Entry readIfExists(Entry tmpl, Transaction txn, long timeout)
  throws TransactionException, UnusableEntryException,
      RemoteException, InterruptedException;

The read and readIfExists methods both take a template, a transaction, and a time-out value. As we've already seen, the template is just an entry with some fields specified and others left as null. Note that you can also use null as a template, which will match any entry in the space.

Just as we saw in the write method, the second parameter is a transaction. For the next several chapters, we will continue to supply a null transaction in our examples. The last parameter is a time-out parameter specified in milliseconds.

Both read and readIfExists look for and return a copy of a matching entry from the space (if such an entry exists). In the case where there are many entries in the space that match the template, just one entry will be returned. You should assume that the space makes an arbitrary choice among multiple matching entries—in other words, you shouldn't write code that depends on any ordering of entries. For example, even if you write a particular entry first, there's no guarantee it will be the first one retrieved. Even if you repeatedly use the same template in a read operation, there is no guarantee that you'll get the same entry back repeatedly.

The two variants of read differ in that read is a “blocking” operation, while readIfExists is not. If no matching entry exists in the space, a read will wait for timeout milliseconds until a matching entry is found. Two time-out values have special meaning: Long.MAX_VALUE means to wait indefinitely (the read will block until a matching entry is available), while the constant JavaSpaces.NO_WAIT means to return immediately if no entry matches the template. If read returns without finding a matching entry, then null is returned by the method.

The readIfExists method, on the other hand, is a “nonblocking” operation that will return immediately if no matching entry is found. The time-out parameter comes into play only when the operation occurs under a transaction; we'll see the details in Chapter 9. For now, you need to know that, under the null transaction, readIfExists is equivalent to calling read with a time-out of NO_WAIT.

Both methods can throw the following exceptions: TransactionException, UnusableEntryException, RemoteException and InterruptedException. The TransactionException is thrown for the same reasons it is thrown in the write operation (that is, if the supplied transaction is invalid). The RemoteException occurs if the network or remote space fails. The Unusable EntryException is thrown when an entry is retrieved from the space that cannot be properly deserialized by your process (we will talk about this exception and serialization in Section 2.6). Finally, InterruptedException is thrown when the thread executing the read method is interrupted in some way, typically by an invocation of the interrupt method on the thread.

Like read, take has both blocking and non-blocking forms:

Entry take(Entry tmpl, Transaction txn, long timeout)
  throws TransactionException, UnusableEntryException,
      RemoteException, InterruptedException;

Entry takeIfExists(Entry tmpl, Transaction txn, long timeout)
  throws TransactionException, UnusableEntryException,
      RemoteException, InterruptedException;

Each take method performs just like the corresponding read method, with the difference that take operations actually remove the matching entry from the space. If take returns a non-null value and no exceptions occur, then we're guaranteed that the entry has been removed from the space, and no other space operation—by this or any other process—will return the same entry.

Both forms of take throw the same exceptions as read and readIfExists. In the case of the take operation, if a TransactionException, Unusable EntryException or InterruptedException occurs, then no entry is taken from the space. However, with a RemoteException, an entry might be removed from a space and yet never returned to the proxy that performed the take. This results in the loss of an entry. In code where this is unacceptable, the take can be performed under a transaction to guarantee that the entry is either taken and returned to the proxy, or not taken from the space at all. We will see how this can be accomplished in Chapter 9.

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

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