This document describes the Java implementation of EPICS v4 pvData.
For more information about the EPICS, please refer to the home page of the Experimental Physics and Industrial Control System.
This is the 14-Dec-2011 version of the Java implementation of pvData. It is a complete implementation of pvData as currently defined.
CONTENTSThis product is available via an open source license
This document is the project and package overviews for pvDataJava. The javaDOC is available at JavaDoc
pvData is one of a set of related projects. It describes and implements the data that the other projects support. Thus it is not useful by itself but understanding pvData is required in order to understand the other projects. The reader should also become familar with projects pvAccess and javaIOC, which are located via the same sourceforge site as this project.
pvData (Process Variable Data) defines and implements an efficent way to store, access, and transmit memory resident structured data.
The javaIOC implements a Process Variable (PV) Database, which is a memory resident database holding pvData, has the following features:
pvData was initially created to support the javaIOC and was part of the javaIOC project. It is now a separate project that is used by the javaIOC. In addition to the javaIOC, pvData is intended for use by 1) channel access clients, 2) Interface between client and network, 3) Interface between network and channel access server, 4) Interface between server and IOC database. Since it is an interface to data, it could also be used by other systems, e.g. TANGO, TINE, etc. A high level Physics application can hold data as pvData. By starting a channel access server, the data can made available to network clients.
pvData contains everything required to support Channel Access and Channel Access clients and servers.
This section describes a meta language for describing pvData. Currently there are no plans for a parser for the meta language. It is used for documentation. The toString introspection and data methods described below do present data in a format similar to the metadata syntax. The meta language is used to describe both introspection interfaces and data interfaces.
PVData supports structured data. All data appears as a top level structure. A structure has an ordered set of fields where each field has a fieldDef defined as follows:
type fieldName value // comment
where value is present for data objects and // indicates the the rest of the line is a comment.
type is one of the following:
structure fieldName
fieldDef
...
or
xxx_t fieldName
// if data object then following appear
fieldDef
...
For structure fieldName each
fieldDef must have a unique
fieldName within the structure For "xxx_t fieldName" xxx_t must be a
previously structure definition of the form:
structure xxx_t
...
structure[] fieldName
structureDef
...
or
xxx_t[] fieldName
Thus a structure array is an array where each element is a structure but all elements have the same introspection interface. For introspection the structureDef appears once without any data valuies.
The above is used to describe introspection objects. Data objects are describe in a similar way but each scalar field and each array field has data values. The definition of the data values depends on the type. For scalars the data value is whatever is valid for the type.
For scalar arrays the syntax is:
= [value,...,value]
where each value is a valid scalar data value depending on the type. Thus it is a comma separated set of values enclosed in [] White space is permitted surrounding each comma.
Define the following top level structure:
structure timeStamp_t
long secondsPastEpoch
int nanoSeconds
Then the following introspection objects can be defined:
structure scalarDoubleExample // introspection object
double value
timeStamp_t timeStamp
or
structure scalarDoubleExample // introspection object
double value
structure timeStamp
long secondsPastEpoch
int nanoSeconds
The following data objects can be defined:
structure scalarDoubleExample // data object
double value 1.0
timeStamp_t timeStamp
long secondsPastEpoch 0
int nanoSeconds 0
or
scalar arrayDoubleExample
double[] value [1.0,2.0]
structure timeStamp
long secondsPastEpoch 0
int nanoSeconds 0
If the following interface is defined:
structure point_t
double x
double y
Then the following introspection objects can be defined:
structure lineExample
point_t begin
point_t end
structure pointArrayExample
point_t[] points
or
structure lineExample
structure begin
double x
double y
structure end
double x
double y
structure pointArrayExample
structure[] points
structure point
double x
double y
And the following data objects can be defined:
structure lineExample
point_t begin
double x 0.0
double y 0.0
point_t end
double x 10
double y 10
structure pointArrayExample
point_t[] value
structure point
double x 0.0
double y 0.0
structure point
double x 10.0
double y 10.0
or
structure lineExample
structure begin
double x 0
double y 0
structure end
double x 10
double y 10
structure pointArrayExample
structure[] value
structure point
double x 0.0
double y 0.0
structure point
double x 10.0
double y 10.0
The following are the type definitions:
enum Type {
scalar,
scalarArray,
structure,
structureArray;
}
where
enum ScalarType {
pvBoolean,
pvByte,pvShort,pvInt,pvLong,
pvFloat,pvDouble,
pvString;
// The following are convenience methods
public boolean isInteger();
public boolean isNumeric();
public boolean isPrimitive();
public static ScalarType getScalarType(String type);
public String toString();
}
where
The complete set of introspection interfaces are:
interface Field {
String getFieldName();
Type getType();
void toString(StringBuilder buf);
void toString(StringBuilder buf,int indentLevel);
String toString();
}
interface Scalar extends Field{
ScalarType getScalarType();
}
interface ScalarArray extends Field{
ScalarType getElementType();
}
interface Structure extends Field{
Field getField(String fieldName);
int getFieldIndex(String fieldName);
Field[] getFields();
}
interface StructureArray extends Field{
Structure getStructure();
}
The introspection interfaces provide access to immutable objects. This allows introspection interfaces to be freely shared between data objects. For example the introspection interface for a timeStamp, which is a structure containing two fields, can be shared by every record that has a time stamp.
PVField is the base interface for a data field:
enum MessageType {info,warning,error,fatalError}
interface Requester {
String getRequesterName();
void message(String message, MessageType messageType);
}
interface SerializableControl {
void flushSerializeBuffer();
void ensureBuffer(int size);
}
interface Serializable {
void serialize(ByteBuffer buffer, SerializableControl flusher);
void deserialize(ByteBuffer buffer, DeserializableControl control);
}
interface SerializableArray extends Serializable {
void serialize(ByteBuffer buffer, SerializableControl flusher, int offset, int count);
}
interface PVAuxInfo {
PVField getPVField();
PVScalar createInfo(String key,ScalarType scalarType);
Map<String,PVScalar> getInfos();
PVScalar getInfo(String key);
void toString()StringBuilder buf;
void toString(StringBuilder buf,int indentLevel);
}
interface PVField extends Requester, Serializable {
int getFieldOffset();
int getNextFieldOffset();
int getNumberFields();
PVAuxInfo getPVAuxInfo(); //auxillary information
Field getField();
PVStructure getParent();
boolean isMutable();
void setImmutable();
void replacePVField(PVField newPVField);
void renameField(String newName);
void postPut(); // calls PVRecordField.postPut if this is a field of a record
void setPostHandler(PostHandler postHandler);
void toString(StringBuilder buf);
void toString(StringBuilder buf,int indentLevel);
String toString();
}
Each scalar type has an associated data interface: PVBoolean, PVByte, PVShort, PVInt, PVLong, PVFloat, PVDouble, and PVString. Each has a get and a put method. For example:
interface PVDouble extends PVScalar{
double get();
void put(double value);
}
PVArray is the base class for arrays.
interface PVArray extends PVField, SerializableArray {
int getLength();
void setLength(int length);
int getCapacity();
void setCapacity(int length);
boolean isCapacityMutable();q!
void setCapacityMutable(boolean isMutable);
}
nterface PVAuxInfo {
PVField getPVField();
PVScalar c
PVScalarArray is the base class for scalar arrays.
interface PVScalarArray extends PVArray {
ScalarArray getScalarArray();
}
For each scalar type an associated array data interface is defined. Each has a get and put method. For example:
public class DoubleArrayData {
public double[] data;
public int offset;
}
interface PVDoubleArray extends PVArray {
int get(int offset, int len, DoubleArrayData data);
int put(int offset,int len, double[] from, int fromOffset);
void shareData(double[] from);
}
PVScalarArray is the interface for an array of structures where each element has the same iontrospection interface.
public class StructureArrayData {
public PVStructure[] data;
public int offset;
}
interface PVStructureArray extends PVArray{
StructureArray getStructureArray();
int get(int offset, int length, StructureArrayData data);
int put(int offset,int length, PVStructure[] from, int fromOffset);
void shareData(PVStructure[] from);
}
PVStructure is the data interface for a structure.
interface BitSetSerializable {
void serialize(ByteBuffer buffer, SerializableControl flusher, BitSet bitSet);
void deserialize(ByteBuffer buffer, DeserializableControl control, BitSet bitSet);
}
interface PVStructure extends PVField , BitSetSerializable{
Structure getStructure();
PVField[] getPVFields();
PVField getSubField(String fieldName);
PVField getSubField(int fieldOffset);
void appendPVField(PVField pvField);
void appendPVFields(PVField[] pvFields);
void removePVField(String fieldName);
// The following are convenience methods
PVBoolean getBooleanField(String fieldName);
PVByte getByteField(String fieldName);
PVShort getShortField(String fieldName);
PVInt getIntField(String fieldName);
PVLong getLongField(String fieldName);
PVFloat getFloatField(String fieldName);
PVDouble getDoubleField(String fieldName);
PVString getStringField(String fieldName);
PVScalarArray getScalarArrayField(String fieldName);
PVStructureArray getStructureArrayField(String fieldName);
PVStructure getStructureField(String fieldName);
PVArray getArrayField(String fieldName,ScalarType elementType);
String getExtendsStructureName();
boolean putExtendsStructureName(String extendsStructureName);
}
The following interface creates introspection instances:
public interface FieldCreate {
Field create(String fieldName,Field field);
Scalar createScalar(String fieldName,ScalarType scalarType);
ScalarArray createScalarArray(String fieldName,ScalarType elementType);
StructureArray createStructureArray(String fieldName,Structure elementStructure);
Structure createStructure(String fieldName, Field[] field);
}
The following interface creates data instances:
public interface PVDataCreate {
PVField createPVField(PVStructure parent, Field field);
PVField createPVField(PVStructure parent,String fieldName,PVField fieldToClone);
PVScalar createPVScalar(PVStructure parent,Scalar scalar);
PVScalar createPVScalar(PVStructure parent,String fieldName,ScalarType fieldType);
PVScalar createPVScalar(PVStructure parent,String fieldName,PVScalar scalarToClone);
PVScalarArray createPVScalarArray(PVStructure parent,ScalarArray array);
PVScalarArray createPVScalarArray(PVStructure parent,String fieldName,ScalarType elementType);
PVScalarArray createPVScalarArray(PVStructure parent,String fieldName,PVScalarArray arrayToClone;
PVStructureArray createPVStructureArray(PVStructure parent,StructureArray structureArray);
PVStructure createPVStructure(PVStructure parent,Structure structure);
PVStructure createPVStructure(PVStructure parent,String fieldName,Field[] fields);
PVStructure createPVStructure(PVStructure parent,String fieldName,PVStructure structToClone);
PVField[] flattenPVStructure(PVStructure pvStructure);
}
An interface named Convert provides all reasonable conversions to/from pvData. See org.epics.pvData.pv.Convert for details.
This document describes everything via Java definitions. The initial implementation is in Java but the functionality could also be implemented in other languages such as C++.
pvData is distributed as a eclipse Java Project named pvData. This project consists of the following java packages:
The Java enum, interface, and class definitions that define pvData. This section provides a complete definition of what pvData is. This package completely describes how pvData is accessed.
Java Facilities for creating a PV Database and pvData. It provides everything required for creating pvData. It provides the following factories:
Although pvDataFactory can provide the implementation for all supported data types, often it is desirable to provide other implementations. To make it easy to create alternate implementations a set of abstract and base classes are supplied.
Provides a way to associated properties with a field.
The basic idea is to associate properties with any field named "value". All the fields in the structure that contains the value field are considered properties of value with the field name being the property name. See that package overview for details.
This package also provides support for "well known" field definitions like timeStamp, alarm, display,etc. Code that uses pvData can be simplified by using this support.
This package provides support that is used by pvData factories and might also be useful to software that uses pvData.
Provides the ability to monitor changes to an arbitrary subset of the fields in a record.
Access security is no implement but a discusssion of what it should be is provided.
This package has the complete set of enum, interface, and class definitions that describe PVData. The implementation is provided in package org.epics.pvData.factory.
A PVStructure is a field that contains an array of subfields. Each field has code for accessing the field. The interface for each field is PVField or an interface that extends PVField. Each field also has an introspection interface, which is Field or an extension of Field. This package overview describes the complete set of data and introspection interfaces for pvData.
This package also describes an interface Convert, which provides a rich set of methods for converting and copying data between field.
The interface FieldCreate creates the introspection interfaces. The interface PVDataCreate creates the PVField interfaces. Between them they provide the ability to create every type of Field and PVField, i.e. they provide a complete implemenation of pvData. It is also possible for other code to provide implementations.
The javaIOC provides a database of PVRecords where each PVRecord has a top level PVStructure. PVAccess provides network access to PVData.
Given a pvname, which consists of a record name and field name, it is possible to introspect the field without requiring access to data. The reflection and data interfaces are separate because the data may not be available. For example when a PVaccess client connects to a PV, the client library can obtain the reflection information without obtaining any data. Only when a client issues an I/O request will data be available. This separation is especially important for arrays and structures so that a client can discover the type without requiring that a large array or structure be transported over the network.
The types are defined by the Java definitions:
enum Type {
scalar,
scalarArray,
structure,
structureArray;
}
enum ScalarType {
pvBoolean,
pvByte, pvShort, pvInt, pvLong,
pvFloat,pvDouble,
pvString;
//Convenience methods
public boolean isInteger(); // pvByte,...,pvLong
public boolean isNumeric(); // pvByte,...pvDouble
public boolean isPrimitive(); // pvBoolean,...pvDouble
public static ScalarType getScalarType(String type);
public String toString();
}
The following interfaces are called by pvAccess for transporting data over the network. The abstract and base classes ensure that these methods are properly implemented.
interface Serializable {
void serialize(ByteBuffer buffer,SerializableControl flusher);
void deserialize(ByteBuffer buffer,DeserializableControl control);
}
interface SerializableControl {
void flushSerializeBuffer();
void ensureBuffer(int size);
}
interface DeserializableControl {
void ensureData(int size);
}
interface SerializableArray extends Serializable {
void serialize(ByteBuffer buffer, SerializableControl flusher, int offset, int count);
}
interface BitSetSerializable {
void serialize(ByteBuffer buffer, SerializableControl flusher, BitSet bitSet);
void deserialize(ByteBuffer buffer, DeserializableControl control, BitSet bitSet);
}
This section defines the complete set of Java PV reflection interfaces.
interface Field {
String getFieldName();
Type getType();
void toString(StringBuilder buf));
void toString(StringBuilder buf,int indentLevel);
String toString();
}
interface Scalar extends Field {
ScalarType getScalarType();
}
interface ScalarArray extends Field{
ScalarType getElementType();
}
interface Structure extends Field{
Field getField(String fieldName);
int getFieldIndex(String fieldName);
Field[] getFields();
}
interface StructureArray extends Field{
Structure getStructure();
}
interface FieldCreate {
Field create(String fieldName,Field field);
Scalar createScalar(String fieldName,ScalarType scalarType);
ScalarArray createScalarArray(String fieldName,ScalarType elementType);
StructureArray createStructureArray(String fieldName,Structure elementStructure);
Structure createStructure(String fieldName, Field[] fields);
}
The above definitions support the following:
Status provides a way to pass status back to client code. It is new and not currently used by pvData but may be in the future. It is used by code that uses pvData.
interface Status extends Serializable {
public enum StatusType {OK,WARNING,ERROR,FATAL};
StatusType getType();
String getMessage();
String getStackDump();
boolean isOK();
boolean isSuccess();
}
interface StatusCreate {
Status getStatusOK();
Status createStatus(StatusType type, String message, Throwable cause);
Status deserializeStatus(ByteBuffer buffer, DeserializableControl control);
}
The Status methods are:
The StatusCreate methods are:
This section defines the Java Interfaces for accessing the data within a PV record.
PVField is the base interface for accessing data. Every field of every structure of every record instance has a PVField associated with it. A structure and a record also has an associated PVField.
interface PVField extends Serializable, Requester {
int getFieldOffset();
int getNextFieldOffset();
int getNumberFields();
PVAuxInfo getPVAuxInfo();
boolean isImmutable();
void setImmutable();
Field getField();
PVStructure getParent();
void replacePVField(PVField newPVField);
void renameField(String newName);
void postPut();
void setRequester(Requester requester);
void setPostHandler(PostHandler postHandler);
String toString();
void toString(StringBuilder buf);
void toString(StringBuilder buf,int indentLevel);
}
where
A PVField extends Requester. Requester is present so that when database errors are found there is someplace to send a message. As will be seen below, PVRecord provides methods to register message requesters. Also a PVDatabase provides an identical method. Thus when a message is generated for a field it is propagated up to the record with the full field name attached and then propagated to the PVDatabase which sends the messages to the registered requesters.
enum MessageType {info,warning,error,fatalError}
interface Requester {
String getRequesterName();
void message(String message, MessageType messageType);
}
where
AuxInfo (Auxillary Information) is information about a field that is application specific. It will not be available outside the application that implements the database. In particular it will not be made available to Channel Access. It is used by the database itself to override the default implementation of fields. The JavaIOC uses it for attaching support code. Database Configuration and other tools can use it for configuration information. Each Field and each PVField can have have an arbitrary number of auxInfos. An auxInfo is a (key,PVScalar) pair where key is a string.
public interface PVAuxInfo {
PVField getPVField();
PVScalar createInfo(String key,ScalarType scalarType);
Map<String,PVScalar> getInfos();
PVScalar getInfo(String key);
void toString(StringBuilder buf);
void toString(StringBuilder buf,int indentLevel);
}
where
interface PVScalar extends PVField {
Scalar getScalar();
}
The interfaces for primitive data types are:
interface PVBoolean extends PVScalar {
boolean get();
void put(boolean value);
}
interface PVByte extends PVScalar {
byte get();
void put(byte value);
}
interface PVShort extends PVScalar {
short get();
void put(short value);
}
interface PVInt extends PVScalar {
int get();
void put(int value);
}
interface PVLong extends PVScalar {
long get();
void put(long value);
}
interface PVFloat extends PVScalar {
float get();
void put(float value);
}
interface PVDouble extends PVScalar {
double get();
void put(double value);
}
The interface for string is:
interface PVString extends PVScalar, SerializableArray {
String get();
void put(String value);
}
PVArray is the base interface for all the other PV Array interfaces. It extends PVField and provides the additional methods:
interface PVArray extends PVField, SerializableArray {
int getLength();
void setLength(int len);
int getCapacity();
void setCapacity(int len);
boolean isCapacityMutable();
void setCapacityMutable(boolean isMutable);
}
The interface for each array type has get and put methods which have the same arguments except for the data type. For example PVDoubleArray is:
public class DoubleArrayData {
public double[] data;
public int offset;
}
interface PVDoubleArray extends PVScalarArray {
int get(int offset, int len, DoubleArrayData data);
int put(int offset, int len, double[]from, int fromOffset);
void shareData(double[] from);
}
Get "exposes" it's internal array by setting data.data and data.offset. The caller is responsible for copying the array elements. This violates the principle that objects should not expose their internal data but is done for efficency. For example it makes it possible to copy between arrays with identical element types via a call to System.arraycopy without requiring an intermediate array.
Both get and put return the number of elements actually transfered. The arguments are:
The caller must be prepared to make multiple calls to retrieve or put an entire array. A caller should accept or put partial arrays. For example the following reads an entire array:
double[] getArray(PVDoubleArray pv)
{
int len = pv.getLength();
double[] storage = new double[len];
DoubleArrayData data = new DoubleArrayData();
int offset = 0;
while(offset < len) {
int num = pv.get(offset,(len-offset),data);
System.arraycopy(data.data,data.offset,storage,offset,num);
offset += num;
}
return storage;
}
shareData results in the PVArray using the primitive array that is passed to this method. This is most useful for immutable arrays. In this case the caller must set the PVArray to be immutable. In the PVArray is not immutable then it is the applications responsibility to coordinate access to the array. This violates the principle that objects should not expose their internal data but is important for immutable arrays. For example pvData and the javaIOC define many enumerated structures where an enumerated structure has twofields: index and choices. Choices is a PVStringArray that holds the enumerated choices. Index is a PVInt that is the index of the currently selected choice. The choices can be immutable. Allowing the choices internal String[] to be shared between all the instances of an enumerated structure saves on storage. Another reason for allowing shared data is so that an application which processes an array can be separated into multiple modules that directly access the internal data array of a PVArray. This can be required for minimizing CPU overhead. In this case it is the applications responsibility to coordinate access to the array.
interface PVScalarArray extends PVArray {
ScalarArray getScalarArray();
}
public class BooleanArrayData {
public boolean[] data;
public int offset;
}
interface PVBooleanArray extends PVScalarArray {
int get(int offset, int len, BooleanArrayData data);
int put(int offset, int len, boolean[]from, int fromOffset);
void shareData(boolean[] from);
}
public class ByteArrayData {
public byte[] data;
public int offset;
}
interface PVByteArray extends PVScalarArray {
int get(int offset, int len, ByteArrayData data);
int put(int offset, int len, byte[]from, int fromOffset);
void shareData(byte[] from);
}
public class ShortArrayData {
public short[] data;
public int offset;
}
interface PVShortArray extends PVScalarArray {
int get(int offset, int len, ShortArrayData data);
int put(int offset, int len, short[]from, int fromOffset);
void shareData(short[] from);
}
public class IntArrayData {
public int[] data;
public int offset;
}
interface PVIntArray extends PVScalarArray {
int get(int offset, int len, IntArrayData data);
int put(int offset, int len, int[]from, int fromOffset);
void shareData(int[] from);
}
public class LongArrayData {
public long[] data;
public int offset;
}
interface PVLongArray extends PVScalarArray {
int get(int offset, int len, LongArrayData data);
int put(int offset, int len, long[]from, int fromOffset);
void shareData(long[] from);
}
public class FloatArrayData {
public float[] data;
public int offset;
}
interface PVFloatArray extends PVScalarArray {
int get(int offset, int len, FloatArrayData data);
int put(int offset, int len, float[]from, int fromOffset);
void shareData(float[] from);
}
public class DoubleArrayData {
public double[] data;
public int offset;
}
interface PVDoubleArray extends PVScalarArray {
int get(int offset, int len, DoubleArrayData data);
int put(int offset, int len, double[]from, int fromOffset);
void shareData(double[] from);
}
public class StringArrayData {
public String[] data;
public int offset;
}
interface PVStringArray extends PVScalarArray {
int get(int offset, int len, StringArrayData data);
int put(int offset, int len, String[]from, int fromOffset);
void shareData(String[] from);
}
public class StructureArrayData {
public PVStructure[] data;
public int offset;
}
interface PVStructureArray extends PVArray {
StructureArray getStructureArray();
int get(int offset, int length, StructureArrayData data);
int put(int offset,int length, PVStructure[] from, int fromOffset);
void shareData(PVStructure[] from);
}
Notes about PVStructureArray: A client can only access the data in the elements of the array via the get and put methods, i.e. it is not possible to access subfields indirectly. PVStructureArray.getNumberFields() returns 1, i.e. the field looks like a leaf field.
The interface for a structure is:
interface PVStructure extends PVField, BitSetSerializable {
Structure getStructure();
PVField[] getPVFields();
PVField getSubField(String fieldName);
PVField getSubField(int fieldOffset);
void appendPVField(PVField pvField);
void appendPVFields(PVField[] pvFields);
void removePVField(String fieldName);
// the following are convenience methods
PVBoolean getBooleanField(String fieldName);
PVByte getByteField(String fieldName);
PVShort getShortField(String fieldName);
PVInt getIntField(String fieldName);
PVLong getLongField(String fieldName);
PVFloat getFloatField(String fieldName);
PVDouble getDoubleField(String fieldName);
PVString getStringField(String fieldName);
PVStructure getStructureField(String fieldName);
PVScalarArray getScalarArrayField(String fieldName,ScalarType elementType);
PVStructureArray getStructureArrayField(String fieldName);
String getExtendsStructureName();
boolean putExtendsStructureName(String extendsStructureName);
}
where
PVDataCreate is an interface that provides methods that create PVField interfaces. A factory is provided that creates PVDataCreate.
interface PVDataCreate {
PVField createPVField(PVStructure parent, Field field);
PVField createPVField(PVStructure parent,String fieldName,PVField fieldToClone);
PVScalar createPVScalar(PVStructure parent,Scalar scalar);
PVScalar createPVScalar(PVStructure parent,String fieldName,ScalarType fieldType);
PVScalar createPVScalar(PVStructure parent,String fieldName,PVScalar scalarToClone);
PVScalarArray createPVScalarArray(PVStructure parent,ScalarArray array);
PVScalarArray createPVScalarArray(PVStructure parent,String fieldName,ScalarType elementType);
PVScalarArray createPVScalarArray(PVStructure parent,String fieldName,PVScalarArray arrayToClone);
PVStructureArray createPVStructureArray(PVStructure parent,StructureArray structureArray);
PVStructure createPVStructure(PVStructure parent,
Structure structure);
PVStructure createPVStructure(PVStructure parent,String fieldName,
Field[] fields);
PVStructure createPVStructure(PVStructure parent,String fieldName,
PVField[] pvFields);
PVStructure createPVStructure(PVStructure parent,String fieldName,
PVStructure structToClone);
PVField[] flattenPVStructure(PVStructure pvStructure);
}
where
NOTE about copying immutable array fields. If an entire immutable array field is copied to another array that has the same elementType, both offsets are 0, and the length is the length of the source array, then the shareData method of the target array is called and the target array is set immutable. Thus the source and target share the same primitive array.
This section describes the supported conversions between data types.
interface Convert {
void getFullFieldName(StringBuilder builder,PVField pvField)
void getString(StringBuilder buf,PVField pv, int indentLevel);
void getString(StringBuilder buf,PVField pv);
void fromString(PVScalar pv,String from);
void fromString(PVScalarArray pv,String from);
int fromStringArray(PVScalarArray pv,
int offset, int len, String[]from, int fromOffset);
int toStringArray(PVScalarArray pv,
int offset, int len, String[]to, int toOffset);
boolean isCopyCompatible(Field from, Field to);
void copy(PVField from,PVField to);
boolean isCopyScalarCompatible(Field from, Field to);
void copyScalar(PVField from, PVField to);
boolean isCopyScalarArrayCompatible(ScalarArray from, ScalarArray to);
int copyScalarArray(PVScalarArray from, int offset,
PVScalarArray to, int toOffset, int len);
boolean isCopyStructureCompatible(Structure from, Structure to);
void copyStructure(PVStructure from, PVStructure to);
boolean isCopyStructureArrayCompatible(StructureArray from, StructureArray to);
void copyStructureArray(PVStructureArray from, PVStructureArray to);
// For the following the pv Type must be PVByte, ...., PVDouble
byte toByte(PVField pv);
short toShort(PVField pv);
int toInt(PVField pv);
long toLong(PVField pv);
float toFloat(PVField pv);
double toDouble(PVField pv);
String toString(PVScalar pv);
void fromByte(PVField pv, byte from);
void fromShort(PVField pv, short from);
void fromInt(PVField pv, int from);
void fromLong(PVField pv, long from);
void fromFloat(PVField pv, float from);
void fromDouble(PVField pv, double from);
// For the following the element type must be pvByte, ...., pvDouble
int toByteArray(PVScalarArray pv,
int offset, int len, byte[]to, int toOffset);
int toShortArray(PVScalarArray pv,
int offset, int len, short[]to, int toOffset);
int toIntArray(PVScalarArray pv,
int offset, int len, int[]to, int toOffset);
int toLongArray(PVScalarArray pv,
int offset, int len, long[]to, int toOffset);
int toFloatArray(PVScalarArray pv,
int offset, int len, float[]to, int toOffset);
int toDoubleArray(PVScalarArray pv,
int offset, int len, double[]to, int toOffset);
int fromByteArray(PVScalarArray pv,
int offset, int len, byte[]from, fromOffset);
int fromShortArray(PVScalarArray pv,
int offset, int len, short[]from, fromOffset);
int fromIntArray(PVScalarArray pv,
int offset, int len, int[]from, fromOffset);
int fromLongArray(PVScalarArray pv,
int offset, int len, long[]from, fromOffset);
int fromFloatArray(PVScalarArray pv,
int offset, int len, float[]from, fromOffset);
int fromDoubleArray(PVScalarArray pv,
int offset, int len, double[]from, fromOffset);
void newLine(StringBuilder builder, int indentLevel);
}
The array methods all return the number of elements copied or converted. This can be less than len if the PVField array contains less than len elements.
newLine is a convenience method for code that implements toString It generates a newline and inserts blanks at the beginning of the newline.
The getString methods dump the data in the metadata syntax described in the pvData project overview. Note that the toString methods of PVField are implemented by calling these convert methods.
Assume that code wants to access two fields from a PVStructure:
The following code uses introspection to get the desired information.
String getValueAndTimeStamp(PVStructure pvStructure) {
PVField valuePV = pvStructure.getSubField("value");
if(valuePV==null) {
return "value field not found";
}
if(valuePV.getField.getType!=Type.scalar) {
return "value field is not a scalar";
}
Scalar scalar = (Scalar)valuePV.getField();
if(scalar.getScalarType!=ScalarType.pvDouble) {
return "value field is not a double";
}
PVDouble pvDouble = (PVDouble)valuePV;
PVField timeStampPV = pvStructure.getSubField("timeStamp");
if(timeStampPV==null) {
return "timeStamp field not found";
}
double value = valuePV.get();
return value + " timeStamp " + timeStampPV.toString();
}
Example of creating a scalar field.
PVDataCreate pvDataCreate = PVDataFactory.getPVDataCreate();
PVDouble pvValue = pvDataCreate.createPVScalar(null,"value",ScalarType.pvDouble);
Create a structure.
FieldCreate fieldCreate = FieldFactory.getFieldCreate();
PVDataCreate pvDataCreate = PVDataFactory.getPVDataCreate();
PVStructure create() {
Field[] fields = new Field[2];
fields[0] = fieldCreate.createScalar("secondsSinceEpoch",ScalarType.pvLong);
fields[1] = fieldeCreate.createScalar("nanoSeconds",ScalarType.pvInt);
PVStructure pvStructure = pvDataCreate.createPVStructure(
null,"timeStamp",fields);
return(pvStructure);
}
Create a PVStructure that has a value field and a timeStamp. Use the structure created in the previous example.
Field[] fields = new Field[2];
fields[0] = fieldCreate.createScalar("value",ScalarType.pvDouble);
PVStructure pvStructure = create();
fields[1] = fieldCreate.createStructure("timeStamp",pvStructure.getStructure.getFields();
PVStructure pvStruct = pvDataCreate.createPVStructure(null,"myStructure",fields);
This package provides factories and classes to implement everything defined in package org.epics.pvData.pv
public final class FieldFactory {
public static FieldCreate getFieldCreate();
}
FieldFactory automatically creates a single instance of FieldCreate and provides a method to get the interface.
public class PVDataFactory {
public static PVDataCreate getPVDataCreate();
}
PVDataFactory automatically creates a single instance of PVDataCreate and provides a method to get the interface.
public final class ConvertFactory {
public static Convert getConvert();
}
ConvertFactory automatically creates a single instance of Convert and provides a method to get the interface.
public final class StatusFactory {
public static StatusCreate getStatusCreate();
}
This provides a singleton that implements StatusCreate.
This section describes base classes for implementing Field instances , i.e. introspection instances. For many applications they provide a complete introspection implementation but they could be extended if necessary.
public class BaseField implements Field {
public BaseField(String fieldName, Type type);
// all Field methods
}
public class BaseScalar extends BaseField implements Scalar {
public BaseScalar(String fieldName,ScalarType scalarType);
// all Scalar methods
}
public class BaseStructure extends BaseField implements Structure {
public BaseStructure(String fieldName,Field[] field);
// all Structure methods
}
public class BaseStructureScalar extends BaseScalar implements StructureScalar {
public BaseStructureScalar(String fieldName,Field[] fields);
// all StructureScalar methods.
}
public class BaseScalarArray extends BaseField implements ScalarArray {
public BaseScalarArray(String fieldName,ScalarType elementType);
// all ScalarArray methods
}
public class BaseStructureArray extends BaseArray implements StructureArray {
public BaseStructureArray(String fieldName,Field[] fields);
// all StructureArray methods
}
}
This is a complete implementation of PVAuxInfo.
public class BasePVAuxInfo implements PVAuxInfo {
public BasePVAuxInfo(PVField pvField);
// All PVAuxInfo methods
}
This is an abstract base class for implementing PVField interfaces. It MUST be the base class for any class that extends PVField.
public abstract class AbstractPVField implements PVField{
protected static Convert convert = ConvertFactory.getConvert();
protected AbstractPVField(PVField parent, Field field);
protected void replaceStructure();
// All public methods of PVField
}
The public methods are described in package org.epics.pvData.pv. The protected methods are:
The following is a base class for any code that implements PVStructure. Any code that implements PVStructure MUST extend this class.
public class BasePVStructure extends AbstractPVField implements PVStructure
public BasePVStructure(PVField parent, Structure structure);
// all public methods of PVStructure
}
The following is an abstract base class for implementing PVScalar. It MUST be the base class for any code that implements an extension of PVScalar.
public class AbstractPVScalar extends AbstractPVField implements PVScalar {
protected AbstractPVScalar(PVStructure parent, Scalar scalar);
public Scalar getScalar();
}
public class BasePVBoolean extends AbstractPVScalar implements PVBoolean
{
protected boolean value = false;
public BasePVBoolean(PVStructure parent,Scalar scalar);
// public methods of PVBoolean
}
public class BasePVByte extends AbstractPVScalar implements PVByte
{
protected byte value;
public BasePVByte(PVStructure parent,Scalar scalar);
// public methods of PVByte
}
public class BasePVShort extends AbstractPVScalar implements PVShort
{
protected short value;
public BasePVShort(PVStructure parent,Scalar scalar);
// public methods of PVShort
}
public class BasePVInt extends AbstractPVScalar implements PVInt
{
protected int value;
public BasePVInt(PVStructure parent,Scalar scalar);
// public methods of PVInt
}
public class BasePVLong extends AbstractPVScalar implements PVLong
{
protected long value;
public BasePVLong(PVStructure parent,Scalar scalar);
// public methods of PVLong
}
public class BasePVFloat extends AbstractPVScalar implements PVFloat
{
protected float value;
public BasePVFloat(PVStructure parent,Scalar scalar);
// public methods of PVFloat
}
public class BasePVDouble extends AbstractPVScalar implements PVDouble
{
protected double value;
public BasePVDouble(PVStructure parent,Scalar scalar);
// public methods of PVDouble
}
public class BasePVString extends AbstractPVScalar implements PVString
{
protected String value;
public BasePVString(PVStructure parent,Scalar scalar);
// public methods of PVString
}
The following is a abstract class for creating array fields. Any code that implements any extension of PVArray must extend this class.
public abstract class AbstractPVArray extends AbstractPVField implements PVArray{
protected int length = 0;
protected int capacity;
protected boolean capacityMutable = true;
protected AbstractPVArray(PVField parent,Array array);
abstract public void setCapacity(int capacity);
// public methods of PVArray
}
For each ScalarType there is a base class for implementing the corresponding array type. For most uses it is not necessary to extend these classes.
public class BasePVBooleanArray extends AbstractPVArray implements PVBooleanArray
{
protected boolean[] value;
// implements all PVBooleanArray methods
}
public class BasePVByteArray extends AbstractPVArray implements PVByteArray
{
protected byte[] value;
// implements all PVByteArray methods
}
public class BasePVShortArray extends AbstractPVArray implements PVShortArray
{
protected short[] value;
// implements all PVShortArray methods
}
public class BasePVIntArray extends AbstractPVArray implements PVIntArray
{
protected int[] value;
// implements all PVIntArray methods
}
public class BasePVLongArray extends AbstractPVArray implements PVLongArray
{
protected long[] value;
// implements all PVLongArray methods
}
public class BasePVFloatArray extends AbstractPVArray implements PVFloatArray
{
protected float[] value;
// implements all PVFloatArray methods
}
public class BasePVDoubleArray extends AbstractPVArray implements PVDoubleArray
{
protected double[] value;
// implements all PVDoubleArray methods
}
public class BasePVStringArray extends AbstractPVArray implements PVStringArray
{
protected String[] value;
// implements all PVStringArray methods
}
public class BasePVStructureArray extends AbstractPVArray implements PVStructureArray
{
// implements all PVStructureArray methods
}
Only fields named "value" have properties. A record can have multiple value fields, which can appear in the top level structure of a record or in a substructure. All other fields in the structure containing a value field are considered properties of the value field. The fieldname is also the property name. The value field can have any type, i.e. scalar, scalarArray, or structure. Typical property fields are timeStamp, alarm, display, control, and history. The timeStamp is a special case. If it appears anywhere in the structure hieraracy above a value field it is a property of the value field.
For example the following top level structure has a single value field. The value field has properties alarm, timeStamp, and display.
structure counterOutput
structure alarm
double value
structure display
string description "Sample Description"
string format "%f"
string units volts
structure limit
double low 0.0
double high 10.0
The following example has three value fields each with properties alarm and timeStamp. Voltage, Current, and Power each have a different alarms but all share the timeStamp.
structure powerSupplyValueStructure
double value
structure alarm
structure powerSupplySimple
structure alarm
structure timeStamp
powerSupplyValueStructure voltage
powerSupplyValueStructure power
powerSupplyValueStructure current
The following field names have special meaning, i.e. support properties for general purpose clients.
In addition a structure can have additional fields that support the value field but are not recognized by most general purpose client tools. Typical examples are:
The model allows for device records. A device record has structure fields that that support the PVData data model. For example a powerSupport record can have fields power, voltage, current that each support the PVData data model.
Interface and factory for finding a field within a structure.
interface PVProperty {
PVField findProperty(PVField pvField,String fieldName);
PVField findPropertyViaParent(PVField pvField,String propertyName);
String[] getPropertyNames(PVField pvField);
}
public class PVPropertyFactory {
public static PVProperty getPVProperty();
}
This section has structure definitions that support standard properties. These definitions are defined in project javaIOC.
A timeStamp is represented by the following structure
structure timeStamp
int64 secondsPartEpoch
int32 nanoSeconds
int32 userTag
The Epoch is the posix epoch, i.e. Jan 1, 1970 00:00:00 UTC. Both the seconds and nanoSeconds are signed integers and thus can be negative. Since the seconds is kept as a 64 bit integer, it allows for a time much greater than the present age of the universe. Since the nanoSeconds portion is kept as a 32 bit integer it is subject to overflow if a value that corresponds to a value that is greater than a little more than 2 seconds of less that about -2 seconds. The support code always adjust seconds so that the nanoSecconds part is normalized, i. e. it has is 0<=nanoSeconds<nanoSecPerSec..
The Java definition of a timeStamp is:
interface TimeStamp {
static final long milliSecPerSec = 1000;
static final long microSecPerSec = milliSecPerSec*milliSecPerSec;
static final long nanoSecPerSec = milliSecPerSec*microSecPerSec;
static final long posixEpochAtEpicsEpoch = 631152000;
void normalize();
long getSecondsPastEpoch();
long getEpicsSecondsPastEpoch();
int getNanoSeconds();
int getUserTag();
void setUserTag(int userTag);
void put(long secondsPastEpoch,int nanoSeconds);
long getMilliSeconds();
void put(long milliSeconds);
void getCurrentTime();
boolean equals(TimeStamp other);
boolean lt(TimeStamp other);
boolean le(TimeStamp other);
void add(long seconds);
void add(double seconds);
double diff(TimeStamp a,TimeStamp b);
}
where:
The TimeStamp class provides arithmetic and comparison methods for time stamps. The result is always kept in normalized form, which means that the nano second portion is 0≤=nano<nanoSecPerSec. Note that it is OK to have timeStamps for times previous to the epoch.
interface PVTimeStamp {
boolean attach(PVField pvField);
void detach();
boolean isAttached();
void get(TimeStamp timeStamp);
boolean set(TimeStamp timeStamp);
}
class PVTimeStampFactory implements PVTimeStamp {
public static PVTimeStamp create();
}
where
An alarm structure is defined as follows:
structure alarm
int32 severity
String message
Note that severity is NOT defined as an enumerated structure. The reason is performance, i. e. prevent passing the array of choice strings everywhere. The file alarm.h provides the choice strings. Thus all code that needs to know about alarms share the exact same choice strings.
The java definitions for alarm are:
enum AlarmSeverity {
NONE,MINOR,MAJOR,INVALID,UNDEFINED;
public static AlarmSeverity getSeverity(int value);
private static final String[] alarmSeverityNames;
public static String[] getSeverityNames() { return alarmSeverityNames;}
}
enum AlarmStatus {
NONE,DEVICE,DRIVER,RECORD,DB,CONF,UNDEFINED,CLIENT;
public static AlarmStatus getStatus(int value);
private static final String[] alarmStatusNames;
public static String[] getStatusNames() { return alarmStatusNames;}
}
class Alarm {
public Alarm();
public String getMessage();
public void setMessage(String message);
public AlarmSeverity getSeverity();
public void setSeverity(AlarmSeverity alarmSeverity);
public AlarmStatus getStatus();
public void setStatus(AlarmStatus alarmStatus);
}
Alarm Severity defines the possible alarm severities
AlarmSeverity has the methods:
AlarmStatus has the methods:
Alarm has the methods:
interface PVAlarm {
boolean attach(PVField pvField);
void detach();
boolean isAttached();
void get(Alarm alarm);
boolean set(Alarm alarm);
}
class PVAlarmFactory implements PVAlarm{
public static PVAlarm create();
}
where
Control information is represented by the following structure
structure control
structure limit
double low
double high
The java definition for Control is:
class Control {
Control();
double getLow();
double getHigh();
void setLow(double value);
void setHigh(double value);
}
where
In addition the following is used to access an alarm structure.
interface PVControl {
boolean attach(PVField pvField);
void detach();
boolean isAttached();
void get(Control control);
boolean set(Control control);
}
class PVControlFactory{
public static PVControl create();
}
where
Display information is represented by the following structure
structure display
structure limit
double low
double high
string description
string format
string units
The Java definition for display is:
class Display {
Display();
double getLow();
double getHigh();
void setLow(double value);
void setHigh(double value);
String getDescription();
void setDescription(String value);
String getFormat();
void setFormat(String value);
String getUnits();
void setUnits(String value);
}
where
In addition the following is used to access a control structure.
interface PVDisplay {
boolean attach(PVField pvField);
void detach();
boolean isAttached();
void get(Display display);
boolean set(Display display);
}
class PVDisplayFactory implements PVDisplay{
public static PVDisplay create()
}
where
An enumerated structure is a structure that has fields:
structure
int32 index
string[] choices
The following is the Java code for accessing an enumerated structure.
interface PVEnumerated {
boolean attach(PVField pvField);
void detach();
boolean isAttached();
boolean setIndex(int index);
int getIndex();
String getChoice();
boolean choicesMutable();
String[] getChoices();
boolean setChoices(String[] choices);
}
class PVEnumeratedFactory{
PVEnumerated create();
}
where
This package provides utility code:
NOTE: This is not currently used by anything.
A MultiChoice consists of an array of strings and a bot set that selects an arbitrary set of the choices.
public interface MultiChoice {
interface Choices {
String[] getChoices();
int getNumberChoices();
}
byte[] getBitMask();
String[] getChoices();
Choices getSelectedChoices();
void setBit(int index);
void clear();
int registerChoice(String choice);
}
public class MultiChoiceFactory{
public static MultiChoice getMultiChoice(PVField pvField);
}
where
This is an implementation of BitSet that supports serialization, which the standard Jave implementation does not allow.
The following is also provided:
interface BitSetUtil {
boolean compress(BitSet bitSet,PVStructure pvStructure);
}
class BitSetUtilFactory {
public static BitSetUtil getCompressBitSet();
}
This provides functions that operate of a BitSet for a PVStructure. It currently has only one method:
public class MessageNode {
public String message;
public MessageType messageType;
}
public interface MessageQueue {
MessageNode get();
boolean put(String message,MessageType messageType);
boolean isEmpty();
boolean isFull();
int getClearOverrun();
}
public class MessageQueueFactory {
public static MessageQueue create(int size);
}
This is for use by code that wants to handle messages without blocking higher priority threads.
A messageNode is a class with two public data members:
A messageQueue is an interface with methods:
MessageQueueFactory provides the public method:
public enum ThreadPriority {
lowest,
lower,
low,
middle,
high,
higher,
highest;
public static final int[] javaPriority;
public int getJavaPriority();
public static int getJavaPriority(ThreadPriority threadPriority);
}
interface ThreadReady {
void ready();
}
interface RunnableReady {
void run(ThreadReady threadReady);
}
interface ThreadCreate {
Thread create(String name, int priority, RunnableReady runnableReady);
Thread[] getThreads();
}
public class ThreadCreateFactory {
public static ThreadCreate getThreadCreate();
}
ThreadCreate provides two features:
An Executor is a thread that can execute any object that implements the Java Runnable interface. The user can request that a single command be executed. If the command is already in the list of commands to be executed it is NOT added to the list when add is called.
public interface ExecutorNode {}
public interface Executor {
ExecutorNode createNode(Runnable command);
void execute(ExecutorNode executorNode);
void stop();
}
public class ExecutorFactory {
static public Executor create(String name,ScanPriority priority);
}
where
TimeFunction is a facility that measures the average number of seconds a function call requires. When timeCall is called, it calls function in a loop. It starts with a loop of one iteration. If the total elapsed time is less then .1 seconds it increases the number of iterrations by a factor of 10. It keeps repeating until the elapsed time is greater than .1 seconds. It returns the average number of seconds per call.
public interface TimeFunctionRequester {
void function();
}
public interface TimeFunction {
double timeCall();
}
public class TimeFunctionFactory {
public static TimeFunction create(TimeFunctionRequester requester);
}
LinkedList implements a double linked list that requires a user to allocate the nodes. It is more efficent that ArrayList for implementing stacks and queues. For lists that are traversed while new elements can be added or removed, LinkedListArray provides a way to get an array of the currrent elements without allocating a new array each time the array is traversed.
LinkedListArray converts a LinkedList to an LinkNode array. The implementation provided by LinkedListFactory only creates a new LinkNode array w.en the number of elements in the linkedList passed to setNodes is greater than the length of the current LinkNode array.
public interface LinkedListNode<T> {
public T getObject();
boolean isOnList();
}
public interface LinkedList {
void addTail(LinkedListNode<T> listNode);
void addHead(LinkedListNode<T> listNode);
void insertAfter(LinkedListNode<T> listNode,LinkedListNode<T> addNode);
void insertBefore(LinkedListNode<T> listNode,LinkedListNode<T> addNode);
LinkedListNode<T> removeTail();
LinkedListNode<T> removeHead();
void remove(LinkedListNode<T> listNode);
void remove(T object);
LinkedListNode<T> getHead();
LinkedListNode<T> getTail();
LinkedListNode<T> getNext(LinkedListNode<T> listNode);
LinkedListNode<T> getPrev(LinkedListNode<T> listNode);
boolean isEmpty();
boolean contains(T object);
}
public interface LinkedListArray<T> {
void setNodes(LinkedList<T> linkedList);
LinkedListNode<T>[] getNodes();
int getLength();
void clear();
}
public class LinkedListCreate<T> {
public static LinkedList<T> create();
public static LinkedListNode<T> createNode(Object object);
public static LinkedListArray<T> createArray();
}
LinkedListCreate is created as follows:
LinkedListCreate<SomeObject> linkedListCreate = new LinkedListCreate<SomeObject>();
Then a linked list can be created as follows:
LinkedList<SomeObject> linkedList = linkedListCreate.create();
The only way a node can be allocated is by calling linkedListCreate.createNode(SomeObject object). The object passed to createNode is what is returned by LinkedList.getObject. For example:
private static class SomeObject {
private LinkedListNode<SomeObject> listNode = null;
// other definitions
private Node(/* arguments*/) {
listNode = linkedListCreate.createNode(this);
}
LinkedListNode<SomeObject> getListNode() {return listNode};
}
// then
SomeObject someObject = new SomeObject(/* args */);
linkedList.addTail(someObject);
A node can only be on one list at a time but can be put, at different times, on different lists as long as they all hold the same type of objects.
The class does not provide a lock but the user can synchronized via calls like:
synchronized(linkedList) {
linkedList.addTail(listNode);
}
The following is an example of how to use LinkedListArray.
LinkedListNode<SomeObject>[] nodes = null;
int length = 0;
synchronized(linkedList) {
linkedListArray.setNodes(linkedList);
nodes = linkedListArray.getNodes();
length = linkedListArray.getLength();
}
for(int i=0; i<length; i++) {
SomeObject object = nodes[i].getObject();
// do something with object
}
This provides a general purpose timer. It provides the following features not provided by java.util.Timer and java.util.TimerTask:
interface Timer {
interface TimerCallback {
void callback();
void timerStopped();
}
interface TimerNode {
void cancel();
boolean isScheduled();
}
void scheduleAfterDelay(TimerNode timerNode,double delay);
void schedulePeriodic(TimerNode timerNode,double delay,double period);
void stop();
}
class TimerFactory {
static public Timer create(String threadName, ThreadPriority priority);
static public TimerNode createNode(TimerCallback timerCallback);
}
This is an interface that must be implemented by the user. It has the following methods:
This is an interface implemented by TimerFactory. It is allocated by calling TimerFactory.createNode. It is passed as an argument when scheduling a callback. It has the single method:
This is the interface for scheduling a timer callback. A timer is created by calling TimerFactory.create. It has the methods:
This is the factory that implements the Timer and TimerNode interfaces. It has the methods:
This is adapted from the java.util.BitSet. It adds serializable. See java.util.BitSet for a description. This implementation adds the following additional methods:
public boolean getAndSet(int bitIndex);
public void set(BitSet src);
public void or_and(BitSet set1, BitSet set2);
where
This provides a queue which has an immutable capacity, which is specified when the queue is created. When the queue is full the user code is expected to keep using the current el;ement until a new free element becomes avalable. This is used by pvData.monitor.
public class QueueCreate<T> {
public Queue<T> create(QueueElement<T>[] queueElements) {
return new QueueImpl<T>(queueElements);
}
public QueueElement<T> createQueueElement(T object) {
return new QueueElementImpl<T>(object);
}
}
public interface QueueElement<T> {
public T getObject();
}
public interface Queue<T> {
void clear();
int getNumberFree();
int capacity();
QueueElement<T> getFree();
void setUsed(QueueElement<T> queueElement);
QueueElement<T> getUsed();
void releaseUsed(QueueElement<T> queueElement);
}
A queueCreate instance is created via a call like the following:
QueueCreate<MyObject> queueCreate = new QueueCreate<MyObject>();
Once a queueCreate is available a queue instance is created via code like the following:
Queue<MyObject> queue create(MyObject[] myObjects) {
QueueElement<MyObject>[] queueElements = new QueueElement[length];
for(int i=0; i<length; i++) {
QueueElement<MonitorElement> queueElement =
queueCreate.createQueueElement(myObjects[i);
queueElements[i] = queueElement;
}
return queueCreate.create(queueElements);
}
The queue methods are:
A producer calls getFree and setUsed via code like the following:
MyObject getFree() {
QueueElement<MyObject> queueElement = queue.getFree();
if(queueElement==null) return null;
return queueElement.getObject();
}
A consumer calls getUsed and releaseUsed via code like the following:
while(true) {
QueueElement<MyObject> queueElement = queue.getUsed();
if(queueElement==null) break;
MyObject myObject = queueElement.getObject();
// do something with myObject
queue.releaseUsed(queueElement);
}
This is a base interface used by many other interfaces.
interface Destroyable {
void destroy();
}
where
This is a helper class for serialization, which is required for sending and receiving pvData over the nerwork.
class SerializeHelper {
static void writeSize(final int s, ByteBuffer buffer);
static int readSize(ByteBuffer buffer);
static void serializeString(final String value, ByteBuffer buffer);
static void serializeSubstring(final String value,
int offset, int length, ByteBuffer buffer);
static String deserializeString(ByteBuffer buffer);
}
where
Project javaIOC provides the ability to monitor changes to an arbitrary subset of the fields in a record. The next section describes how a client specifies monitor options. The rest of this package description is of interest to developers. Although this package does not implement monitors it does describe what the client and what pvAccess needs to know about monitors.
A client can monitor an arbitrary subset of the fields in a record instance. Each field has an associated monitor algorithm which decides if changes to that field should cause a monitor. The algorithms defined by this package are: onPut, onChange, and deadband. The set of algorithms is extensible.
The basic interface for monitoring is interface Monitor, which has four methods: start, stop, poll, and release. Code that implements the client side for monitors has code like the following:
//somewhere the following request is issued
monitor.start();
//somewhere the following is done:
while(true) {
MonitorElement monitorElement = monitor.poll();
if(monitorElement==null) {
// no elements in the queue. Do something.
} else {
// got a monitor. Handle the element and then
monitor.release(monitorElement);
}
}
This package provides the following support for monitors:
Developers who need to understand or implement monitor algorithms will be interested in this package. For example it is used by pvAccess to implement monitors. The interfaces can be implemented by other servers, for example the javaIOC (org.epics.ioc.caV3) implements a version of interface Monitor that links to a version 3 EPICS IOC.
This package defines the following monitor algorithms:
The monitor queue implementation provides support for the following queue sizes greater than or equal to two.
This section describes that monitor support provided by project javaIOC. Two issues are discussed: record options and field options.
The options that apply to an entire record are:
The following options are supported for all fields:
For each field the following monitor algorithms are supported:
The examples will use the syntax supported by calling:
PVStructure pvRequest = org.epics.ca.client.CreateRequestFactory.createRequest(request);
The request string has the following:
"record[option=value,...]field(fieldDef,...)"
The standard record options are:
Each fieldDef is either of the form:
fullRecordName
Where fullRecordName is the full name of the field in the record. The name in the structure the client will get is the field name from the record. Or fieldDef has the form:
fieldName{fieldDef,...}
Each fieldDef is of the form:
fullFieldName[option=value,...]
where fullFieldName is of the full field name of the record field and the options define the monitor options.
Monitor options are identified via the following option.
algorithm=name
where name is the algorithm name. For the algorithms implemented by pvData this is one of: onPut, onChange, or deadband.
onPut is the default so it does not have to be specified.
onChange accepts one additional option:
causeMonitor=value
where value must be "true" or "false". The default is true.
deadband accepts the following additional options:
deadband=deadbandValue
type=typeValue
isPercent=isPercentValue
where
NOTE: This applys to records in a javaIOC.
Any numeric scalar field in a record instance can optionally have an associated deadband structure, which is defined as follows:
<structure structureName = "deadband">
<structure name = "display">
<scalar name = "isPercent" scalarType = "boolean"/>
<!-- if true than percentage change. If false than absolute change -->
<scalar name = "value" scalarType = "double"/>
<!-- the deadband for display -->
</structure>
<structure name = "archive">
<scalar name = "isPercent" scalarType = "boolean"/>
<!-- if true than percentage change. If false than absolute change -->
<scalar name = "value" scalarType = "double"/>
<!-- the deadband for archive -->
</structure>
</structure>
As an example:
<record name = "example">
<scalar name = "value" scalarType = "double">
<structure name = "deadband" extends = "deadband">
<structure name = "display">
<deadband>.1</deadband>
</structure>
<structure name = "archive">
<deadband>.3</deadband>
</structure
</structure>
<!-- other stuff -->
</record>
NOTE: If an algorithm is not defined by the user for a field and a deadband is defined for the record than the algorithm is made to be deadband and the display deadband is used as the deadband.
The following:
String request = "alarm,timeStamp,power.value";
PVStructure pvRequest = org.epics.ca.client.CreateRequestFactory.createRequest(request);
Creates a pvRequest that monitors the alarm, timeStamp, and power.value fields of a record. The client will receive a structure with the fields named alarm,timeStamp, and value. Since the client has not specified any options the defaults will be used. This means:
The following:
String request = "timeStamp[algorithm=onChange,causeMonitor=true]";
PVStructure pvRequest = org.epics.ca.client.CreateRequestFactory.createRequest(request);
Will cause monitior whenever the timeStamp is changed. This is a way to be notified whenever a javaIOC record is processed because this is normally the only time the timeStamp is changed.
The following:
String request =
"alarm,timeStamp,value{power.value[algortithm=deadband,isPercent=true,deadband=1.0}";
PVStructure pvRequest = org.epics.ca.client.CreateRequestFactory.createRequest(request);
This requests a monitor whenever and alarm is raised or when the power.value changes by at least 1 percent.
This is the interface implemented by a factory that implements a monitoring algorithm
interface Monitor extends Destroyable {
void start();
void stop();
MonitorElement poll();
void release(MonitorElement monitorElement);
}
where
This is the interface returned by a call to poll.
interface MonitorElement {
PVStructure getPVStructure();
BitSet getChangedBitSet();
BitSet getOverrunBitSet();
}
where
This is the interface implemented by the code that request a monitor.
interface MonitorRequester extends Requester {
void monitorConnect(Monitor monitor, Structure structure);
void monitorEvent(Monitor monitor);
void unlisten();
}
where
The following are implemented by code that implements a monitor algorithm.
interface MonitorAlgorithm {
String getAlgorithmName();
boolean causeMonitor();
void monitorIssued();
}
where
This is the interface implemented by MonitorQueueFactory.
interface MonitorQueue {
void clear();
int getNumberFree();
int capacity();
MonitorElement getFree();
void setUsed(MonitorElement monitorElement);
MonitorElement getUsed();
void releaseUsed(MonitorElement monitorElement);
}
class MonitorQueueFactory {
static MonitorQueue create(MonitorElement[] monitorElements);
}
where
This package describes Access Security. Acceses security is not currently implemented so this section just discusses ideas of what access security should be.
This section is temporary. It will be removed after access security is implemented.
Epics Access Security had the concept of field level with only two levels allowed. For pvData more than two levels are allowed. In addition names can be associated with levels via inteface:
interface AccessSecurityLevel {
String getName(int level) throws IndexOutOfBoundsException;
int getLevel(String name) throws NoSuchFieldException;
String[] getNames();
}
A default implementation provides the following definition for names:
String[] names = {"internal","configuration","calibration","runtime"};
Another implementations can also be provided but any implementation should define levels starting with 0. Level 0 should be acceptable as the default.
Access Security limits access to A PVData database. Access Security for a PVData database is similar to the EPICS V3 access security. The requirements for access security were generated at ANL/APS in 1992. The requirements document is: Channel Access Security Requirements The V3 implementation is described in the V3 Application Developer's Guide. In that document read chapter "Access Security".
This document discusses the following:
TBD. This section will describe how to start access security for a javaIOC.
Access security protects IOC databases from unauthorized PVAccess Clients. Access security is based on the following:
Each record is assigned an assess security group or is put by default into a group named "default".
Each field of each record is assigned an access security level which is an integer that is greater than or equal to 0. Level 0 is the highest security level. If a client has access for level n then the client also has access for all levels greater than n. In addition names can be associated with the levels, which is implemeted via interface AccessSecurityLevel. A default implementation is provided which defines the names "internal","configuration","calibration" and ,"runtime". Other implementations can also be created but a particular site should define and use a common implementation.
NOTE: The definion of access security level is an extension to what V3 epics supports. It allows only two levels: 0 and 1 with 1 being the highest security level.
PVAccess can be enhanced to implement authentication for udp and tcp access to a PVData database but this is not the responsibility of access security. The configuration file, however, provides sytax that allows for authentication.
A PVData database can be accessed only via PVAccess or via a shell. It is assumed that access to the shell is protected via physical security and standard networking and physical security methods.
No attempt has been made to protect against the sophisticated saboteur. Network security methods must be used to limit access to the subnet on which the iocs reside.
This section describes the format of a file containing definitions of the user access groups, host access groups, and access security groups. Lets first give a simple example and then a complete description of the syntax.
<accessSecurity>
<credentials>
<credential name="user">[publickey,sharedkey]</credential>
<credential name="host">void</credential>
<!-- an arbitrary number of credentials can follow-->
<plugin name="publickey"></plugin>
<plugin name="sharedkey"></plugin>
<credentials>
<groups>
<group name = "users" credential = "user">[user1,user2]</group>
<group name = "hosts" credential = "host">[host1,host2]</group>
</group>
<access>
<group name = "default">
<rule>
<right>[read]</right>
</rule>
<rule>
<right>[read,write,process]</right>
<group>users.AND.hosts</group>
</rule>
</group>
</access>
</accessSecurity>
These rules provide read access to anyone located anywhere and read,write, and process access to user1 and user2 if they are located at host1 or host2.
An access security configuration file must start and end as follows:
<?xml version="1.0" ?> <accesssecurity> <!-- credentials, groups, states, access definitions --> </accesssecurity>
??? Should accesssecurity tag define xmlns or xsd ??
Between the accessSecurity tags the following definitions appear:
<credentials>
<credential name="name">[method,....method]></credential>
<!-- will look for plugin that supports method in the order specified,
first plugin that grants access means access is allowed -->
<!-- an arbitrary number of credentials can follow-->
<plugin name="method">
<!-- data for plugin. Syntax TBD -->
</plugin>
<!-- an arbitrary number of plugins can follow-->
</credentials>
<groups>
<group name="name" credential="name">[name,...,name]</group>
<!-- an arbitrary number of groups can be defined -->
</groups>
<states>
<state name = "name">
<provider>name</provider>
<!-- The channel provider -->
<pvname>name</pvname>
<!-- name must be a record that has a top level enumerated field named value -->
</state>
<!-- an arbitrary number of states may appear -->
</states>
<access>
<group name = "name">
<rule accessSecurityLevel = "name" >
<right>[right,...,right]</right>
<state name = "name">[state,...,state]</state>
<group>expression</group>
<!-- expression chooses a group from groups above-->
</rule>
<!-- an arbitrary number of rules may appear -->
</group>
<!-- an arbitrary number of groups can be defined -->
</access>
<log>
<group name = "name">
<rule accessSecurityLevel = "name" >
<right>[right,...,right]</right>
<state name = "name">[state,...,state]</state>
<group>expression</group>
<!-- expression chooses a group from groups above-->
</rule>
<!-- an arbitrary number of rules may appear -->
</group>
<!-- an arbitrary number of groups can be defined -->
</log>
Credentials allow group names to be verified by PVAccess. All credential definitions are enclosed in a single credentials xml element. An arbitrary set of credential and plugin elements can appear within credentials. A credential has an array of methods. The implementation looks for plugins in array order and asks the plugin if the client is valid
A credential has a name and holds an array of method names. A method is used on the server for authentication For example "void" just uses the response from the client, "sharedkey" does some real work, "client-ip" yields the client's IP number from PVAccess.
groups provide a way to combine a set of names with a single name. For example all accelerator physicists can be combined into a single group named acceleratorPhysist. Another example is that all workstations in the control room can be combined in a group named controlRoom.
A group has a name, selectes a credential, and contains a set of names.
states alow rules to apply only when something is or is not in a particular state. A single xml element "states" holds all possible states.
Each state has a name, which is referenced in access rules, and has two sub-elements: provider and pvname:
access contains definitions for an arbitrary number of access groups. Each access group defines an arbitrary number of rules. Each record being proctected by access security is assigned to an accesss security group. Thus each record instance belongs to a single access group. The default group has the name "default", which is the group for all records that do not have the accessGroup defined. Unlike a V3 access group the access group name will not be available via the PVRecord interface but will be assigned and kept within the access security system itself.
Each access group has a name, which selects the set of records to which the access group applies. The group contains an arbitrary set of rule definitions.
A rule optionally has an attribute accessSecurityLevel, which chooses an access security level. If the access security level is not specified then level 0 is assumed. The value for accessSecurityLevel is the name associated with the level. Within a rule the following elements can appear:
The access privilege for a client is determined as follows:
The definitions are similar to those for access and access group. But these are differences:
The level is now the maximum level to log rather than the minimum. Thus if the rule specifies level 0 than only level 0 fields are logged.
none means no logging, read means log reads, etc.
The following:
<credentials>
<credential name="user">[publickey,sharedkey]</credential>
<credential name="host">void</credential>
<credential name="ip">pva-client-ip</credential>
<!-- an arbitrary number of credentials can follow-->
<plugin name="publickey">
<!-- data for plugin. Syntax TBD -->
</plugin>
<plugin name="sharedkey">
<!-- data for plugin. Syntax TBD -->
</plugin>
<plugin name="pva-client-ip">
<!-- data for plugin. Syntax TBD -->
</plugin>
</credentials>
The example defines three credentials that may be used in the rules: user, host, and ip address. The publickey and sharedkey plugins are used (tried in that order) to authenticate users, the client may just declare its host name without further checking, and a PVAccess call is used to determine the numerical IP of the client.
<groups> <group name = "operator" credential = "user">[op1,op2,superguy]</group> <group name = "supervisor" credential = "user">[superguy]</group> <group name = "physicist" credential = "user">[joe,bill,sheng]</group> <group name = "controlRoom" credential = "host">[crhosr0,crhost1,crhost2]</group> <group name = "ioc" credential = "ip">[192.168.143.0/24,192.168.144.1,192.168.144.2]</group> </group>
The following:
<states>
<state name = "ringOpState">
<provider>CAV3</provider>
<pvname>OpIOC:ringOpState</pvname>
</state>
<state name = "linacOpState">
<provider>CAV3</provider>
<pvname>OpIOC:linacOpState</pvname>
</state>
</states>
Defines states for a storage ring and for the linac. The choices for each state are determined by the site. As an example they could be something like: "offline", "standy", and "operational"
The following"
<access>
<group name = "default">
<rule>
<right>[read]</right>
</rule accessSecurityLevel = "runtime">
<rule>
<right>[read,write,process]</right>
<group>(operator|physicist).AND.controlRoom</group>
</rule>
<rule>
<right>[read,write]</right>
<state name = "ringOpState">[offLine]</state>
<group>ringDeveloper.AND.controlRoom</group>
</rule>
</access>
Allows access to records in the default access security group as follows:
The following:
<log>
<group name = "default">
<rule>
<right>[write,process]</right>
<group>.NOT.((operators.OR.physicists).AND.controlRoom)</group>
</rule>
<rule>
<right>[write,process]</right>
<state name = "ringOpState">[offLine]</state>
<group>.NOT.ringDevelopers</group>
</rule>
</log>
This will log all write and process requests to all fields of all records in access group default except:
Lets design a set of rules for a Linac. Assume the following:
Most Linac IOC records will not have the ASG field defined and will thus be placed in group default.
The following records will have an ASG defined:
The following access configuration satisfies the above rules.
<credentials>
<credential name="user">[publickey,sharedkey]</credential>
<credential name="host">void</credential>
<credential name="ip">pva-client-ip</credential>
<!-- an arbitrary number of credentials can follow-->
<plugin name="publickey">
<!-- data for plugin. Syntax TBD -->
</plugin>
<plugin name="sharedkey">
<!-- data for plugin. Syntax TBD -->
</plugin>
<plugin name="pva-client-ip">
<!-- data for plugin. Syntax TBD -->
</plugin>
</credentials>
<groups>
<group name = "op" credential = "user">[op1,op2,superguy]</group>
<group name = "opSup" credential = "user">[superguy]</group>
<group name = "linac" credential = "user">[waw,nassiri,grelick,berg,fuja,gsm]</group>
<group name = "linacSup" credential = "user">[gsm]</group>
<group name = "appDev" credential = "user">[nda,kko]</group>
<group name = "cr" credential = "host">[mars,hera,gold]</group>
<group name = "ioc" credential = "ip">
[192.168.143.0/24,192.168.144.1,192.168.144.2]
</group>
</groups>
<states/>
<state name = "opstate">
<provider>CAV3</provider>
<pvname>LI:OPSTATE</pvname>
</state>
<state name = "permit">
<provider>CAV3</provider>
<pvname>LI:permit</pvname>
</state>
</states>
<access>
<group name = "default">
<rule accessSecurityLevel = "runtime">
<right>[read,write,process]</right>
<state name = "opstate">[operational]</state>
<group>op.AND.cr</group>
</rule>
<rule>
<right>[read,write,process]</right>
<state name = "opstate">[development]</state>
<!-- during development operators do not have access -->
<group>(opSup.OR.linacSup.OR.appdev).AND.cr</group>
</rule>
<rule>
<right>[read,write,process]</right>
<state name = "permit">[true]</state>
<group>(opSup.OR.linacSup.OR.appdev).AND.cr</group>
</rule>
<rule>
<right>[read]</right>
</rule>
<rule>
<right>[read,write,process]</right>
<group>ioc</group>
</rule>
</group>
<group name = "critical">
<rule>
<right>[read,write,process]</right>
<state name = "permit">[true]</state>
<group>(opSup.OR.linacSup.OR.appDev)</group>
</rule>
<rule>
<right>[read]</right>
</rule>
<rule>
<right>[read,write,process]</right>
<group>ioc</group>
</rule>
</group>
<group name = "permit">
<rule>
<right>[read,write,process]</right>
<group>opSup</group>
</rule>
<rule>
<right>[read]</right>
</rule>
</group>
</access>
A brief summary of the Functional Requirements is:
TBD.
TBD. The plan is to start/restart access security via record instances that have support that interfaces to access security
TBD.
TBD.