COMP 310
Fall 2018

Lec28: Generic Extended Visitors and Data Packets

Home  Info  Canvas   Java Resources  Eclipse Resources  Piazza

One last item about RMI:  Transient Fields in Classes

HW07 overview


Warning:  the material in this lecture is conceptually very difficult.   No student is expected to understand this material right away.   We will be taking the approach of quickly showing the class the breadth of the topic to give everyone a feel for the overall issues involved and then as we encounter specific issues in our examples, we can deal with them in as a concrete, contextual manner as possible.

Advice: When studying the advanced generics discussed here, concentrate more on the ideas and relationships that the generics are trying to express and less on the syntax that is being used to implement those notions.  

First let's review a little bit on generics and push those notions on to generic list frameworks.


Generic Extended Visitors

Life gets a bit more complicated when we try to apply generics to extended visitors.   The difficulty stems from the fact that the type of the host ID (index) and the type of the host itself are not independent.  Nor are they independent of the visitors that are used to process them.  

Unfortunately, the Java generics syntax to express this is unwieldy at best and unable to do the job at worst.  The following the current state of the art in expressing the relative typing notions in extended visitors:

For instance, a particular type of host (specifically, the top-level, superclass of all concrete hosts) is defined as using an ID value of a particular type.   This is related to the fact that a host should only accept (execute) a visitor who can handle that type of host.   We want to enforce the relative covariant-contravariant relationship between the host and visitor, a characteristic of these sorts of component-framework systems, as discussed in the above linked Resources pages.   A host thus becomes defined in terms of the type of the host that its visitors can be accepted by: 

/**
 * An interface defining a host for an IExtVisitor who can visit this type of host.
 * Each concrete host will have a particular index value associated with it and call
 * the case associated with that index value on the visitor.
 * @param <I> The type of the index value used by the extended visitor.
 * @param <H> The type of the host used by the extended visitor.  Restricted to be an implementation of IExtVisitorHost<I, ? extends H>
 * 
 */
public abstract interface IExtVisitorHost<I, H extends IExtVisitorHost<I,? extends H>> extends Serializable{
  /**
   * The method that executes (accepts) the extended visitor to process this host.
   * @param <R> The return type of the visitor.
   * @param <P> The type of the input parameters of the visitor.
   * @param algo The extended visitor to execute.
   * @param params  Vararg input parameters to the visitor.
   * @return The return value from executing the visitor.
   */
  public <R, P> R execute(IExtVisitor<R, I, P, ? extends H> algo,  P... params);
}

These sorts of generic definitions are sometimes called "recursive typing" for obvious reasons.   This recursive typing necessarily affects the typing of the visitor, which must be defined in terms of a host...a host who can accept (execute) visitors whose hosts are supertypes of this visitor's host.   Got that?   

/**
 * Interface that defines an extended visitor that has specific types for its
 * return value, R, its index value, I, its input parameters, P, and its
 * host, H.   The host is restricted to being a subclass of IExtVisitorHost 
 * who takes the same index value and who accepts a visitor that takes this 
 * same host type.
 * @param <R> The type of the return value
 * @param <I> The type of the index value
 * @param <P> The type of the input parameters
 * @param <H> The type of the host, restricted to being a subclass of IExtVisitorHost<I, H>
 * @author Stephen Wong (c) 2010
 */
public abstract interface IExtVisitor<R, I, P, H extends IExtVisitorHost<I, ? super H>> extends Serializable{
	/**
	 * The parameterized case of the visitor.  The case is parameterized by the index value, idx.
	 * @param <T> The type of the host the is expected to call this method.  T is restricted to be a subclass of IExtVisitorHost<I, ? super H>
	 * @param idx The index value for the desired case
	 * @param host The host for the visitor
	 * @param params Vararg input parameters
	 * @return The value returned by the running the indexed case.
	 */
	public <T extends IExtVisitorHost<I, ? super H>> R caseAt( I idx, T host, P... params);
}

When one moves down a level to the implementation of a command-based generic extended visitor, the generics get even hairier.   The situation is complicated by the fact that at this level, these are abstract classes that are supplying strongly-typed services for their subclasses.   In the end, we want the typing to apply to the sub-class, not this abstract class:  

/**
 * Abstract implementation of IExtVisitorHost that provides defines a host by an index value and thus, 
 * a concrete execute method.    
 * 
 * Usage:   
 * public class MyExtVisitorHost extends AExtVisitorHost<MyIndex, MyExtVisitorHost> {...}
 * 
 * @param <I>  The type of the index value that is being used.
 * @param <H>  The type of the concrete SUBCLASS that extends this class.
 * @author Stephen Wong (c) 2010
 */
public abstract class AExtVisitorHost<I, H extends IExtVisitorHost<I, ? extends H>>  implements IExtVisitorHost<I, H> {
	// code body elided
}

The above declaration looks similar to those above, but the meaning of "H" has changed to refer to the subclass's type.   The same rubric applies to the abstract command-based visitor class:

/**
 * Abstract implementation of IExtVisitor that adds an invariant implementation of 
 * storing commands associated with each case in a dictionary indexed by the 
 * case's associated index value.   
 * When a particular case is called, the associated command is retrieved and 
 * executed.  The return value is the return value from the command.
 * If no associated command is found, then a default command is executed.
 * In general, command-based implementations of IExtVisitor will be concrete subclasses of this class.
 * 
 * Usage:  
 * public class MyExtVisitor extends AExtVisitor<MyReturn, MyIndex, MyReturn, MyExtVisitorHost> {...}
 * 
 * @param <R> The type of the return value
 * @param <I> The type of the index value
 * @param <P> The type of the input parameters
 * @param <H> The type of the host, restricted to being a subclass of IExtVisitorHost<I, ? super H>
 * @author Stephen Wong (c) 2010
 */
public abstract class AExtVisitor<R, I, P, H extends IExtVisitorHost<I, ? super H> > implements IExtVisitor<R,I,P, H> {
	// code body elided
}

The typing issues carry over to the commands that are used.   The commands are typed similarly to the visitors that hold them:

/**
 * Interface that defines a command used by AExtVisitor that has specific 
 * types for its return value, R, its index value, I, its input parameters, P, 
 * and its host, H.   The host is restricted to being a subclass of 
 * IExtVisitorHost who takes the same index value and who accepts a visitor 
 * that takes this same host type.
 * An IExtVisitorCmd is associated with every recognized case of an AExtVisitor,
 * including the default case.
 *
 * Usage: 
 * new IExtVisitorCmd<MyReturn, MyIndex, MyParam, MyExtVisitorHost>(){
 *     public <T extends IExtVisitorHost<Integer, ? super MyExtVisitorHost>> MyReturn apply(MyIndex index, T host, MyParam... params) {...}
 * } 
 * 
 * @param <R> The type of the return value
 * @param <I> The type of the index value
 * @param <P> The type of the input parameters
 * @param <H> The type of the host, restricted to being a subclass of IExtVisitorHost<I, ? super H>
 * @author Stephen Wong (c) 2010
 */
public abstract interface IExtVisitorCmd<R, I, P, H extends IExtVisitorHost<I,? super H>> extends Serializable{  
  /**
   * The method that is run by AExtVisitor when the case associated with this 
   * command is executed.
   * @param <T> The type of the host the is expected to call this method.  T is restricted to be a subclass of IExtVisitorHost<I, ? super H>
   * @param index The index value for the case for which this command is associated. 
   * @param host The host for the visitor
   * @param params Vararg input parameters
   * @return The value returned by the running this command.
   */
  public abstract <T extends IExtVisitorHost<I, ? super H>> R apply(I index, T host, P... params);
}
 

To see what an algorithm using generic extended visitors, please see the implementation of the deletion algorithm for the self-balancing trees.

One of the key reasons for using the recursive generic typing described above is to enable the transparent (in terms of typing) insertion of intermediate abstract classes that provide services to their subclasses.    The recursive typing allows the implementation level of the extrended visitor hierarchy to be typed relative to the classes and interfaces at that same level, rather than with respect to their superclasses as would happen if the recursive typing had not been used.


(click for full-size image)
Datapacket UML diagram

Generic Data Packets

Data packets are simply wrappers around data objects.   Think of data packets as shipping boxes for data.    Data packets enable the rest of the system to deal with an consistently typed object that provides services relevant to the transport of the data.     Here, the data packets will support a visitor design pattern, which will enable us to do type-dependent processing on the data contained inside the data packet.   Since generic data packets are a more specific application of generic extended visitors, they enable us to implement some welcome syntactical simplifications.   

In the end, we'd like to define something like DataPacket<T> where T is the type of data the data packet holds.   Each different type of data, T, DataPacket<T> represents a different type of host.  At some level, it doesn't really matter what the host ID associated with T actually is, just that it be uniquely associated with that data type. Here, we will simply use an interface IDataPacketID, to represent that value which identifies a particular host data type T. That is, any given data type T has an associated IDataPacketID that uniquely identifies that data type. All that is required to determine if two pieces of data are the same type, in terms of how the visitor processes by associating the same case to them, is that their respective IDataPacketID values are equal by the strict Java object equality rules. That is, id1 and id2 represent the same data type if and only if id2.equals(id1) and reflexively, id1.equals(id2). Note that this does NOT imply that id1 == id2 ! It does however imply that id1.hashCode() == id2.hashCode() . The result of all of this is that two different instances of data can be considered to be the same data type even if the implementations of their associated IDataPacketID are different, so long as their ID values satisfy object equality.

Abstract Data Packets and Data Packet Host IDs

First, we define an abstract level that simply sets up the data packet as an extended visitor host that uses a particular kind of ID type to call it case on its visitors. The host ID value that a data packet uses is associated with the type of data the data packet is holding. Thus, data packets that hold different types of data are technically different extended visitor hosts.

/**
 * A host ID value compatible with ADataPackets
 * 
 * The implementation of this interface is immaterial.   It only has to follow
 * the Java rules for object equality, i.e. the rules governing the 
 * behavior of the hashCode() and equals() methods.
 * 
 * This enables two completely different ID value implementations to be used 
 * to represent the same data type, so long as they are "equal".
 * 
 * @author Stephen Wong (c) 2018 
 */
public interface IDataPacketID extends Serializable {
}

/**
 * Abstract data packet that defines the use of a IDataPacketID object as the host ID type.  
 * The type of data held by the data packet defines its type and thus what case it
 * calls on its processing visitors.
 * Specifies use of IDataPacketID type for host ID
 * @author Stephen Wong (c) 2018
 */
public abstract class ADataPacket extends AExtVisitorHost<IDataPacketID, ADataPacket> {

	/**
	 * Version number for serialization
	 */
	private static final long serialVersionUID = 5990493490087888131L;

	/**
	 * The stub for the sender of the datapacket
	 */

	/**
	 * Constructor for this abstract superclass
	 * @param id An IDataPacketID value to be used as the host ID value defining this type of data packet.
	 */
	public ADataPacket(IDataPacketID id) {
		super(id);
	}

}

Data Packet Data Types

In order to enforce the invariance that the data types we are working with have a well-defined ID associated with them, we defing a top-level abstract data type that "knows" its own ID value, IDataPacketData. All instances of IDataPacketData are able to produce the correct ID value associated with their data type.

It is important to note at this point that the system does not care about the implementation of any given data type. The system should be decoupled from the data type implementations!

ALL DATA TYPES MUST BE DEFINED BY INTERFACES, NOT CONCRETE CLASSES!

Thus all data types held by data packets are defined by interfaces extending IDataPacketData.

The getID() method will be implemented as a default method on the interface defining a data type. This enables all implementations of the defining interface to invariantly be able to produce the correct ID value for their own data type. Unfortunately, the Java language does not allow a default method to be final, so that invariance cannot be fully protected by the compiler.

We would also like to be able to get the associated ID value directly from the defining interface for any given data type. This is useful when the ID is needed but there is no data of that type available, such as when loading case commands into an extended visitor. Unfortunately, the Java language neither enables interfaces to define abstract behavior for its static methods nor does it allow instances to call static methods as if they were instance methods.. This forces the definition of a static GetID() method for each data type's defining interface without the aid or protections from compiler. Below are some examples of how that can be accomplished with a minimum of repeated code.

/**
 * Top-level datapacket data type.   DataPackets will only hold data of this type.  
 * This is a data type that knows its own host ID value and is Serializable.
 * 
 * Note that intermediate interface definitions below this level can be used to provide type-narrowing and
 * when used in specific API's, where the datapacket sender type is known, default methods can be defined
 * that enable a data type instance to generate its own datapacket.
 * 
 * @author Stephen Wong (c) 2018
 */
public interface IDataPacketData extends Serializable {

	/**
	 *  public static IDataPacketID GetID() {...}
	 *  
	 *  For convenience, a corresponding static GetID() method should be defined on the specific implementing
	 *  data type interface (which must extend IDataPacketData).   GetID() should return the same value as getID().
	 *  The implementation of GetID() should call an IDataPacketFactory's makeID() method.  The default getID()
	 *  implementation should simply delegate to the static GetID() method (see the getID() docs).
	 *  
	 *  Unfortunately, Java has no syntax to define abstract static methods, so the GetID() method cannot 
	 *  be defined at this top level to force its implementation at the lower specific data type levels.  
	 *   
	 */

	/**
	 * Get the host ID value associated with the specific implementing data type.
	 * This method should be overridden with a default method by the INTERFACE 
	 * defining the data type. 
	 * 
	 * NEVER subsequently override the defined default method!  Unfortunately, 
	 * Java has no syntax for defining an invariant method at the interface level.    
	 * That default method represents the invariant ID value that is associated with 
	 * the data type defined by the interface it is defined on.  
	 * 
	 * Since an instance method, such as a default method, can delegate to a static method but not the other
	 * way around, the implementation of getID() should simply delegate to the interface's static GetID() method.
	 * 
	 * @return A host ID value compatible with ADataPackets
	 */
	public IDataPacketID getID();

}

Data packet data types are often narrowed by defining intermediate abstract data types that are used to restrict the types of data used in different parts of a system. This helps promote greater type-safety making development easier and less error-prone.

Generating DataPacketID Values

There are many ways to generate a unique identifier for a class but since the system doesn't actually care how that is accomplished, the details of the process can be hidden inside of a factory. A single factory can be defined that is able to generate the associated IDataPacketID value for any given data type. All that is needed to do so is something, an object, that captures all relevant data about the data type.  

In Java, there is a whole "meta-programming" system called the "Java Reflection" system.   This is a series of classes that are used to represents classes, methods, etc.    In particular, the class Class<T> is a class that represents the class of T.   (Yes, it's a class that represents a class!).

Every class and interface in Java, T, has a static field called "class" that references an object of type Class<T>.   That is, String.class references an object of type Class<String>  and  JFrame.class references an object of type Class<JFrame>.    This object is unique for every class and can thus be used as the ID value in the extended visitors.    It should be noted that "T.class" is not a valid Java syntax because by run-time, the actual class of T has been type-erased (and yes, it is legal in a language that has run-time generics such as C#!).

The Class object representing the data type's interface captures all the relevant information about that interface and thus is very useful in generating a unique ID value for that interface and thus for the defined data type.

We can thus define an abstract factory that takes a Class object representing the data type's defining interface and generates an associated IDataPacketID value for that data type. The factory's makeID() method takes a Class<? extends IDataPacketData> which means that it only accepts Class objects representing sub-types of IDataPacketData which is the top-level abstract data type with which the system works. This restriction provides some practical type safety during development.

/**
 * Abstract factory for generating datapacket-compatible host ID values.
 * The purpose of the factory is to hide the implementation of the ID values,
 * since the ID will work as long as it follows Java's rules for object equality.
 * @author Stephen Wong (c) 2018
 */
public interface IDataPacketIDFactory {

	/**
	 * Generate datapacket host ID value given the Class object corresponding to the INTERFACE
	 * that defines a data type.  Since the interface defines the data type, the Class object 
	 * representing that interface has all the necessary information to generate the unique ID 
	 * for that data type. 
	 * @param dataInterface   The Class object for a data type's defining interface.
	 * @return The datapacket host ID value that corresponds to the given data type.
	 */
	public IDataPacketID makeID(Class<? extends IDataPacketData> dataInterface);

}

Concrete implementations of this factory can thus be defined, typically as singleton instances since they tend to be stateless.

A typical data type definition would thus look similar to the code below. In practice there are often intermediate level interfaces between IDataPacketData and the data type definition to provide "type narrowing" to help enforce type safety and increase ease-of-use in the system.

Data Packet Data Type Definition Example

A data type definition is an interface that extends IDataPacketData either directly or through intermediate abstract data type definitions:

/**
 * An example definition of a datapacket compatible data type.
 * This INTERFACE defines the data type, not any concrete class implementation!
 */
public interface IExampleDataType extends IDataPacketData {
	/**
	 * This method allows one to get the ID value directly from the interface.
	 * 
	 * The only difference between this code and any other data type's getID() code is the value of the 
	 * Class object being passed to the DataPacketIDFactory's makeID() method.    This has to be 
	 * specified here because this is the only place where the proper Class object is unequivocally known.
	 * 
	 *	@return The ID value associated with this data type.
	 */
	public static IDataPacketID GetID() {
		return DataPacketIDFactory.Singleton.makeID(IExampleDataType.class);   // DataPacketIDFactory.Singleton is an instance of an IDataPacketIDFactory
	}
	
	/**
	 * This method MUST be defined at this INTERFACE level so that any concrete implementation 
	 * will automatically have the ability to generate its proper host ID value.
	 * Since an instance method can call a static method but not the other way around, simply delegate to 
	 * the static method from here. 
	 * 
	 * NEVER override this method, as it defines an invariant for the data type.   Unfortunately, Java does not allow 
	 * one to define an invariant instance method at the interface level, i.e. this method cannot be made final.
	 */
	@Override
	public default IDataPacketID getID() {
		return IExampleDataType.GetID();
	}
	
	
	// Other methods elided
}

Concrete ADataPacket Implementation

The actual data packet is just a container for the type of data specified by the ID value. By restricting the contained data to be instances of IDataPacketData, the data packet can guarantee that it has access to the correct host ID value for the data. Since a data packet is really just a "box" for transporting data from one machine to another, a data packet, just like a UPS package, has a "sender". The actual type of the sender is dependent on the system, so it is generically defined at this level.

/**
 * Concrete data packet that holds a generic type of data.
 * Adds internal data content of type T and host ID type IDataPacketID
 * @author Stephen Wong (c) 2018
 *
 * @param <T>  The type of the data being held.  T must be an INTERFACE extending IDataPacketData. 
 * @param <S>  The type of the sender object to which datapackets can be sent back to. 
 */
public class DataPacket<T extends IDataPacketData, S> extends ADataPacket {

	/**
	 * Version number for serialization
	 */
	private static final long serialVersionUID = -4384652128226661822L;

	/**
	 * The data being held
	 */
	private T data;

	/**
	 * The sender of this data packet
	 */
	private S sender;

	/**
	 * The constructor for a data packet. 
	 * Usage: 
	 * 
	 * ADataPacket dp = new DataPacket<IMyData>(aMyData, aSender);
	 * 
	 * @param data  The data to be held in the data packet.  The data's host ID value will be pulled from the data object.
	 * @param sender The sender of this data packet
	 */
	public DataPacket(T data, S sender) {
		super(data.getID());
		this.data = data;
		this.sender = sender;
	}

	/**
	 * Accessor for the held data
	 * @return  The data being held
	 */
	public T getData() {
		return data;
	}

	/**
	 * Accessor for this data packet's sender
	 * @return the sender
	 */
	public S getSender() {
		return sender;
	}

}

The actual DataPacket type in use in a system can be type-narrowed by defining a subclass that has a well-defined sender type. This increases type safety and makes the system easier to use.

If the data types are narrowed to the point that the type of data packet's sender (see below) type is known, then it become possible to define a data type that can generate its own datapacket.

ADataPacket Visitors

The visitor for an ADataPacket is simplified from the generalized extended visitor because we know its ID type and the fact that the contained data is restricted to instances of IDataPacketData:

/**
 * Concrete visitor for processing an abstract data packet.
 * For convenience and increased type safety, the commands used by this visitor should be subclasses of
 * ADataPacketAlgoCmd<R, D, P>, where D is the type of the data that particular command processes,
 * i.e. the defining type of its associated DataPacket<D> host.
 * @author Stephen Wong (c) 2018
 *
 * @param <R>  The return type of the visitor
 * @param <P>  The vararg input parameter type of the visitor 
 */
public class DataPacketAlgo<R,P> extends AExtVisitor<R, IDataPacketID, P, ADataPacket> {

	/**
	 * Version number for serialization
	 */
	private static final long serialVersionUID = 2045919428614770686L;

	/**
	 * Constructor for the class.
	 * @param defaultCmd  The default command to be used.  See the main class documentation for usage suggestions.
	 * @param <A> The type of the adapter for the command to the local model.
	 * @param <H> The specific type of ADataPacket subclass that is being used
	 */
	public <A, H extends ADataPacket> DataPacketAlgo(ADataPacketAlgoCmd<R, ? extends IDataPacketData, P, A, H> defaultCmd){
		super(defaultCmd);
	}
}

The second type parameter of the ADataPacketAlgoCmd default command above is the type of data is works on. Because it is the default command, it must take the top-level IDataPacketData as its data type. The wildcard specification allows for type-narrowing using intermediate abstract data type interfaces.

It is common in large systems to restrict the visitors in particular parts of the system to use restricted data types and thus type-narrowed subclasses of DataPacketAlgo are often utilized to create greater type safety and ease of use and development.

DataPacketAlgo Commands

In the full generic extended visitor implementation, the commands were problematic because Java's type system did not allow us to strongly type the host input parameter to match the actual hosts used.  But since we are working in the reduced, more specific space of data packets, we can exactly determine the host's type a priori.  The application of a Template Method Pattern enables us to delegate from a type-unsafe apply method to a type-safe one:

/**
 * A DataPacketAlgo command that is designed to work on a DataPacket<D> host.
 * 
 * This convenience class both simplifies the command code but also increase type safety by restricting the host type.
 * 
 * Usage:
 * 
 * myDataPacketAlgo.addCmd(IMyData.GetID(), new ADataPacketAlgoCmd<IMyReturn, IMyData, IMyParam>(){
 *     private static final long serialVersionUID = aGeneratedUIDvalue;
 *     
 *     public IMyReturn apply(DataPacket<IMyData> host, IMyParam... params){
 *         // your code here
 *     }
 * }
 * 
 * Note:  In Eclipse, the auto-generation of the implemented methods of this class does not work properly.
 * The concrete apply method below is replicated by the automatic method generator because it doesn't 
 * recognize that the method already exists and is final.  Luckily, a compiler error message gets generated
 * in the attempt to override a final method.   Simply delete the extraneous auto-generated method.
 * 
 * @author Stephen Wong (c) 2018
 *
 * @param <R> The return type
 * @param <D> The data type held by the host
 * @param <P> The input parameter type 
 * @param <A> The type of the adapter to the local model
 * @param <H> The type of datapacket host the command should coerce the given host into when dispatching to the abstract apply() method.
 * * ----------------------------------------------
 * Restricts command to hosts of type ADataPacket
 */
public abstract class ADataPacketAlgoCmd<R, D extends IDataPacketData, P, A, H extends ADataPacket>
		implements IExtVisitorCmd<R, IDataPacketID, P, ADataPacket> {

	/**
	 * Version number for serialization
	 */
	private static final long serialVersionUID = -5627902537609466988L;

	/**
	 * The actual method called by the host visitor when the associated case is invoked.   
	 * This method simply forwards the call to the abstract apply method, performing 
	 * an unchecked cast of the host to the required DataPacket type.
	 * @param id  The IDataPacketID value used to identify the host
	 * @param host The host calling the visitor
	 * @param params Vararg input parameters to be used for processing the host
	 * @return The result of this case.
	 */
	@SuppressWarnings("unchecked")
	final public <T extends IExtVisitorHost<IDataPacketID, ? super ADataPacket>> R apply(IDataPacketID id, T host,
			P... params) {
		return apply(id, (H) host, params);
	}

	/**
	 * Abstract method that actually performs the processing of the case.   
	 * Here, the host is strongly typed to be the DataPacket type appropriate for the case (D).
	 * @param index The host ID identifying the host
	 * @param host  The DataPacket host calling the visitor
	 * @param params  Vararg input parameter to be used for processing the host
	 * @return  The result of this case.
	 */
	abstract public R apply(IDataPacketID index, H host, @SuppressWarnings("unchecked") P... params);

	/**
	 * Sets the ICmd2ModelAdapter for this command to use to communicate with the
	 * local ChatApp host system.   Any implementation that saves this reference 
	 * should mark its internal ICmd2ModelAdapter field as "transient" to prevent it
	 * from being serialized during any transport process.
	 * @param cmd2ModelAdpt  The adapter to the ChatApp model.
	 */
	abstract public void setCmd2ModelAdpt(A cmd2ModelAdpt);
}

The Java compiler would normally issue an "Unchecked cast" warning at the casting of the host to DataPacket<D> in the upper apply method.   But the invariant execution contract of the Visitor pattern insures us that only hosts of type DataPacket<D> will call the apply method of the command.  The cast is therefore always safe and thus we can safely use the "@SuppressWarnings("unchecked")" compiler directive.

The end result is that we now have a fully type-safe apply method that we will override (implement) to perform our work on data packets.

Since the command is the entity that is actually going to process the data contained in a host data packet, every command needs an adapter (generic type "A" above) to the rest of the system to display its results and/or communicate its results to the rest of the system. The adapter interface may be part of a larger API to which the command is bound.

As with the other parts of the system, type-narrowed ADataPacketAlgoCmd definitions are often used for greater type-safety.

Composite IDataPacketData Data Type Example

Sometimes it is useful to be able to send a collection of data packets all at once. Here is an example of an IDataPacketData data type that can be used to represent data that is a collection of data packets.  This is useful when transporting aggregated data transmissions, say from multiple sources.   Because Java disallows the use of arrays of generic types as either subtypes or supertypes of other arrays of generic types, creating data packets of arrays of DataPacket<T> is problematic.   So instead, we would like to define a data type that is a collection of ADataPackets, for instance, a Vector<ADataPacket>, but here type erasure bites us because Java cannot distinguish between vectors of holding different types at run time.  We would not be able to tell the difference between a data packet that held a vector of data packets from a data packet that held a vector of Strings, for instance.    So, we are forced to define a specific data type that extends IDataPacketData which can be implemented as a subclass of Vector<ADataPacket>. This data type is no longer generic and thus has a well-defined, specific ID associated with it.

Since we are defining our data type as an Iterable<ADataPacket>, we can also define a static method to create an invariant mapping of a given visitor over all the elements of the Iterable.

/**
 * A data type that represents an Iterable of data packets
 * @author Stephen Wong (c) 2018
 *
 */
public interface IVDataPacket extends IDataPacketData, Iterable<ADataPacket> {


	/**
	 * This method allows one to get the ID value directly from the interface.
	 * 
	 * The only difference between this code and any other data type's getID() code is the value of the 
	 * Class object being passed to the DataPacketIDFactory's makeID() method.    This has to be 
	 * specified here because this is the only place where the proper Class object is unequivocally known.
	 * 
	 * @return The ID value associated with this data type.
	 */
	public static IDataPacketID GetID() {
		return DataPacketIDFactory.Singleton.makeID(IVDataPacket.class);
	}
	
	@Override
	public default IDataPacketID getID() {
		return IVDataPacket.GetID();
	}
	
	
	/**
	 * Convenience method that creates a command that maps a visitor over the vector of data packets.
	 * 
	 * Since the result returned by the returned command is a List of R, 
	 * the returned command must be wrapped in another command
	 * before it can be used in a recursive algorithm, which would require a return of type R.
	 * @param <R>  The return type of the given visitor.
	 * @param <P>  The vararg input parameter type of the original visitor. 
	 * @param <A> The type of the adapter to the local system.
	 * @param <S> The type of the sender
	 * @param algo The visitor to be mapped over all the stored data packets.
	 * @return An ADataPacketAlgoCmd whose results are a vector of results from applying the given visitor to each data packet element.
	 */
	public static <R, P, A, S> ADataPacketAlgoCmd<List<R>, IVDataPacket, P, A, DataPacket<IVDataPacket, S> > makeMapCmd(final DataPacketAlgo<R,P> algo) {
		return new ADataPacketAlgoCmd<List<R>, IVDataPacket, P, A, DataPacket<IVDataPacket, S>>(){

			private static final long serialVersionUID = -5855856243603215928L;

			@Override
			public List<R> apply(IDataPacketID id, DataPacket<IVDataPacket, S> host,  @SuppressWarnings("unchecked") P... params) {
				List<R> results = new ArrayList<R>();
				for(ADataPacket d: host.getData()){
					results.add(d.execute(algo, params));
				}
				return results;
			}
			
			@Override
			public void setCmd2ModelAdpt(A cmd2ModelAdpt) {
				// not used.
			}

		};
	}
}


/**
 * Concrete IVDataPacket composite data type for use in data packets, DataPacket<IVDataPacket>, 
 * implemented as a Vector of ADataPackets. 
 * Note that Vector<ADataPacket> cannot be used as a data packet data type 
 * directly because type erasure prevents the distinction between 
 * Vector<A> and Vector<B> -- that is, they produce the same Class object.
 * 
 * Usage:
 *      // make the vector of data packets
 *      VDataPacket vdp = new VDataPacket();
 *      vdp.add(datapacket1);
 *      vdp.add(datapacket2);
 *      vdp.add(datapacket3);
 *      // etc
 *      // make the composite data packet
 *      ADataPacket vd = new DataPacket<IVDataPacket, SenderType>(vdp, aSender);
 * 
 * This class is a vector of the abstract data packets, ADataPacket.  If a vector of more specific 
 * types of data packets is desired, a custom class that is a sub-class of the desired
 * Vector type should be used instead of this class.   Composites made as such would be 
 * distinguishable as per their held types.
 * 
 * @author Stephen Wong (c) 2018
 *
 */
public class VDataPacket extends Vector<ADataPacket> implements IVDataPacket{

	/**
	 * Version number for serialization
	 */
	private static final long serialVersionUID = -860544422905072718L;

}

Conclusion

With the above infrastructure, we are poised to send data packets wherever we want, containing whatever we want and being processed however we want!   And now for the real kicker:

Not only do data packets carry information in the data they contain, but the TYPE of the data that they contain is also information in of itself and in a Visitor pattern scenario, it is the type of the data that will determine the actions that will take place in response to receiving the data packet.   The data is just support information for that type-driven action!

(At this point, it is perfectly normal for you to feel like this.)

 


© 2018 by Stephen Wong