/*
* Copyright 2016 Cel Skeggs
*
* This file is part of the CCRE, the Common Chicken Runtime Engine.
*
* The CCRE is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* The CCRE 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the CCRE. If not, see <http://www.gnu.org/licenses/>.
*/
package ccre.frc.devices;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import ccre.channel.BooleanIO;
import ccre.channel.BooleanOutput;
import ccre.channel.FloatIO;
import ccre.channel.FloatOutput;
import ccre.frc.Device;
import ccre.frc.DeviceGroup;
import ccre.frc.DeviceListPanel;
/**
* A virtual device, with helper functions for caching subscription results.
*
* @author skeggsc
*/
public class KeyedSubDevice extends DeviceGroup implements Disableable {
/**
* Gets the BooleanControlDevice for the given name, or creates it if it
* doesn't exist.
*
* @param name the unique name of this control.
* @return the fetched or created control.
*/
protected BooleanIO getBooleanInput(String name) {
return getOrCreate(BooleanIO.class, name, () -> add(new BooleanControlDevice(name)).asIO());
}
/**
* Gets the FloatControlDevice for the given name, or creates it if it
* doesn't exist.
*
* @param name the unique name of this control.
* @return the fetched or created control.
*/
protected FloatIO getFloatInput(String name) {
return getOrCreate(FloatIO.class, name, () -> add(new FloatControlDevice(name)).asIO());
}
/**
* Gets the SpinDevice for the given name, or creates it if it doesn't
* exist.
*
* @param name the unique name of this control.
* @return the fetched or created control.
*/
protected FloatIO getFloatInputSpinner(String name) {
return getOrCreate(FloatIO.class, name, () -> add(new SpinDevice(name, null)).asIO());
}
/**
* Gets the BooleanViewDevice for the given name, or creates it if it
* doesn't exist.
*
* @param name the unique name of this control.
* @return the fetched or created control.
*/
protected BooleanOutput getBooleanOutput(String name) {
return getOrCreate(BooleanOutput.class, name, () -> add(new BooleanViewDevice(name)));
}
/**
* Gets the FloatViewDevice for the given name, or creates it if it doesn't
* exist.
*
* @param name the unique name of this control.
* @return the fetched or created control.
*/
protected FloatOutput getFloatOutput(String name) {
return getOrCreate(FloatOutput.class, name, () -> add(new FloatViewDevice(name)));
}
private synchronized <T> T getOrCreate(Class<T> cls, String name, Supplier<T> init) {
Map<String, T> ht = getClassMap(cls);
T out = ht.get(name);
if (out == null) {
out = cls.cast(init.get());
ht.put(name, out);
}
return out;
}
private <T> Map<String, T> getClassMap(Class<T> cls) {
HashMap<String, ?> map = cmap.get(cls);
if (map == null) {
map = new HashMap<>();
cmap.put(cls, map);
}
return convertMap(map, String.class, cls);
}
@SuppressWarnings("unchecked")
private static <K, V> Map<K, V> convertMap(Map<?, ?> map, Class<K> key, Class<V> value) {
return Collections.checkedMap((Map<K, V>) map, key, value);
}
private HashMap<Class<?>, HashMap<String, ?>> cmap = new HashMap<>();
private DeviceListPanel master;
private boolean wasAddedToMaster = false;
/**
* Creates a new CANTalonDevice described as name with a specified
* DeviceListPanel to contain this device.
*
* Make sure to call addToMaster - don't add this directly.
*
* @param name how to describe the CANTalonDevice.
* @param master the panel that contains this.
* @see #addToMaster()
*/
public KeyedSubDevice(String name, DeviceListPanel master) {
add(new HeadingDevice(name));
this.master = master;
}
/**
* Add this device to its master panel, or do nothing if this has already
* been done.
*
* @return this KeyedSubDevice, for method chaining purposes.
*/
public synchronized KeyedSubDevice addToMaster() {
if (!wasAddedToMaster) {
wasAddedToMaster = true;
master.add(this);
}
return this;
}
@Override
public void notifyDisabled(boolean disabled) {
for (Device d : this) {
if (d instanceof Disableable) {
((Disableable) d).notifyDisabled(disabled);
}
}
}
}