<commit_before>/*
 * Copyright (C) 2020 European Spallation Source ERIC
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package xal.plugin.epics7.server;

import com.cosylab.epics.caj.cas.util.MemoryProcessVariable;
import gov.aps.jca.CAException;
import gov.aps.jca.dbr.DBR;
import gov.aps.jca.dbr.DBRType;
import gov.aps.jca.dbr.DBR_Byte;
import gov.aps.jca.dbr.DBR_Double;
import gov.aps.jca.dbr.DBR_Float;
import gov.aps.jca.dbr.DBR_Int;
import gov.aps.jca.dbr.DBR_Short;
import gov.aps.jca.dbr.DBR_String;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.epics.pvdata.factory.PVDataFactory;
import org.epics.pvdata.factory.StandardFieldFactory;
import org.epics.pvdata.pv.PVByteArray;
import org.epics.pvdata.pv.PVDataCreate;
import org.epics.pvdata.pv.PVDoubleArray;
import org.epics.pvdata.pv.PVField;
import org.epics.pvdata.pv.PVFloatArray;
import org.epics.pvdata.pv.PVIntArray;
import org.epics.pvdata.pv.PVLongArray;
import org.epics.pvdata.pv.PVShortArray;
import org.epics.pvdata.pv.PVStringArray;
import org.epics.pvdata.pv.PVStructure;
import org.epics.pvdata.pv.ScalarType;
import org.epics.pvdata.pv.StandardField;
import org.epics.pvdata.pv.Structure;
import org.epics.pvdata.pv.Type;
import org.epics.pvdatabase.PVRecord;
import xal.ca.ChannelRecord;
import xal.ca.ChannelStatusRecord;
import xal.ca.ChannelTimeRecord;
import xal.ca.ConnectionException;
import xal.ca.GetException;
import xal.ca.IEventSinkValStatus;
import xal.ca.IEventSinkValTime;
import xal.ca.IEventSinkValue;
import xal.ca.IServerChannel;
import xal.ca.Monitor;
import xal.ca.MonitorException;
import xal.ca.PutException;
import xal.ca.PutListener;
import xal.plugin.epics7.Epics7Channel;
import xal.plugin.epics7.Epics7ChannelRecord;
import xal.plugin.epics7.Epics7ChannelStatusRecord;
import xal.plugin.epics7.Epics7ChannelTimeRecord;

/**
 * Server channel implementation. It creates PVAccess and CA channels,
 * independently of the signal prefix. This is done to ensure backwards
 * compatibility.
 *
 * Gets are done on the PVRecord, while sets are done to both PVRecord and CA
 * PV, so that they are always in sync.
 *
 * @author <NAME> <<EMAIL>>
 */
public class Epics7ServerChannel extends Epics7Channel implements IServerChannel {

    // Record associated with this channel.    
    private MemoryProcessVariable memoryProcessVariable;
    private PVRecord pvRecord;

    private final Epics7ServerChannelSystem epics7ServerChannelSystem;

    private static final String PROPERTIES = ALARM_FIELD + "," + TIMESTAMP_FIELD + ","
            + DISPLAY_FIELD + "," + CONTROL_FIELD;

    public Epics7ServerChannel(String signalName, Epics7ServerChannelSystem CHANNEL_SYSTEM) {
        super(signalName, CHANNEL_SYSTEM);

        // Removing protocol in case it is defined.
        if (m_strId.startsWith("ca://") || m_strId.startsWith("pva://")) {
            m_strId = m_strId.substring(m_strId.indexOf("://") + 3);
        }

        this.epics7ServerChannelSystem = CHANNEL_SYSTEM;

        requestConnection();
    }

    // Always return true because there is no connection to be made.
    @Override
    public boolean connectAndWait(double timeout) {
        requestConnection();
        return isConnected();
    }

    // No connection to be made, just create the record. By default its type is double.
    @Override
    public final void requestConnection() {
        if (pvRecord == null) {
            addCAPV(DBRType.DOUBLE);
            addRecord(ScalarType.pvDouble, false);
            connectionFlag = true;
        }
    }

    @Override
    public void disconnect() {
        removeCAPV();
        removeRecord();
        connectionFlag = false;
    }

    private void addCAPV(DBRType type) {
        MemoryProcessVariable newMemoryProcessVariable = new MemoryProcessVariable(m_strId, null, type, new double[]{0.0});

        if (memoryProcessVariable != null) {
            // TODO: Copy metadata from old record to new record
            removeCAPV();
        }
        memoryProcessVariable = newMemoryProcessVariable;

        epics7ServerChannelSystem.addMemPV(memoryProcessVariable);
    }

    private void addRecord(ScalarType scalarType, boolean array) {
        StandardField standardField = StandardFieldFactory.getStandardField();

        String properties = PROPERTIES;
        if (scalarType != ScalarType.pvString) {
            properties += "," + VALUE_ALARM_FIELD;
        }

        Structure structure;
        if (array) {
            structure = standardField.scalarArray(scalarType, properties);
        } else {
            structure = standardField.scalar(scalarType, properties);
        }
        PVDataCreate pvDataCreate = PVDataFactory.getPVDataCreate();
        PVStructure pvStructure = pvDataCreate.createPVStructure(structure);
        PVRecord newPVRecord = new PVRecord(m_strId, pvStructure);
        if (pvRecord != null) {
            // TODO: Copy metadata from old record to new record
            removeRecord();
        }
        pvRecord = newPVRecord;

        epics7ServerChannelSystem.addRecord(pvRecord);
    }

    private void removeCAPV() {
        if (memoryProcessVariable != null) {
            epics7ServerChannelSystem.removeMemPV(memoryProcessVariable);
            memoryProcessVariable = null;
        }
    }

    private void removeRecord() {
        if (pvRecord != null) {
            epics7ServerChannelSystem.removeRecord(pvRecord);
            pvRecord = null;
        }
    }

    @Override
    public int elementCount() throws ConnectionException {
        if (pvRecord != null) {
            PVField valueField = pvRecord.getPVStructure().getSubField(VALUE_FIELD);
            Type type = valueField.getField().getType();
            switch (type) {
                case scalar:
                    return 1;
                case scalarArray:
                    return Epics7ChannelRecord.getCountArray(pvRecord.getPVStructure(), valueField);
                default:
                    break;
            }
        }
        return 0;
    }

    protected PVStructure getDisplay() {

        System.out.println("ServerChannel");
        if (pvRecord != null) {
            return pvRecord.getPVStructure().getStructureField(DISPLAY_FIELD);
        }<commit_msg>Adding monitors to update the value on one protocol channel when the other one is updated by a put.<commit_after>/*
 * Copyright (C) 2020 European Spallation Source ERIC
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package xal.plugin.epics7.server;

import com.cosylab.epics.caj.cas.util.MemoryProcessVariable;
import gov.aps.jca.CAException;
import gov.aps.jca.dbr.DBR;
import gov.aps.jca.dbr.DBRType;
import gov.aps.jca.dbr.DBR_Byte;
import gov.aps.jca.dbr.DBR_Double;
import gov.aps.jca.dbr.DBR_Float;
import gov.aps.jca.dbr.DBR_Int;
import gov.aps.jca.dbr.DBR_Short;
import gov.aps.jca.dbr.DBR_String;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.epics.pvdata.factory.PVDataFactory;
import org.epics.pvdata.factory.StandardFieldFactory;
import org.epics.pvdata.pv.PVByteArray;
import org.epics.pvdata.pv.PVDataCreate;
import org.epics.pvdata.pv.PVDoubleArray;
import org.epics.pvdata.pv.PVField;
import org.epics.pvdata.pv.PVFloatArray;
import org.epics.pvdata.pv.PVIntArray;
import org.epics.pvdata.pv.PVLongArray;
import org.epics.pvdata.pv.PVShortArray;
import org.epics.pvdata.pv.PVStringArray;
import org.epics.pvdata.pv.PVStructure;
import org.epics.pvdata.pv.ScalarType;
import org.epics.pvdata.pv.StandardField;
import org.epics.pvdata.pv.Structure;
import org.epics.pvdata.pv.Type;
import org.epics.pvdatabase.PVRecord;
import xal.ca.ChannelRecord;
import xal.ca.ChannelStatusRecord;
import xal.ca.ChannelTimeRecord;
import xal.ca.ConnectionException;
import xal.ca.GetException;
import xal.ca.IEventSinkValStatus;
import xal.ca.IEventSinkValTime;
import xal.ca.IEventSinkValue;
import xal.ca.IServerChannel;
import xal.ca.Monitor;
import xal.ca.MonitorException;
import xal.ca.PutException;
import xal.ca.PutListener;
import xal.plugin.epics7.Epics7Channel;
import xal.plugin.epics7.Epics7ChannelRecord;
import xal.plugin.epics7.Epics7ChannelStatusRecord;
import xal.plugin.epics7.Epics7ChannelTimeRecord;

/**
 * Server channel implementation. It creates PVAccess and CA channels,
 * independently of the signal prefix. This is done to ensure backwards
 * compatibility.
 *
 * Gets are done on the PVRecord, while sets are done to both PVRecord and CA
 * PV, so that they are always in sync.
 *
 * @author <NAME> <<EMAIL>>
 */
public class Epics7ServerChannel extends Epics7Channel implements IServerChannel {

    // Record associated with this channel.    
    private MemoryProcessVariable memoryProcessVariable;
    private PVRecord pvRecord;

    private final Epics7ServerChannelSystem epics7ServerChannelSystem;

    private static final String PROPERTIES = ALARM_FIELD + "," + TIMESTAMP_FIELD + ","
            + DISPLAY_FIELD + "," + CONTROL_FIELD;

    public Epics7ServerChannel(String signalName, Epics7ServerChannelSystem CHANNEL_SYSTEM) {
        super(signalName, CHANNEL_SYSTEM);

        // Removing protocol in case it is defined.
        if (m_strId.startsWith("ca://") || m_strId.startsWith("pva://")) {
            m_strId = m_strId.substring(m_strId.indexOf("://") + 3);
        }

        this.epics7ServerChannelSystem = CHANNEL_SYSTEM;

        requestConnection();
    }

    // Always return true because there is no connection to be made.
    @Override
    public boolean connectAndWait(double timeout) {
        requestConnection();
        return isConnected();
    }

    // No connection to be made, just create the record. By default its type is double.
    @Override
    public final void requestConnection() {
        if (pvRecord == null) {
            addCAPV(DBRType.DOUBLE);
            addRecord(ScalarType.pvDouble, false);

            // Adding a monitor to update the value on one protocol channel when the
            // other one is updated by new data received in a put.
            try {
                Epics7ServerMonitor.createNewMonitor(pvRecord, memoryProcessVariable,
                        Epics7Channel.VALUE_REQUEST, (pvS) -> {
                        }, 0);
            } catch (ConnectionException e) {
                Logger.getLogger(Epics7ServerChannel.class.getName()).severe("Not possible to set callback to update value");
            }

            connectionFlag = true;
        }
    }

    @Override
    public void disconnect() {
        removeCAPV();
        removeRecord();
        connectionFlag = false;
    }

    private void addCAPV(DBRType type) {
        MemoryProcessVariable newMemoryProcessVariable = new MemoryProcessVariable(m_strId, null, type, new double[]{0.0});

        if (memoryProcessVariable != null) {
            // TODO: Copy metadata from old record to new record
            removeCAPV();
        }
        memoryProcessVariable = newMemoryProcessVariable;

        epics7ServerChannelSystem.addMemPV(memoryProcessVariable);
    }

    private void addRecord(ScalarType scalarType, boolean array) {
        StandardField standardField = StandardFieldFactory.getStandardField();

        String properties = PROPERTIES;
        if (scalarType != ScalarType.pvString) {
            properties += "," + VALUE_ALARM_FIELD;
        }

        Structure structure;
        if (array) {
            structure = standardField.scalarArray(scalarType, properties);
        } else {
            structure = standardField.scalar(scalarType, properties);
        }
        PVDataCreate pvDataCreate = PVDataFactory.getPVDataCreate();
        PVStructure pvStructure = pvDataCreate.createPVStructure(structure);
        PVRecord newPVRecord = new PVRecord(m_strId, pvStructure);
        if (pvRecord != null) {
            // TODO: Copy metadata from old record to new record
            removeRecord();
        }
        pvRecord = newPVRecord;

        epics7ServerChannelSystem.addRecord(pvRecord);
    }

    private void removeCAPV() {
        if (memoryProcessVariable != null) {
            epics7ServerChannelSystem.removeMemPV(memoryProcessVariable);
            memoryProcessVariable = null;
        }
    }

    private void removeRecord() {
        if (pvRecord != null) {
            epics7ServerChannelSystem.removeRecord(pvRecord);
            pvRecord = null;
        }
    }

    @Override
    public int elementCount() throws ConnectionException {
        if (pvRecord != null) {
            PVField valueField = pvRecord.getPVStructure().getSubField(VALUE_FIELD);
            Type type = valueField.getField().getType();
            switch (type) {
                case scalar:
                    return 1;
                case scalarArray:
                    return Epics7ChannelRecord.getCountArray(pvRecord.getPVStructure(), valueField);
                default:
                    break;
            }
        }
        return 0;
    }

    protected PVStructure getDisplay() {
        if (pvRecord != null) {
            return pvRecord.getPVStructure().getStructureField(DISPLAY_FIELD);
        }
{'fraction_non_alphanumeric': 0.08774594408008285, 'fraction_numerical': 0.006489471867449085, 'mean_word_length': 2.9471389645776567, 'pattern_counts': {'":': 2, '<': 9, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6723364', 'n_tokens_mistral': 4274, 'n_tokens_neox': 4093, 'n_words': 1080}