/* * ALMA - Atacama Large Millimiter Array (c) European Southern Observatory, 2007 * * This library 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 2.1 of the License, or (at your option) * any later version. * * This library 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 this library; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ package alma.acsplugins.alarmsystem.gui.table; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import alma.acs.gui.util.threadsupport.EDTExecutor; /** * A container for the alarms as needed by the AlarmTableModel * <P> * It is composed of 2 collections: * <OL> * <LI> the <code>HashMap</code> stores each entry accessed by its alarmID (the key) * <LI> the <code>Vector</code> of <code>Strings</code> used to remember the position of * each alarm when the max number of alarms has been reached * It also allows to access the alarms by row * </OL> * Basically the array stores the alarmID of each row in the table. * The content of the row i.e. the Alarm, is then obtained by the * HashMap passing the alarmID. * <BR> * In this way it is possible to get the entry of a row by getting * the key from the Vector. And it is possible to get an alarm * from the HashMap. * <P> * Synchronization is done by thread confinement (inside the EDT). * Invocation of read only methods must be done inside the EDT. * * @author acaproni * */ public class AlarmsContainer { /** * The exception generated by the Alarm Container * * @author acaproni * */ public class AlarmContainerException extends Exception { /** * Constructor * * @see java.lang.Exception */ public AlarmContainerException() { super(); } /** * Constructor * * @see java.lang.Exception */ public AlarmContainerException(String message, Throwable cause) { super(message, cause); } /** * Constructor * * @see java.lang.Exception */ public AlarmContainerException(String message) { super(message); } /** * Constructor * * @see java.lang.Exception */ public AlarmContainerException(Throwable cause) { super(cause); } } /** * The entries in the table */ private Map<String, AlarmTableEntry> entries = Collections.synchronizedMap(new HashMap<String, AlarmTableEntry>()); /** * The index when the reduction rules are not applied * <P> * Each item in the vector represents the ID of the entry * shown in a table row when the reduction rules are not used. */ private final List<String> index = Collections.synchronizedList(new Vector<String>()); /** * The maximum number of alarms to store in the container */ private final int maxAlarms; /** * Build an AlarmContainer * * @param max The max number of alarms to store in the container * @param panel The <code>AlarmPanel</code> */ protected AlarmsContainer(int max) { maxAlarms=max; } /** * Return the number of alarms in the container. * * @return The number of alarms in the container */ public int size() { return index.size(); } /** * Add an entry (i.e a alarm) in the collection. * <P> * If there is no room available in the container, * an exception is thrown. * Checking if there is enough room must be done by the * caller. * * @param alarm The not null entry to add * @throw {@link AlarmContainerException} If the entry is already in the container */ protected void add(final AlarmTableEntry entry) throws AlarmContainerException { if (entry==null) { throw new IllegalArgumentException("The entry can't be null"); } if (entry.getAlarmId()==null ||entry.getAlarmId().length()==0) { throw new IllegalStateException("The alarm ID is invalid"); } if (index.size()>=maxAlarms) { throw new ArrayIndexOutOfBoundsException("Container full"); } if (entries.containsKey(entry.getAlarmId())) { throw new AlarmContainerException("Alarm already in the Container"); } if (index.contains(entry.getAlarmId())) { // entries contains the key but index not!!!! throw new IllegalStateException("Inconsistency between index and entries"); } try { EDTExecutor.instance().executeSync(new Runnable() { @Override public void run() { index.add(entry.getAlarmId()); entries.put(entry.getAlarmId(), entry); } }); } catch (Throwable t) { throw new AlarmContainerException("Error adding alarm",t); } } /** * Check if an alarm with the given ID is in the container * * @param alarmID The ID of the alarm * @return true if an entries for an alarm with the given ID exists * in the container */ public boolean contains(String alarmID) { if (alarmID==null) { throw new IllegalArgumentException("The ID can't be null"); } return entries.containsKey(alarmID); } /** * Return the entry in the given position * * @param pos The position of the alarm in the container * @param reduced <code>true</code> if the alarms in the table are reduced * @return The AlarmTableEntry in the given position */ public AlarmTableEntry get(final int pos) { if (pos<0 || pos>index.size()) { throw new IndexOutOfBoundsException("Can't acces item at pos "+pos+": [0,"+index.size()+"]"); } String ID = index.get(pos); AlarmTableEntry entry=get(ID); if (entry==null) { throw new IllegalStateException("Inconsistent state of AlarmsContainer: entry is null at pos "+pos); } return entry; } /** * Return the entry with the given ID * * @param id The not null ID of the alarm in the container * @return The AlarmTableEntry with the given position * <code>null</code>if the container does not contain an entry for the given id */ public AlarmTableEntry get(String id) { if (id==null) { throw new IllegalArgumentException("The ID can't be null"); } return entries.get(id); } /** * Remove all the elements in the container */ protected void clear() { EDTExecutor.instance().execute(new Runnable() { @Override public void run() { index.clear(); entries.clear(); } }); } /** * Remove the oldest entry in the container * * @return The removed item * @throws AlarmContainerException If the container is empty */ protected AlarmTableEntry removeOldest() throws AlarmContainerException { if (index.size()==0) { throw new AlarmContainerException("The container is empty"); } class OldRemover implements Runnable { // The entry removed private AlarmTableEntry removedEntry; public AlarmTableEntry getRemovedEntry() { return removedEntry; } public void run() { String ID = index.remove(0); if (ID==null) { throw new IllegalStateException("The index vector returned a null item"); } removedEntry = entries.remove(ID); } } OldRemover remover = new OldRemover(); try { EDTExecutor.instance().executeSync(remover); } catch (Throwable t) { throw new IllegalStateException("Error removing the oldest entry",t); } AlarmTableEntry ret = remover.getRemovedEntry(); if (ret==null) { throw new IllegalStateException("The entries HashMap contains a null entry"); } return ret; } /** * Remove the entry for the passed alarm * * @param entry The alarm whose entry must be removed * @throws AlarmContainerException If the alarm is not in the container */ protected void remove(final AlarmTableEntry entry) throws AlarmContainerException { if (entry==null) { throw new IllegalArgumentException("The alarm can't be null"); } try { EDTExecutor.instance().executeSync(new Runnable() { @Override public void run() { String ID=entry.getAlarmId(); int pos=index.indexOf(ID); if (pos<0) { IllegalStateException iste =new IllegalStateException("Alarm not in the container"); throw iste; } index.remove(pos); AlarmTableEntry oldEntry = entries.remove(ID); if (oldEntry==null) { throw new IllegalStateException("The ID was in index but not in entries"); } } }); } catch (Throwable t) { throw new AlarmContainerException("Error removing alarm",t); } } /** * Remove all the inactive alarms of a given type. * <P> * If the type is INACTIVE all inactive alarms are deleted * regardless of their priority * * @param type The type of the inactive alarms * @return The number of alarms removed */ protected int removeInactiveAlarms(final AlarmGUIType type) throws AlarmContainerException { if (type==null) { throw new IllegalArgumentException("The type can't be null"); } /** * A class to remove the alarm of the given type inside the EDT * @author acaproni * */ class InactiveAlarmsRemover implements Runnable { /** * The number of alarms removed */ private int removed=0; /** * * @return The number of removed alarms */ public int getRemoved() { return removed; } @Override public void run() { Vector<String> keys = new Vector<String>(); for (String key: entries.keySet()) { keys.add(new String(key)); } for (String key: keys) { AlarmTableEntry alarm = entries.get(key); if (alarm==null) { throw new IllegalStateException("Got a null alarm for key "+key); } if (alarm.getStatus().isActive()) { continue; } if (type==AlarmGUIType.INACTIVE || alarm.getPriority()==type.id) { // Remove the alarm try { remove(alarm); removed++; } catch (Throwable t) { throw new IllegalStateException("Exception got removing "+alarm.getAlarmId()+" from the AlarmsContainer",t); } } } } } InactiveAlarmsRemover remover = new InactiveAlarmsRemover(); try { EDTExecutor.instance().executeSync(remover); } catch (Throwable t) { throw new AlarmContainerException("Error removing inactive alarms of type"+type,t); } return remover.getRemoved(); } /** * Replace the alarm in a row with passed one. * <P> * The entry to replace the alarm is given by the alarm ID of the parameter. * * @param newAlarm The not null new alarm * @throws AlarmContainerException if the entry is not in the container */ protected void replace(final AlarmTableEntry newAlarm) throws AlarmContainerException { if (newAlarm==null) { throw new IllegalArgumentException("The alarm can't be null"); } try { EDTExecutor.instance().executeSync(new Runnable() { @Override public void run() { int pos = index.indexOf(newAlarm.getAlarmId()); if (pos<0) { throw new IllegalStateException("Entry not present in the container"); } AlarmTableEntry entry = entries.get(newAlarm.getAlarmId()); if (entry==null) { // There was no entry for this ID throw new IllegalStateException("Inconsistent state of index and entries"); } entry.updateAlarm(newAlarm); // If active, move the item in the head of the container if (newAlarm.getStatus().isActive()) { String key = index.remove(pos); index.add(0,key); } } }); } catch (Throwable t) { throw new AlarmContainerException("Error replacing entry "+newAlarm,t); } } /** * Check if the container has alarm not yet acknowledged. * <P> * If there are alarms to be acknowledged by the user, this * method returns the highest of their priorities. * Note that for alarm system the highest priority * is 0 and lowest is 3. * * @return -1 if there are not alarm to acknowledge; * the highest priority of the alarm to acknowledge * @throws AlarmContainerException in case of error getting the highest priority to ack */ protected int hasNotAckAlarms() throws AlarmContainerException { class AckAlarmsChecker implements Runnable { int higestPriorityToAck=Integer.MAX_VALUE; /** * * @return The highest priority to ack * or -1 if there are not alarm to ack */ public int getHigestPriorityToAck() { return (higestPriorityToAck==Integer.MAX_VALUE)?-1:higestPriorityToAck; } @Override public void run() { higestPriorityToAck=Integer.MAX_VALUE; Set<String> keys=entries.keySet(); for (String key: keys) { AlarmTableEntry entry=entries.get(key); if (entry.getStatus().isActive() && entry.isNew() && entry.getPriority()<higestPriorityToAck) { higestPriorityToAck=entry.getPriority(); } if (higestPriorityToAck==0) { break; } } } } AckAlarmsChecker checker = new AckAlarmsChecker(); try { EDTExecutor.instance().executeSync(checker); } catch (Throwable t) { throw new AlarmContainerException("Error getting the alarm highest priority of the alarms to ACK",t); } return checker.getHigestPriorityToAck(); } }