jEQN is built on top of a software architecture that allows to decouple the simulation logic of each component from the coordination and communication logic of the simulation container, known as simulation engine, either in a local or a distributed environment. As a result, the jEQN syntax is defined in terms of the linking services provided by the architecture and the jEQN-specific simulation components. The linking services are provided by the underlying service interface to configure the simulation model by connecting the simulation components, which can be classified into the following groups, according to the typical entities found in the EQN domain:
Groups ranging from 1 to 5 are the simulation components commonly identified in EQN domain, while group 6 is here introduced to support the parameterization of the remaining groups. Components like users, user generators, service request generators and policies belong to group 6.
The design of jEQN is based on a key principle that can be summarized as “single functionality for each component”. Each simulation component of groups 1 through 5 encapsulates only the behavioural logic and thus is not concerned with parameters that do not directly affect it. By such approach, the cohesion of each component is maximized to make it reusable across the several values the parameters might assume. A key point is hence how the parameters are chosen. A straightforward, but effective, methodology is to analyse the simulation logic, i.e., the sequence of states and actions taken in correspondence to external events and then detect the candidate parameters among the characteristics that do not affect the logic itself. Thus, the parameters are designed and developed in isolation, in order to decouple the jEQN simulation components from the parameters themselves and to allow a flexible configuration of such components.
By analyzing the logic of each component, also referred to as behavior, it is possible to detect the candidate parameters that do not affect the structure of the simulation logic itself. The results of such analysis are summarized below.
User sources have the following candidate parameters:
Analogously, waiting systems can be parameterized as follows:
While routers can clearly be parameterized by the routing policy, service centers do not allow any kind of parameterization.
The group of special nodes includes several components, such as nodes for managing pools of tokens (create, allocate, release, destroy) and nodes for parallel execution and synchronization (fork, join, split). For the sake of brevity, the parameterization of such nodes is not discussed here, even though it can be easily deferred from their behavior.
The parameters identified in the previous section can be classified into the following categories depending on their peculiarities:
cat1. parameters used to take decisions;
cat2. parameters that provide sequence of values;
cat3. parameters that provide storage support.
Parameters like the enqueuing policy (decision: where to insert a given user) and the termination condition (decision: assess whether or not the source has to terminate), belong to the category 1. Category 2 includes parameters like inter-arrival times, user generators, etc. Finally, the category 3 defines parameters for storing objects along the simulation.
The first general design guideline to produce reusable components is to define a parameter as an interface within each component and to have an implementation passed at declaration time. By such approach components are configured with the proper value of parameters. In addition, future extensions can be easily dealt with.
However, the realization of a DSL does not rely only on the above guidelines. Allowing the parameters to be built by assembling small components plays a fundamental role to obtain a flexible language.
Compared to the other categories, the parameters in this category have a unique feature: they are used to take decisions, i.e. to implement policies. To support a flexible configuration of them, two main hierarchies are defined: DecisionData, to model the data used to take the decision, and Policy, to classify the decision type.
The DecisionData hierarchy, which is developed according to the schema in Figure 1, allows decoupling the jEQN components from the data upon which the policy has to return the decision.
Figure 1. Decision Data Hierarchy
The root class, which requires a generic type T at instantiation time, defines an abstract method to extract the data. The class is specialized into ListOf-DecisionData to achieve all the advantages of the Composite design pattern, and into UserBased-DecisionData, which allows extraction of decision data from User objects. The latter is thus used as base class for the four types of decision data that can be extracted from a User object:
The use of a given type of DecisionData within the jEQN simulators is accomplished by use of the support hierarchy DecisionDataFactory, shown in Figure 2. The base class is declared and its services invoked within the simulation component but the actual implementation is set at coding time.
Figure 2. DecisionDataFactory hierarchy
The Policy hierarchy is shown in Figure 3.
Figure 3. jEQN Policy Framework
It defines a meta-model within which all the jEQN policies must be defined. Since the decisions can be taken upon input, both explicit (at decision time) and implicit (at definition time), and state data, the meta-model introduces a classification of the policies according to the use of the above three parameters and the type of decision to return:
The parameter T represents the type of the decision data used by the policy in object. It has to correspond to the method getDecisionData() return type for the actual implementation of DecisionData<T> interface. Since the Java 5 features and the Object root class property, the return type is allowed to be extended to any Java class.
Differently from the parameter T, which needs to be explicitly passed any time a decision has to be taken, the parameter I represents the implicit input to the policy, i.e. the data provided at policy instantiation time and then stored within the class itself.
The state parameter, S, models the policy’s internal state, which allows the policy to base its new decision upon the previous decisions.
D is the type of the decision, i.e. the type of result of the invocation of method getDecisionFor(). This type must be coherent with the type expected by the decision actuator, i.e. the simulation component.
The idea driving the hierarchy is to uniquely qualify each type of policy, in terms of their parameters, to allow an unequivocal classification of the policy to be implemented in jEQN.
The hierarchy roots with a Policy interface that plays only a mark role. The children interfaces introduce constraints on the parameters that are to be included in each of them. They mostly constitute a classification whose leaf classes, “-Dependent-” classes, are to be only used, as parent classes, for the implementation of jEQN policies.
At the second level of the hierarchy, there are the interfaces InputDependentPolicy and StateDependent-Policy. The mark interface InputDependentPolicy qualifies the policies that require input data to produce a decision. The StateDependentPolicy interface defines the basic services needed by policies that make use of internal state.
The third level includes the interfaces ExplicitInputDependentPolicy, ImplicitInputDepen-dentPolicy, InputAndStatePolicy and the class State-OnlyDependentePolicy. While the three interfaces introduce further, yet uncompleted, classification on the type of policy, the class StateOnlyDependentPolicy provides a concrete and fully classified type.
The ExplicitInputPolicy provides the base interface for policies that uses explicit input, i.e. the method getDecisionFor(t:T). Analogously, the ImplicitInputPolicy defines the accessory methods for the implicit input, which has to be set at policy instantiation time.
The InputAndStatePolicy is a mark interface to model policies that use both internal state data and input data. It is therefore an extension of the StatePolicy interface and the mark interface InputPolicy.
The leaf class StateOnlyDependentPolicy is the component to be used to implement policies that require only internal state data to produce the decision. This class declares the private field state, containing such data, and the getDecision() method, which does not need input parameters.
The following levels introduce further classifications upon these interfaces and are not discussed here.
The hierarchy here above introduced is meant to be a classification guideline for every policy in jEQN. Each policy must be first positioned in the above classification and then have its parameter type chosen.
Since jEQN components should not be concerned with the type of policy, the above (standard) hierarchy is supported by an extended hierarchy that turns upside down the standard hierarchy. This, together with its base class MaskBasePolicy, acts as a uniform container for any “-Dependent-” policy in jEQN. The MaskBasePolicy is a direct child of the class ImplicitAndExplicitInputAnd-StateDependentPolicy, which is the “widest”, in terms of parameters, in the standard hierarchy.
Although this double approach that introduces first generalization with the standard hierarchy and then specialization with the extended hierarchy, might seem awkward, it brings two advantages. The first, more theoretical, is that the jEQN policy developers are better driven in the classification of their policies because they are allowed to choose only among the leaf classes. The second, more practical, is that the jEQN components can fully constrain the type of policy as a function of their needs because this is imposed through the extended hierarchy.
Category 2 includes the following parameters:
As regards interarrival times, the abstract interface NumericStream, provided with a getNextNumber() method, can be used for decoupling the source logic from the type of sequence number passed.
In such way, an exponential source is automatically available by plugging an ExponentialNumericStream into the user source component. Similarly for any other probability distributions and for tracks of data loaded from files. All the components related to the NumericStream modeling were included in a separated package named jRand.
The parameter user generators can analogously be realized by defining a base interface, UserGenerator, to be used within the source. The interface can then be specialized and implemented for the possible types of generators. In particular, two types have been currently identified: SingleCategoryUserGenerator and Multi-CategoryUserGenerator.
The type SingleCategoryUserGenerator generates User-type users and assigns to them the given Category-type plus a conventional name.
The type MultiCategoryUserGenerator uses the Composite pattern and is defined as a set of SingleCategoryUserGenerator elements and a NumericStream for their selection. The NumericStream density function determines the probability of each category of users.
A similar approach can be adopted for the parameter service request generators, which is basically a positive sequence of numbers representing the time service requests.
Category 3 includes a single parameter, i.e., the queue structure to store the users. To obtain reusable components, the category 2’s approach can be used again. The component is based on the interface UserQueue, which declares enqueueing and dequeueing methods, and has the actual implementation set at model definition time.
Although the decoupling between the component and the parameter is carried out by use of the interface, a flexible structure has to be designed in order to allow the building of a wide range of queueing structures by simply assembling available components. With such aim, the hierarchy illustrated in Figure 4 has been specified and implemented.
Figure 4. Class Diagrams of User Queue Parameters
The hierarchy bases its main idea upon the facts that waiting system components perform only enqueueing and dequeueing requests and are not concerned with the underlying structure details. Therefore, the base interface, UserQueue, defines only the signatures of such services, which are then specialized for InfiniteUserQueue, FiniteUserQueue and MultiUserQueue.
Infinite-UserQueue is the basic building block that implements the interface, which represents a single queue with no capacity limits.
Other queue types can be obtained through it and the base interface. In fact, the finite single queue (FiniteUserQueue) is obtained by use of the Decoration pattern on the InfiniteUserQueue with the property maxQueueLength, while the MultiUserQueue is obtained by use of the Composition pattern over a set of single InfiniteUserQueue elements.
InfiniteUserQueue is in turn designed modularly. The following parameters can be specified:
As regards the physical structure, an abstract data structure interface is used within the InfiniteUserQueue component and its concrete implementation (e.g.: ArrayList, etc.) is to be set at declaration time. This choice has been operated to set the most appropriate data structure for the actual enqueuing policy chosen.
The second parameter, enqueueing policy, follows a similar approach. The most general enqueueing policy is defined as a MaskBasePolicy whose parameters are:
The enqueueing policy’s role is to determine an insertion point (Integer) in the current queue for a given user, and therefore is not concerned with the details of such decision. For this reason, this policy must be open to policies that might use any type of implicit input and state data.
A MultiUserQueues is defined as a composition of UserQueue elements to provide extended functionalities while keeping the uniform access interface of UserQueue. This component can be configured with the following parameters:
The QueueAssigner must be initialized with the dispatchingPolicy (category 1 parameter) and the decisionDataFactory for the policy decision data. The policy type is set to be a MaskBasePolicy because no restrictions exist on the use of the three parameters. These, however, must fit the following type constraints:
The policy is constrained only on the explicit type of input data (UserBased) and the type of decision, to be used to insert the given User.
The UserWithDrawer is the policy that is in charge of choosing the queue from which the next user has to be extracted. The policy does not impose any restriction on the use of the parameter and requires only the decision type, which must be of UserQueue type.
This section describes the main groups of jEQN entities, in terms of InputPorts, OutputPorts and behaviour (simulation logic).
The purpose of this group of components is to generate and insert new users into the system. Source component has an output port through which users can be sent to other jEQN entities.
The behaviour is as follows:
The base component can be parameterized by specifying proper implementation of:
The purpose of this group of components is to route users towards specific entities or ports. Currently, this group contains only the component Router. The ports of a Router component are:
The behaviour is as follows:
The Router component has a single parameter: the routing policy
The purpose of this group of components is to store users that can not be immediately processed by the service center. The simulation components in this group are two: PreemptiveWaitingSystem and NonPreemptiveWaitingSystem.
The behavior of the component NonPreemptiveWaitingSystem is as follows:
The behavior of the component PreemptiveWaitingSystem is as follows:
The ports of the component NonPreemptive-WaitingSystems are:
The ports of the component PreemptiveWaiting-System are:
The parameters of the component NonPreemptiveWaitingSystem are:
The parameters of the component PreemptiveWaitingSystem are the same of the non preemptive one with the addiction of:
The UserQueue is an abstract component that offers user insertion and extraction methods. It is specialized into FiniteUserQueue, InfiniteUserQueue and MultiUserQueue as shown in Figure 5.
Figure 5 UserQueue hierarchy
The component InfiniteUserQueue can be configured by specifying:
The component FiniteUserQueue can be configured by specifying:
The component MultiUserQueue can be configured by specifying:
The purpose of this group of components is to simulate the user processing. The components in this group are: NonPreemptiveServiceCenter, Preemptive-ServiceCenter and InfiniteServer.
The behavior of the component NonPreemptiveServiceCenter is as follows:
The behavior of the component PreemptiveServiceCenter is as follows:
The behavior of the component InfiniteServer is as follows:
The ports of the component NonPreemptiveServiceCenter are:
The ports of the component PreemptiveServiceCenter are:
The ports of the component InfiniteServer are:
The components of this group do not have parameters.