All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.epics.gpclient.datasource.pva.PVAChannelHandler Maven / Gradle / Ivy

There is a newer version: 1.0.8
Show newest version
/**
 * Copyright information and license terms for this software can be
 * found in the file LICENSE.TXT included with the distribution.
 */
package org.epics.gpclient.datasource.pva;

import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.epics.gpclient.ReadCollector;
import org.epics.gpclient.WriteCollector;
import org.epics.gpclient.WriteCollector.WriteRequest;
import org.epics.gpclient.datasource.DataSourceTypeAdapter;

import org.epics.pvaccess.client.Channel;
import org.epics.pvaccess.client.Channel.ConnectionState;
import org.epics.pvaccess.client.ChannelProvider;
import org.epics.pvaccess.client.ChannelPut;
import org.epics.pvaccess.client.ChannelPutRequester;
import org.epics.pvaccess.client.ChannelRequester;
import org.epics.pvaccess.client.GetFieldRequester;
import org.epics.pvdata.copy.CreateRequest;
import org.epics.pvdata.factory.ConvertFactory;
import org.epics.pvdata.factory.PVDataFactory;
import org.epics.pvdata.misc.BitSet;
import org.epics.pvdata.monitor.Monitor;
import org.epics.pvdata.monitor.MonitorElement;
import org.epics.pvdata.monitor.MonitorRequester;
import org.epics.pvdata.pv.Convert;
import org.epics.pvdata.pv.Field;
import org.epics.pvdata.pv.MessageType;
import org.epics.pvdata.pv.PVField;
import org.epics.pvdata.pv.PVInt;
import org.epics.pvdata.pv.PVScalar;
import org.epics.pvdata.pv.PVScalarArray;
import org.epics.pvdata.pv.PVStringArray;
import org.epics.pvdata.pv.PVStructure;
import org.epics.pvdata.pv.Status;
import org.epics.pvdata.pv.StringArrayData;
import org.epics.pvdata.pv.Structure;
import org.epics.gpclient.datasource.MultiplexedChannelHandler;
import org.epics.util.array.CollectionNumbers;
import org.epics.util.array.ListNumber;
import org.epics.util.array.UnsafeUnwrapper;

/**
 * 
 * @author msekoranja
 */
class PVAChannelHandler extends
		MultiplexedChannelHandler implements
		ChannelRequester, GetFieldRequester, ChannelPutRequester, MonitorRequester {

	private final ChannelProvider pvaChannelProvider;
	private final short priority;
	private final PVATypeSupport pvaTypeSupport;

	private volatile Channel channel = null;

	private final AtomicBoolean monitorCreated = new AtomicBoolean(false);
	private final AtomicLong monitorLossCounter = new AtomicLong(0);
	//private volatile Monitor monitor = null;
	
	private volatile Field channelType = null;
	private volatile boolean isChannelEnumType = false;
	
	private final AtomicBoolean channelPutCreated = new AtomicBoolean(false);
	private volatile ChannelPut channelPut = null;
	private volatile PVStructure channelPutStructure = null;
	private volatile BitSet bitSet = null;
	private volatile PVField channelPutValueField = null;


	private static final Logger logger = Logger.getLogger(PVAChannelHandler.class.getName());

	private static CreateRequest createRequest = CreateRequest.create();
	private static PVStructure allPVRequest = createRequest.createRequest("field()");
	private static PVStructure standardPutPVRequest = createRequest.createRequest("field(value)");
	private static PVStructure enumPutPVRequest = createRequest.createRequest("field(value.index)");
	
	private static final String PVREQUEST_PREFIX = "?request=";
	private final PVStructure pvRequest;
	private final String extractPVField;
	
	public static PVAChannelHandler create(String channelName,
			ChannelProvider channelProvider, short priority,
			PVATypeSupport typeSupport) {
		
		int pos = channelName.indexOf(PVREQUEST_PREFIX); 
		if (pos == -1)
		{
			return new PVAChannelHandler(channelName, null, channelProvider, priority, typeSupport);
		}
		else
		{
			String pvRequestString = channelName.substring(pos+PVREQUEST_PREFIX.length());
			channelName = channelName.substring(0, pos);
			return new PVAChannelHandler(channelName, pvRequestString, channelProvider, priority, typeSupport);
		}
		
	}

	public PVAChannelHandler(String channelName, String pvRequestString,
			ChannelProvider channelProvider, short priority,
			PVATypeSupport typeSupport) {
		super(channelName);
		this.pvRequest = (pvRequestString != null) ? createRequest.createRequest(pvRequestString) : null;
		this.pvaChannelProvider = channelProvider;
		this.priority = priority;
		this.pvaTypeSupport = typeSupport;
		
		if (pvRequest != null)
		{
			PVStructure field = pvRequest.getStructureField("field");
			extractPVField = getOnlyChildFieldName(field);
		}
		else
			extractPVField = null;
		
		// NOTE: mind "return" above
	}

	private static final String _OPTIONS = "_options";
	private static final String TAKE_PARENT = _OPTIONS;
	private static final String getOnlyChildFieldName(PVStructure field)
	{		
		if (field != null)
		{
			String[] fieldNames = field.getStructure().getFieldNames();
			if (fieldNames.length > 0)
			{
				String name = null;
				for (int i = 0; i < fieldNames.length; i++)
				{
					// ignore options
					if (!fieldNames[0].equals(_OPTIONS))
					{
						if (name == null)
							name = fieldNames[0];
						else
							return null;
					}
				}
				
				if (name == null)
				{
					// only "_options" field, that's OK
					return TAKE_PARENT;
				}
				else
				{
					String childName = getOnlyChildFieldName(field.getStructureField(name));
					if (childName == null)
						return null;
					else if (childName.equals(_OPTIONS))
						return name;
					else
						return name + "." + childName;
				}
			}
			else
			{
				// no options, no subfield(s)
				return TAKE_PARENT;
			}
		}
		else
			return null;
	}
	
	
	/**
	 * @return the channel
	 */
	public Channel getChannel() {
		return channel;
	}

	/**
	 * @return the channelType
	 */
	public Field getChannelType() {
		return channelType;
	}

	public String getExtractFieldName() {
		return extractPVField;
	}

	@Override
	public String getRequesterName() {
		return this.getClass().getName();
	}

	@Override
	public void message(String message, MessageType messageType) {
		logger.log(toLoggerLevel(messageType), message);
	}

	/**
	 * Converts MessageType to Java Logging API Level.
	 * @param messageType pvData message type.
	 * @return Corresponded Java Logging API Level.
	 */
	public static Level toLoggerLevel(MessageType messageType) {
		switch (messageType) {
		case info:
			return Level.INFO;
		case warning:
			return Level.WARNING;
		case error:
		case fatalError:
			return Level.SEVERE;
		default:
			return Level.INFO;
		}
	}
	
	private void reportStatus(String message, Status status)
	{
		if (!status.isSuccess()) {
			logger.log(Level.WARNING, message + ": " + status.getMessage());

			// for developers
			String dump = status.getStackDump();
			if (dump != null && !dump.isEmpty())
				logger.log(Level.FINER, message + ": " + status.getMessage() + ", cause:\n" + dump);
		}
	}
	
	@Override
	public void connect() {
		pvaChannelProvider.createChannel(getChannelName(), this, priority);
	}

	@Override
	public void channelCreated(Status status, Channel channel) {
		reportStatus("Failed to create channel instance '" + channel.getChannelName(), status);
		this.channel = channel;
	}
	
    @Override
    public void channelStateChange(Channel channel, ConnectionState connectionState) {
        try {

            // introspect
            if (connectionState == ConnectionState.CONNECTED) {
                if (extractPVField == null) {
                    channel.getField(this, null);
                } else {
                    channel.getField(this, extractPVField);
                }
            } else {
                processConnection(newConnectionPayload());
            }

        } catch (Exception ex) {
            reportExceptionToAllReadersAndWriters(ex);
        }
    }

	/* (non-Javadoc)
	 * @see org.epics.pvaccess.client.GetFieldRequester#getDone(org.epics.pvdata.pv.Status, org.epics.pvdata.pv.Field)
	 */
	@Override
	public void getDone(Status status, Field field) {
		reportStatus("Failed to instrospect channel '" + channel.getChannelName() + "'", status);
		
		if (status.isSuccess())
		{
			channelType = field;
		
			Field valueField = (channelType instanceof Structure) ? ((Structure)channelType).getField("value") : null;
			if (valueField != null && valueField.getID().equals("enum_t"))
			{
				isChannelEnumType = true;
				// TODO could create a monitor just to get value.choices
			}
			else
				isChannelEnumType = false;
		}
	
		processConnection(newConnectionPayload());
	}
    
    private PVAConnectionPayload newConnectionPayload() {
        return new PVAConnectionPayload(channelType, channel != null && channel.isConnected(), extractPVField);
    }

    @Override
    public boolean isConnected(PVAConnectionPayload connectionPayload) {
        return connectionPayload.connected;
    }

    @Override
    protected boolean isWriteConnected(PVAConnectionPayload connectionPayload) {
    	// NOTE: access-rights not yet supported
        return connectionPayload.connected;
    }

    @Override
    public synchronized Map getProperties() {
        Map properties = new HashMap();
        if (channel != null) {
            properties.put("Channel name", channel.getChannelName());
            if (pvRequest != null)
                properties.put("User pvRequest", pvRequest.toString());
            properties.put("Connection state", channel.getConnectionState().name());
            properties.put("Provider name", channel.getProvider().getProviderName());
            if (channel.getConnectionState() == Channel.ConnectionState.CONNECTED) {
                properties.put("Remote address", channel.getRemoteAddress());
                properties.put("Channel type", channelType.getID());
                //properties.put("Read access", channel.getReadAccess());
                //properties.put("Write access", channel.getWriteAccess());
            }
            properties.put("Monitor loss count", monitorLossCounter.get());
        }
        return properties;
    }

    @Override
	public void disconnect() {
		// Close the channel
		try {
			channel.destroy();
		} finally {
			channel = null;
			
			//monitor = null;
			monitorCreated.set(false);
			
			channelType = null;
			
			channelPut = null;
			channelPutValueField = null;
			channelPutCreated.set(false);
		}
	}
	
	private final LinkedList> writeRequests = new LinkedList>(); 

    @Override
    protected void processWriteRequest(WriteRequest request) {
        boolean wasEmpty;
        synchronized (writeRequests) {
            wasEmpty = writeRequests.isEmpty();
            writeRequests.add(request);
        }

        if (!channelPutCreated.getAndSet(true)) {
            channel.createChannelPut(this, isChannelEnumType ? enumPutPVRequest : standardPutPVRequest);
        } else if (wasEmpty) {
            doNextWrite();
        }
    }

    private void doNextWrite() {
        WriteRequest writeRequest;
        synchronized (writeRequests) {
            writeRequest = writeRequests.peek();
        }

        if (writeRequest != null) {
            try {
                if (channelPutValueField == null) {
                    throw new RuntimeException("No 'value' field");
                }

                fromObject(channelPutValueField, writeRequest.getValue());
                channelPut.put(channelPutStructure, bitSet);
            } catch (Exception ex) {
                writeRequests.poll();
                writeRequest.writeFailed(ex);
            }
        }

    }
	
	@Override
	public void channelPutConnect(Status status, ChannelPut channelPut, Structure putStructure) {
		reportStatus("Failed to create ChannelPut instance", status);

		if (status.isSuccess())
		{
			this.channelPut = channelPut;
			
			if (channelPutStructure == null ||
				!channelPutStructure.getStructure().equals(putStructure))
			{
				channelPutStructure = PVDataFactory.getPVDataCreate().createPVStructure(putStructure);
				bitSet = new BitSet(channelPutStructure.getNumberFields());
			}
			
			if (isChannelEnumType)
			{
				// handle inconsistent behavior
				this.channelPutValueField = channelPutStructure.getSubField("value");
				if (this.channelPutValueField instanceof PVStructure)
					this.channelPutValueField = ((PVStructure)channelPutValueField).getSubField("index");
			}
			else
			{
				this.channelPutValueField = channelPutStructure.getSubField("value");
			}

			
			// set BitSet
			bitSet.clear();	// re-connect case
			if (this.channelPutValueField != null)
				bitSet.set(channelPutValueField.getFieldOffset());
		}
		
		doNextWrite();
	}

	@Override
	public void putDone(Status status, ChannelPut channePut) {
		reportStatus("Failed to put value", status);
		
		WriteRequest writeRequest;
		synchronized (writeRequests)
		{
			writeRequest = writeRequests.poll();
		}

		if (writeRequest != null)
		{
			if (status.isSuccess())
			{
				writeRequest.writeSuccessful();
			}
			else
			{
				writeRequest.writeFailed(new Exception(status.getMessage()));
			}
			
			doNextWrite();
		}
		
	}
	
	@Override
	public void getDone(Status status, ChannelPut channelPut, PVStructure pvStructure, BitSet bitSet) {
		// never used, i.e. ChannelPut.get() never called
	}

	private final static Convert convert = ConvertFactory.getConvert();
	
	// TODO check if non-V types can ever be given as newValue
	private final void fromObject(PVField field, Object newValue)
	{
		// enum support
		if (isChannelEnumType)
		{
			// value.index int field expected
			PVInt indexPutField = (PVInt)channelPutValueField;
			
			int index = -1;
			if (newValue instanceof Number)
			{
				index = ((Number)newValue).intValue();
			}
			else if (newValue instanceof String)
			{
				String nv = (String)newValue; 
				
				PVStructure lastValue = getLastMessagePayload();
				if (lastValue == null)
					throw new IllegalArgumentException("no monitor on '" + getChannelName() +"' created to get list of valid enum choices");
				
				PVStringArray pvChoices = (PVStringArray)lastValue.getSubField("value.choices");
				StringArrayData data = new StringArrayData();
				pvChoices.get(0, pvChoices.getLength(), data);
				final String[] choices = data.data;
				
				for (int i = 0; i < choices.length; i++)
				{
					if (nv.equals(choices[i]))
					{
						index = i;
						break;
					}
				}
				
				// fallback: try to convert string to an number (index)
				if (index == -1)
				{
					try {
						int ix = Integer.parseInt(nv);
						if (ix >= 0 && ix < choices.length)
							index = ix;
					} catch (Throwable th) {
						// failed to convert, noop
					}
				}
				
				if (index == -1)
					throw new IllegalArgumentException("enumeration '" + nv +"' is not a valid choice");
			}
			
			indexPutField.put(index);
			
			return;
		}
		
        if (channelPutValueField instanceof PVScalar)
        {
	        if (newValue instanceof Double)
				convert.fromDouble((PVScalar)field, ((Double)newValue).doubleValue());
			else if (newValue instanceof Integer)
				convert.fromInt((PVScalar)field, ((Integer)newValue).intValue());
			else if (newValue instanceof String)
				convert.fromString((PVScalar)field, (String)newValue);
			else if (newValue instanceof Byte)
				convert.fromByte((PVScalar)field, ((Byte)newValue).byteValue());
			else if (newValue instanceof Short)
				convert.fromShort((PVScalar)field, ((Short)newValue).shortValue());
			else if (newValue instanceof Long)
				convert.fromLong((PVScalar)field, ((Long)newValue).longValue());
			else if (newValue instanceof Float)
				convert.fromFloat((PVScalar)field, ((Float)newValue).floatValue());
			else if (newValue instanceof Boolean)
				//  TODO no convert.fromBoolean
				//convert.fromBoolean((PVScalar)field, ((Boolean)newValue).booleanValue());
				convert.fromByte((PVScalar)field, ((Boolean)newValue).booleanValue() ? (byte)1 : (byte)0);
    		else
    			throw new RuntimeException("Unsupported write, cannot put '" + newValue.getClass() + "' into scalar '" + channelPutValueField.getField() + "'");
        }
        else if (channelPutValueField instanceof PVScalarArray)
        {
            // if it's a ListNumber, extract the array
            if (newValue instanceof ListNumber) {
                ListNumber data = (ListNumber) newValue;
                // FIXME: Optimize!!! You should get the array type of whatever it is and write the exact boundaries
                Object wrappedArray = UnsafeUnwrapper.readSafeDoubleArray(data).array;
            }
            else if (!newValue.getClass().isArray())
            {
            	// create an array
            	Object newValueArray = Array.newInstance(newValue.getClass(), 1);
            	Array.set(newValueArray, 0, newValue);
            	newValue = newValueArray;
            }
            
            if (newValue instanceof double[])
    			convert.fromDoubleArray((PVScalarArray)field, 0, ((double[])newValue).length, (double[])newValue, 0);
    		else if (newValue instanceof int[])
    			convert.fromIntArray((PVScalarArray)field, 0, ((int[])newValue).length, (int[])newValue, 0);
    		else if (newValue instanceof String[])
    			convert.fromStringArray((PVScalarArray)field, 0, ((String[])newValue).length, (String[])newValue, 0);
            // special case from string to array
    		else if (newValue instanceof String)
    		{
    			String str = ((String)newValue).trim();
    			
    			// remove []
    			if (str.charAt(0) == '[' && str.charAt(str.length()-1) == ']')
    				str = str.substring(1, str.length()-1);
    			
    			// split on commas and whitespaces
    			String[] splitValues = str.split("[,\\s]+");
    			convert.fromStringArray((PVScalarArray)field, 0, splitValues.length, splitValues, 0);
    		}
    		
    		else if (newValue instanceof byte[])
    			convert.fromByteArray((PVScalarArray)field, 0, ((byte[])newValue).length, (byte[])newValue, 0);
    		else if (newValue instanceof short[])
    			convert.fromShortArray((PVScalarArray)field, 0, ((short[])newValue).length, (short[])newValue, 0);
    		else if (newValue instanceof long[])
    			convert.fromLongArray((PVScalarArray)field, 0, ((long[])newValue).length, (long[])newValue, 0);
    		else if (newValue instanceof float[])
    			convert.fromFloatArray((PVScalarArray)field, 0, ((float[])newValue).length, (float[])newValue, 0);
    		else if (newValue instanceof boolean[])
    		{
    			boolean[] bArray = (boolean[])newValue;
    			byte[] byteArray = new byte[bArray.length];
    			for (int i = 0; i < bArray.length; i++)
    				byteArray[i] = bArray[i] ? (byte)1 : (byte)0;
    			convert.fromByteArray((PVScalarArray)field, 0, byteArray.length, byteArray, 0);
    		}
    		else
    			throw new RuntimeException("Unsupported write, cannot put '" + newValue.getClass() + "' into array'" + channelPutValueField.getField() + "'");
        }
		else
			throw new RuntimeException("Unsupported write, cannot put '" + newValue.getClass() + "' into '" + channelPutValueField.getField() + "'");

        
	}
	

        
    @Override
    protected PVATypeAdapter findTypeAdapter(ReadCollector cache, PVAConnectionPayload connection) {
        return pvaTypeSupport.find(cache, connection);
    }

	@Override
	public void addReader(ReadCollector subscription) {
		super.addReader(subscription);
		
		if (!monitorCreated.getAndSet(true))
		{
			// TODO remove this....
			for (int i = 0; i < 100 && channel.getConnectionState() == ConnectionState.NEVER_CONNECTED; i++)
			{
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) { }
			}
			// TODO optimize fields
			channel.createMonitor(this, pvRequest != null ? pvRequest : allPVRequest);
		}
	}

	/* (non-Javadoc)
	 * @see org.epics.pvdata.monitor.MonitorRequester#monitorConnect(org.epics.pvdata.pv.Status, org.epics.pvdata.monitor.Monitor, org.epics.pvdata.pv.Structure)
	 */
	@Override
	public void monitorConnect(Status status, Monitor monitor, Structure structure) {
		reportStatus("Failed to create monitor", status);
		
		if (status.isSuccess())
		{
			//this.monitor = monitor;
			monitor.start();
		}
	}

	/* (non-Javadoc)
	 * @see org.epics.pvdata.monitor.MonitorRequester#monitorEvent(org.epics.pvdata.monitor.Monitor)
	 */
	@Override
	public void monitorEvent(Monitor monitor) {
		MonitorElement monitorElement;
		while ((monitorElement = monitor.poll()) != null)
		{
			if (monitorElement.getOverrunBitSet().cardinality() > 0)
				monitorLossCounter.incrementAndGet();
			
			// TODO combine bitSet, etc.... do we need to copy structure?
			processMessage(monitorElement.getPVStructure());
			monitor.release(monitorElement);
		}
	}

	/* (non-Javadoc)
	 * @see org.epics.pvdata.monitor.MonitorRequester#unlisten(org.epics.pvdata.monitor.Monitor)
	 */
	@Override
	public void unlisten(Monitor monitor) {
		// TODO Auto-generated method stub
	}
	
	@Override
	public String toString() {
		return "PVAChannelHandler [getChannelName()=" + getChannelName() + "]";
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy