JTango is Java implementation of Tango-Controls. Tango-Controls is a SCADA framework widely used at large research facilities across Europe. As it was developed by enthusiast with scientific background rather than software developers it is usually the case to see a code like this:
public String[] getPollStatus(final String deviceName) throws DevFailed {
xlogger.entry();
final String fullDeviceName = TangoUtil.getfullNameForDevice(deviceName);
final List<String> pollStatus = new ArrayList<String>();
boolean deviceFound = true;
for (final DeviceClassBuilder deviceClass : classList) {
int nbDevices = 0;
for (final DeviceImpl device : deviceClass.getDeviceImplList()) {
if (fullDeviceName.equalsIgnoreCase(device.getName())) {
for (final CommandImpl command : device.getCommandList()) {
if (command.isPolled()) {
final StringBuilder buf = buildPollingStatus(device, command);
pollStatus.add(buf.toString());
}
}
for (final AttributeImpl attribute : device.getAttributeList()) {
if (attribute.isPolled()) {
final StringBuilder buf = buildPollingStatus(device, attribute);
pollStatus.add(buf.toString());
}
}
break;
}
nbDevices++;
}
if (nbDevices == deviceClass.getDeviceImplList().size()) {
deviceFound = false;
} else {
deviceFound = true;
break;
}
}
if (!deviceFound) {
DevFailedUtils.throwDevFailed(ExceptionMessages.DEVICE_NOT_FOUND, deviceName + DOES_NOT_EXIST);
}
String[] ret;
if (pollStatus.isEmpty()) {
ret = new String[0];
// ret[0] =
// "Dear Client,\nI am in a great mood today, I ain't gonna bug.\nYour favourite server :)";
} else {
ret = pollStatus.toArray(new String[pollStatus.size()]);
}
xlogger.exit();
return ret;
}
After a very carefull look onto this code one can understand that it does several things simulateneously, namely:
- It tries to find a device (aka some entity). For this it iterates over a collection (
deviceClass.getDeviceImplList()
) calculating a counter (nbDevices
) and finally setting a boolean flag (deviceFound
) - It builds a poll status for commands and attributes of the found device iterating over its collection of commands and attributes and checking if they are polled
- It returns build result or empty array
Having these three points we can now refactor this code into this one:
public String[] getPollStatus(final String deviceName) throws DevFailed {
xlogger.entry(deviceName);
String[] ret = new PollStatusCommand(deviceName, classList).call();
xlogger.exit(ret);
return ret;
}
Basically we delegate execution of the method to a helper object:
public class PollStatusCommand implements Callable<String[]> {...}
Note that this class implements standard Callable interace and can be easily used in asynchronuous calculations. The main entry point of this class is method call:
public String[] call() throws DevFailed {
final List<String> result = new ArrayList<String>();
final DeviceImpl device = tryFindDeviceByName(deviceName);
result.addAll(getPolledStatuses(device, device.getCommandList()));
result.addAll(getPolledStatuses(device, device.getAttributeList()));
//will be String[0] if result is empty
return result.toArray(new String[result.size()]);
}
Now it is straightforward to understand what this method is doing:
- It tries to find a device by name. Here is the implementation:
private DeviceImpl tryFindDeviceByName(final String deviceName) throws DevFailed {
//Firstly it concatenates all the possible devices into a single collection using Google Guava
// (once we totally switch to Java 8 this code can be replaced with Stream API)
List<DeviceImpl> allDevices = Lists.newLinkedList(Iterables.concat(Iterables.transform(classList, new Function<DeviceClassBuilder, List<DeviceImpl>>() {
@Override
public List<DeviceImpl> apply(DeviceClassBuilder input) {
return input.getDeviceImplList();
}
})));
// now try to find a device by name
Optional<DeviceImpl> device = Iterables.tryFind(allDevices, new Predicate<DeviceImpl>() {
@Override
public boolean apply(DeviceImpl input) {
return deviceName.equalsIgnoreCase(input.getName());
}
});
//if not found try to find a device by alias using external DataBase
if (!device.isPresent()) {
//try to find device by alias
device = Iterables.tryFind(allDevices, new Predicate<DeviceImpl>() {
@Override
public boolean apply(DeviceImpl input) {
try {
//returns alias or deviceName
return TangoUtil.getfullNameForDevice(deviceName).equalsIgnoreCase(input.getName());
} catch (DevFailed devFailed) {
logger.warn("Failed to get full name for device {}", deviceName);
DevFailedUtils.logDevFailed(devFailed, logger);
return false;
}
}
});
}
//if not found throw an exception
if (!device.isPresent()) {
DevFailedUtils.throwDevFailed(ExceptionMessages.DEVICE_NOT_FOUND, deviceName + AdminDevice.DOES_NOT_EXIST);
}
return device.get();
}
- build result:
private Collection<String> getPolledStatuses(final DeviceImpl device, List<? extends IPollable> pollableList) {
Collection<? extends IPollable> polledCommands = Collections2.filter(pollableList, new Predicate<IPollable>() {
@Override
public boolean apply(IPollable pollable) {
return pollable.isPolled();
}
});
return Collections2.transform(polledCommands, new Function<IPollable, String>() {
@Override
public String apply(IPollable pollable) {
return buildPollingStatus(device, pollable);
}
});
}
The logic of each method in this class is quite simple but very powerfull: first aggregate all the required entities and then apply a function to them!
Having this class we can easily unit test its behaviour:
public class PollStatusCommandTest {
private List<DeviceClassBuilder> classList = new ArrayList<>();
{
DeviceClassBuilder mockDeviceClassBuilder = mock(DeviceClassBuilder.class);
List<DeviceImpl> deviceList = new ArrayList<>();
createNonPolledDevice(deviceList);
createPolledDevice(deviceList);
doReturn(deviceList).when(mockDeviceClassBuilder).getDeviceImplList();
classList.add(mockDeviceClassBuilder);
}
private void createNonPolledDevice(List<DeviceImpl> deviceList) {
//device without any polled entities
DeviceImpl mockDeviceImpl = mock(DeviceImpl.class);
doReturn("1/1/1").when(mockDeviceImpl).getName();
deviceList.add(mockDeviceImpl);
}
private void createPolledDevice(List<DeviceImpl> deviceList) {
DeviceImpl polledDevice = mock(DeviceImpl.class);
doReturn("1/1/2").when(polledDevice).getName();
CommandImpl notPolledCommand = mock(CommandImpl.class);
doReturn(false).when(notPolledCommand).isPolled();
CommandImpl polledCommand = mock(CommandImpl.class);
doReturn(true).when(polledCommand).isPolled();
doReturn("").when(polledCommand).getLastDevFailed();
AttributeImpl notPolledAttribute = mock(AttributeImpl.class);
doReturn(false).when(notPolledAttribute).isPolled();
AttributeImpl polledAttribute = mock(AttributeImpl.class);
doReturn(true).when(polledAttribute).isPolled();
doReturn("").when(polledAttribute).getLastDevFailed();
doReturn(Lists.newArrayList(polledCommand, notPolledCommand)).when(polledDevice).getCommandList();
doReturn(Lists.newArrayList(notPolledAttribute, polledAttribute)).when(polledDevice).getAttributeList();
deviceList.add(polledDevice);
}
@Test
public void noPolledCommandsNorAttributes() throws Exception {
PollStatusCommand instance = new PollStatusCommand("1/1/1", classList);
String[] result = instance.call();
assertArrayEquals(new String[0], result);
}
@Test
public void polledCommandsAndAttributes() throws Exception {
PollStatusCommand instance = new PollStatusCommand("1/1/2", classList);
String[] result = instance.call();
assertEquals(2, result.length);
}
@Test
public void nonExistingDevice() throws DevFailed {
try {
new PollStatusCommand("1/1/3", classList).call();
} catch (DevFailed devFailed) {
assertEquals("API_DeviceNotFound", devFailed.getMessage());
}
}
}
Hope this can be useful!