gda.device.scannable.scannablegroup.ScannableGroup.java Source code

Java tutorial

Introduction

Here is the source code for gda.device.scannable.scannablegroup.ScannableGroup.java

Source

/*-
 * Copyright  2009-2013 Diamond Light Source Ltd.
 *
 * This file is part of GDA.
 *
 * GDA is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License version 3 as published by the Free
 * Software Foundation.
 *
 * GDA 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 GDA. If not, see <http://www.gnu.org/licenses/>.
 */

package gda.device.scannable.scannablegroup;

import gda.device.DeviceException;
import gda.device.Scannable;
import gda.device.scannable.ScannableBase;
import gda.device.scannable.ScannableUtils;
import gda.device.scannable.PositionConvertorFunctions;
import gda.factory.Configurable;
import gda.factory.FactoryException;
import gda.factory.Finder;
import gda.observable.IObserver;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.python.core.Py;
import org.python.core.PyString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A logical group of scannables
 */
public class ScannableGroup extends ScannableBase implements Configurable, IScannableGroup, IObserver {

    private static final Logger logger = LoggerFactory.getLogger(ScannableGroup.class);

    // the list of members
    String[] groupMemberNames = new String[0]; // will use jakarta commons.lang to manipulate this
    ArrayList<Scannable> groupMembers = new ArrayList<Scannable>();

    /**
     * Constructor.
     */
    public ScannableGroup() {
    }

    /**
     * Constructor.
     * 
     * @param name
     * @param groupMembers
     */
    public ScannableGroup(String name, Scannable[] groupMembers) {
        setName(name);
        setGroupMembers(groupMembers);
    }

    @Override
    public void configure() throws FactoryException {
        // add all membernames to the list of members
        Set<String> namesOfGroupMembers = setOfGroupMemberNames(groupMembers);
        for (String name : groupMemberNames) {

            if (name == null) {
                continue;
            }

            if (!namesOfGroupMembers.contains(name)) {
                try {
                    Scannable newScannable = (Scannable) Finder.getInstance().find(name.trim());
                    if (newScannable != null) {
                        addGroupMember(newScannable);
                        namesOfGroupMembers.add(name);
                        newScannable.addIObserver(this);
                    }
                } catch (ClassCastException e) {
                    // finder must have returned something which was not a Scannable
                }
            }
        }

        // Update group member names to match group members
        setGroupMemberNamesArrayUsingGroupMembersList();

        // configure all members
        for (Scannable scannable : groupMembers) {
            if (scannable instanceof ScannableBase) {
                ((ScannableBase) scannable).configure();
            }
            scannable.addIObserver(this);
        }

        setArrays();
        configured = true;
    }

    @Override
    public void addGroupMemberName(String groupMemberName) {
        if (!ArrayUtils.contains(groupMemberNames, groupMemberName)) {
            groupMemberNames = (String[]) ArrayUtils.add(groupMemberNames, groupMemberName);
        }
    }

    /**
     * Sets the group member names for this scannable group.
     * 
     * @param groupMemberNames
     *            the group member names
     */
    public void setGroupMemberNames(List<String> groupMemberNames) {
        this.groupMemberNames = new String[0];
        for (String name : groupMemberNames) {
            addGroupMemberName(name);
        }
    }

    @Override
    public String[] getGroupMemberNames() {
        return this.groupMemberNames;
    }

    /**
     * Adds a scannable to this group. This will not add a Scannable if its name matches anther member's name, even if
     * they are different objects.
     * 
     * @param groupMember
     */
    public void addGroupMember(Scannable groupMember) {
        Scannable member = getGroupMember(groupMember.getName());
        if (member == null) {
            if (!this.groupMembers.contains(groupMember)) {
                this.groupMembers.add(groupMember);
                if (configured) {
                    setGroupMemberNamesArrayUsingGroupMembersList();
                }
            }
        } else {
            logger.info(getName() + " will not add Scannable named " + groupMember.getName()
                    + " as it already has a Scannable with the same name");
        }
    }

    public void removeGroupMember(Scannable groupMember) {
        if (!this.groupMembers.contains(groupMember)) {
            this.groupMembers.remove(groupMember);
            if (configured) {
                setGroupMemberNamesArrayUsingGroupMembersList();
            }
        }
    }

    public void removeGroupMember(String nameOfGroupMember) {
        Scannable member = getGroupMember(nameOfGroupMember);
        if (member != null) {
            removeGroupMember(member);
        }
    }

    /**
     * @return array of scannable objects in this group
     */
    public List<Scannable> getGroupMembers() {
        return this.groupMembers;
    }

    /**
     * @param name
     * @return the Scannable of the given name
     */
    public Scannable getGroupMember(String name) {
        for (Scannable member : groupMembers) {
            if (member.getName().equals(name)) {
                return member;
            }
        }
        return null;
    }

    /**
     * Sets the group members that make up this scannable group.
     * 
     * @param groupMembers
     *            the group members
     */
    public void setGroupMembers(List<Scannable> groupMembers) {
        this.groupMembers = new ArrayList<Scannable>(groupMembers);
        if (configured) {
            setGroupMemberNamesArrayUsingGroupMembersList();
            setArrays();
        }
    }

    /**
     * Sets the members of this group. 
     * <p>
     * This is final, as for historical reasons there are two setters on here, and it is natural to extend just one.
     * <p>
     * 
     * @param groupMembers
     */
    final public void setGroupMembers(Scannable[] groupMembers) {
        setGroupMembers(new ArrayList<Scannable>(Arrays.asList(groupMembers)));
    }

    /**
     * See __getattr__(String name).
     **/
    public Object __getattr__(PyString name) {
        return __getattr__(name.internedString());
    }

    /**
     * Python method used by the interpreter to get attributes that are not defined on an object. Used here to provide
     * dotted access to member scannables.
     * 
     * @param name
     * @return The named member scannable or null if it does not exist.
     */
    Object __getattr__(String name) {
        // find the member's name and return it
        for (Scannable member : groupMembers) {
            if (member.getName().compareTo(name) == 0) {
                return member;
            }
        }

        // else try adding the scanablegroups name to the request
        String newName = getName() + "_" + name;
        for (Scannable member : groupMembers) {
            if (member.getName().compareTo(newName) == 0) {
                return member;
            }
        }

        throw Py.AttributeError(name);
    }

    @Override
    public void asynchronousMoveTo(Object position) throws DeviceException {
        //TODO must check if the number of inputs match with number of members
        Vector<Object[]> targets = extractPositionsFromObject(position);

        // send out moves
        for (int i = 0; i < groupMembers.size(); i++) {
            // Don't try to move zero-input-extra name scannables
            if ((groupMembers.get(i).getInputNames().length + groupMembers.get(i).getExtraNames().length) == 0) {
                continue;
            }
            Object[] thisTarget = targets.get(i);
            if (thisTarget.length == 1) {
                if (targets.get(i)[0] != null) {
                    groupMembers.get(i).asynchronousMoveTo(targets.get(i)[0]);
                }
            } else {
                groupMembers.get(i).asynchronousMoveTo(targets.get(i));
            }
        }

    }

    private Vector<Object[]> extractPositionsFromObject(Object position) throws DeviceException {
        // map object to an array of doubles
        int inputLength = 0;
        for (Scannable member : groupMembers) {
            inputLength += member.getInputNames().length;
        }
        Object[] targetPosition = PositionConvertorFunctions.toObjectArray(position);
        if (targetPosition.length != inputLength) {
            throw new DeviceException("Position does not have correct number of fields. Expected = " + inputLength
                    + " actual = " + targetPosition.length + " position= " + position.toString());
        }
        // break down to individual commands
        int targetIterator = 0;
        Vector<Object[]> targets = new Vector<Object[]>();
        for (Scannable member : groupMembers) {
            Object[] thisTarget = new Object[member.getInputNames().length];
            for (int i = 0; i < member.getInputNames().length; i++) {
                thisTarget[i] = targetPosition[targetIterator];
                targetIterator++;
            }
            targets.add(thisTarget);
        }
        return targets;
    }

    @Override
    public Object getPosition() throws DeviceException {

        // create array of correct length
        int outputLength = 0;
        for (Scannable member : groupMembers) {
            outputLength += member.getInputNames().length;
            outputLength += member.getExtraNames().length;
        }
        Object[] position = new Object[outputLength];

        // loop through members and add position values to array
        try {
            List<Object[]> memberPositions = new Vector<Object[]>();
            for (Scannable member : groupMembers) {

                Object[] memberPosition;
                try {

                    Object pos = member.getPosition();
                    if (pos != null) {
                        memberPosition = PositionConvertorFunctions.toObjectArray(pos);
                    } else {
                        memberPosition = new Object[0];
                    }
                } catch (Exception e) {
                    // if this fails, try getting strings instead. If that fails then let exception escalate
                    memberPosition = ScannableUtils.getFormattedCurrentPositionArray(member);
                }
                memberPositions.add(memberPosition);
            }

            int n = 0;
            for (int i = 0; i < groupMembers.size(); i++) {
                for (int j = 0; j < groupMembers.get(i).getInputNames().length; j++) {
                    position[n] = memberPositions.get(i)[j];
                    n++;
                }
            }
            for (int i = 0; i < groupMembers.size(); i++) {
                for (int j = 0; j < groupMembers.get(i).getExtraNames().length; j++) {
                    position[n] = memberPositions.get(i)[j + groupMembers.get(i).getInputNames().length];
                    n++;
                }
            }
        } catch (Exception e) {
            throw new DeviceException(
                    "Exception occurred during " + getName() + " getPosition(): " + e.getMessage());
        }

        return position;
    }

    @Override
    public boolean isBusy() throws DeviceException {
        for (Scannable member : groupMembers) {
            if (member.isBusy()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void setExtraNames(String[] names) {
        // do nothing: don't want to override members' extra names
    }

    @Override
    public void setInputNames(String[] names) {
        // do nothing: don't want to override members' input names
    }

    @Override
    public String toFormattedString() {
        //TODO this method does not provide correct indentation level for a scannable group inside another scannable group
        //TODO the regex parser is unreliable as described by FIXME below
        // IT would be better to create format message by delegate to individual members directly, rather than re-parsing output again.

        //TODO this works if the toFormattedString method of the members conforms to a standard. But I don't think there is one!
        //Rather use getPosition and format here.
        String membersOutput = getName() + " ::\n";
        for (Scannable member : groupMembers) {
            membersOutput += member.toFormattedString() + "\n";
        }

        String[] originalInputNames = getInputNames();
        String[] namesToSplitOn = getInputNames();
        String[] names = getGroupMemberNames();
        String[] extras = getExtraNames();

        // FIXME regex-based splitting of membersOutput is broken if one group member name is a substring of another - e.g. "col_y" and "col_yaw"

        if (originalInputNames.length + extras.length == 0) {
            return membersOutput;
        }

        if (extras.length > 0) {
            namesToSplitOn = (String[]) ArrayUtils.addAll(namesToSplitOn, extras);
        }

        // find the longest name, to help with formatting the output
        int longestName = 0;
        for (String objName : namesToSplitOn) {
            if (objName.length() > longestName) {
                longestName = objName.length();
            }
        }
        namesToSplitOn = (String[]) ArrayUtils.add(namesToSplitOn, getName());
        namesToSplitOn = (String[]) ArrayUtils.addAll(namesToSplitOn, names);

        String regex = "";
        for (String name : namesToSplitOn) {
            regex += name + " +|";
        }
        regex = regex.substring(0, regex.length() - 1);

        String[] values = membersOutput.split(regex);

        String returnString = getName() + "::\n";
        int nextNameIndex = 0;
        for (int i = 0; i < values.length; i++) {
            String value = values[i].trim();
            if (value.startsWith(":")) {
                value = value.substring(1).trim();
            }
            if (StringUtils.containsOnly(value, ":()") || value.isEmpty()) {
                continue;
            }
            returnString += " " + StringUtils.rightPad(namesToSplitOn[nextNameIndex], longestName) + ": " + value
                    + "\n";
            nextNameIndex++;
        }
        returnString.trim();
        returnString = returnString.substring(0, returnString.length() - 1);

        return returnString;
    }

    @Override
    public void stop() throws DeviceException {
        for (Scannable member : groupMembers) {
            member.stop();
        }
    }

    /**
     * Acts as a fan-out for messages from the Scannables inside this group {@inheritDoc}
     * 
     * @see gda.observable.IObserver#update(java.lang.Object, java.lang.Object)
     */
    @Override
    public void update(Object theObserved, Object changeCode) {
        /*
         * only fan out if the notification did not already come from oneself. This is required for situations where a
         * scannable is observing its surrounding scannableGroup and the scannablegroup is observing its children.
         */
        if (theObserved != this)
            notifyIObservers(this, changeCode);
    }

    @Override
    public String[] getExtraNames() {
        // recalculate every time as these attributes may be dynamic
        String[] extraNames = new String[0];
        for (Scannable member : groupMembers) {
            extraNames = (String[]) ArrayUtils.addAll(extraNames, member.getExtraNames());
        }
        return extraNames;
    }

    @Override
    public String[] getInputNames() {
        // recalculate every time as these attributes may be dynamic
        String[] inputNames = new String[0];
        for (Scannable member : groupMembers) {
            inputNames = (String[]) ArrayUtils.addAll(inputNames, member.getInputNames());
        }
        return inputNames;
    }

    @Override
    public String[] getOutputFormat() {
        List<String> outputFormat = new Vector<String>();
        for (int i = 0; i < groupMembers.size(); i++) {
            for (int j = 0; j < groupMembers.get(i).getInputNames().length; j++) {
                outputFormat.add(groupMembers.get(i).getOutputFormat()[j]);
            }
        }
        for (int i = 0; i < groupMembers.size(); i++) {
            for (int j = 0; j < groupMembers.get(i).getExtraNames().length; j++) {
                outputFormat
                        .add(groupMembers.get(i).getOutputFormat()[j + groupMembers.get(i).getInputNames().length]);
            }
        }
        return outputFormat.toArray(new String[] {});
    }

    @Override
    public void atPointEnd() throws DeviceException {
        for (Scannable scannable : groupMembers) {
            scannable.atPointEnd();
        }
    }

    @Override
    public void atPointStart() throws DeviceException {
        for (Scannable scannable : groupMembers) {
            scannable.atPointStart();
        }
    }

    @Override
    public void atScanLineEnd() throws DeviceException {
        for (Scannable scannable : groupMembers) {
            scannable.atScanLineEnd();
        }

    }

    @Override
    public void atScanEnd() throws DeviceException {
        for (Scannable scannable : groupMembers) {
            scannable.atScanEnd();
        }
    }

    @Override
    public void atLevelMoveStart() throws DeviceException {
        for (Scannable scannable : groupMembers) {
            scannable.atLevelMoveStart();
        }
    }

    @Override
    public void atLevelStart() throws DeviceException {
        for (Scannable scannable : groupMembers) {
            scannable.atLevelStart();
        }
    }

    @Override
    public void atLevelEnd() throws DeviceException {
        for (Scannable scannable : groupMembers) {
            scannable.atLevelEnd();
        }
    }

    @Override
    public void atCommandFailure() throws DeviceException {
        for (Scannable scannable : groupMembers) {
            scannable.atCommandFailure();
        }
    }

    @Override
    public void atScanStart() throws DeviceException {
        for (Scannable scannable : groupMembers) {
            scannable.atScanStart();
        }
    }

    @Override
    public void atScanLineStart() throws DeviceException {
        for (Scannable scannable : groupMembers) {
            scannable.atScanLineStart();
        }
    }

    @Override
    public String checkPositionValid(Object illDefinedPosObject) throws DeviceException {

        Vector<Object[]> targets;
        try {
            targets = extractPositionsFromObject(illDefinedPosObject);
        } catch (Exception e) {
            return e.getMessage();
        }

        for (int i = 0; i < groupMembers.size(); i++) {
            Object[] thisTarget = targets.get(i);
            if ((thisTarget != null) && (thisTarget.length > 0) && (thisTarget[0] != null)) {
                String reason = groupMembers.get(i).checkPositionValid(thisTarget);
                if (reason != null) {
                    return reason;
                }
            }
        }
        return null;
    }

    /**
     * Updates the input names array, extra names array and format array to match the group members.
     */
    protected void setArrays() {
        // assume that the groupMembers array has been filled
        // create array of correct length
        int inputLength = 0;
        int extraLength = 0;
        int formatLength = 0;
        for (Scannable member : groupMembers) {
            inputLength += member.getInputNames().length;
            extraLength += member.getExtraNames().length;
            formatLength += member.getOutputFormat().length;
        }
        this.inputNames = new String[inputLength];
        this.extraNames = new String[extraLength];
        this.outputFormat = new String[formatLength];
        int input = 0;
        int extra = 0;
        int format = 0;
        for (Scannable member : groupMembers) {
            String[] thisInputNames = member.getInputNames();
            if (thisInputNames.length == 1) {
                this.inputNames[input] = member.getName();
                input++;
            } else {
                for (String element : thisInputNames) {
                    this.inputNames[input] = element;
                    input++;
                }
            }
            String[] thisExtraNames = member.getExtraNames();
            for (String element : thisExtraNames) {
                this.extraNames[extra] = element;
                extra++;
            }
            String[] thisFormats = member.getOutputFormat();
            for (String element : thisFormats) {
                this.outputFormat[format] = element;
                format++;
            }
        }
    }

    protected void setGroupMemberNamesArrayUsingGroupMembersList() {
        groupMemberNames = setOfGroupMemberNames(groupMembers).toArray(new String[0]);
    }

    private static Set<String> setOfGroupMemberNames(List<Scannable> groupMembers) {
        Set<String> name = new LinkedHashSet<String>();
        for (Scannable groupMember : groupMembers) {
            name.add(groupMember.getName());
        }
        return name;
    }

    @Override
    public void waitWhileBusy() throws DeviceException, InterruptedException {
        for (Scannable scannable : groupMembers) {
            scannable.waitWhileBusy();
        }
    }
}