/* * 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.List; import java.util.Vector; import javax.swing.SwingWorker; import alma.acs.gui.util.threadsupport.EDTExecutor; import alma.alarmsystem.clients.AlarmCategoryClient; import alma.alarmsystem.clients.CategoryClient; import alma.alarmsystem.clients.alarm.AlarmClientException; import cern.laser.client.data.Alarm; /** * Extends <code>AlarmsContainer</code> to cope with reduced alarms. * <P> * Methods of this class ensure that the model is changed from inside the swing EDT. * <BR>Methods that queries the model instead are not forced to be executed inside the EDT and * immediately return the requested value * (i.e. the caller must ensure to query from inside the EDT). * * <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 AlarmsReductionContainer extends AlarmsContainer { /** * The index when the reduction rules are in place * <P> * Each item in the vector represents the ID of the entry * shown in a table row when the reduction rules are used. */ private final List<String> indexWithReduction = Collections.synchronizedList(new Vector<String>()); /** * The <code>CategoryClient</code> to ask for parents/children * while reducing alarms. * <P> * It can be <code>null</code> so needs to be checked before * invoking methods. */ private AlarmCategoryClient categoryClient=null; /** * Constructor * * @param max * * @see {@link AlarmsContainer} */ public AlarmsReductionContainer(int max) { super(max); } /** * Return the number of alarms in the container depending * if the reduction rules are applied or not * * @param <code>true</code> if the reduction rules are applied * @return The number of alarms in the container */ public int size(boolean reduced) { if (!reduced) { return super.size(); } else { return indexWithReduction.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. * <P> * @param entry The not <code>null</code> entry to add * @throw {@link AlarmContainerException} If the entry is already in the container */ public void add(final AlarmTableEntry entry) throws AlarmContainerException { super.add(entry); addAlarm(entry); } /** * Add an alarm to the reduction container * * @param alarm The alarm to add to the container */ private void addAlarm(final AlarmTableEntry alarm) { if (!alarm.getStatus().isReduced()) { EDTExecutor.instance().execute(new Runnable() { @Override public void run() { indexWithReduction.add(alarm.getAlarmId()); } }); } hideReducedChildren(alarm); } /** * Hide the active alarms of this entry. * * @param entry The not <code>null</code> entry to hide active children */ private void hideReducedChildren(final AlarmTableEntry entry) { if (entry==null) { throw new IllegalArgumentException("The entry can't be null"); } if (categoryClient==null) { return; } new SwingWorker<Alarm[], Object>() { @Override protected Alarm[] doInBackground() throws Exception { // slow task return getReducedChildren(entry.getAlarmId(),entry.isNodeParent()); } @Override protected void done() { // Update the model Alarm[] children=null; try { children=get(); } catch (Throwable t) { System.out.println("Error getting the children of "+entry); t.printStackTrace(); return; } if (children!=null) { for (Alarm al: children) { indexWithReduction.remove(al.getAlarmId()); } } super.done(); } }.execute(); } /** * Get the children of a reduction of the alarm with the passed ID. * <P> * This method asks the {@link CategoryClient} for the children of alarm i.e. * it implies a CORBA call and must not be run into the swing EDT. * * @param alarmID The not <code>null</code> and not empty ID of the alarm * @param nodeReduction <code>true</code> if it is a node reduction * @return The array of children of the alarm reduced by the alarm with the passed ID * @throws AlarmClientException in case of error from the {@link CategoryClient} */ private Alarm[] getReducedChildren(final String alarmID, final boolean nodeReduction) throws AlarmClientException { if (alarmID==null || alarmID.isEmpty()) { throw new IllegalArgumentException("Invalid null or empty alarm ID"); } return categoryClient.getChildren(alarmID, nodeReduction); } /** * Set the <code>CategoryClient</code> * * @param client The <code>CategoryCLient</code>; it can be <code>null</code>. */ public synchronized void setCategoryClient(AlarmCategoryClient client) { this.categoryClient=client; } /** * 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 <code>AlarmTableEntry<code> in the given position */ public AlarmTableEntry get(int pos, boolean reduced) { if (!reduced) { return super.get(pos); } String ID = indexWithReduction.get(pos); AlarmTableEntry ret = get(ID); if (ret==null) { throw new IllegalStateException("Inconsistent state of reduced container pos="+pos+", size="+indexWithReduction.size()); } return ret; } /** * Remove all the elements in the container */ public void clear() { EDTExecutor.instance().execute(new Runnable() { @Override public void run() { indexWithReduction.clear(); } }); super.clear(); } /** * Remove the entry for the passed alarm * * @param alarm The alarm whose entry must be removed * @throws AlarmContainerException If the alarm is not in the container */ public void remove(final AlarmTableEntry alarm) throws AlarmContainerException { if (alarm==null) { throw new IllegalArgumentException("The alarm can't be null"); } EDTExecutor.instance().execute(new Runnable() { @Override public void run() { int pos = indexWithReduction.indexOf(alarm.getAlarmId()); if (pos>=0) { indexWithReduction.remove(pos); } } }); super.remove(alarm); } /** * Remove the oldest entry in the container * * @return The removed item * @throws AlarmContainerException If the container is empty */ public AlarmTableEntry removeOldest() throws AlarmContainerException { final AlarmTableEntry removedEntry = super.removeOldest(); if (removedEntry==null) { throw new IllegalStateException("The oldest alarm can't be null!"); } try { EDTExecutor.instance().executeSync(new Runnable() { @Override public void run() { indexWithReduction.remove(removedEntry.getAlarmId()); } }); } catch (Throwable t) { throw new AlarmContainerException("Error removing oldest alarm from the alarm ("+removedEntry.getAlarmId()+") reduction container",t); } return removedEntry; } /** * 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 */ public void replace(AlarmTableEntry newAlarm) throws AlarmContainerException { super.replace(newAlarm); int pos=indexWithReduction.indexOf(newAlarm.getAlarmId()); if (pos>=0) { String key = indexWithReduction.remove(pos); indexWithReduction.add(0,key); if (newAlarm.getStatus().isActive()) { hideReducedChildren(newAlarm); } else { showActiveChildren(newAlarm,pos); } } else { // This should never happen but we don't want to lose alarms addAlarm(newAlarm); } } /** * Show the active children of the passed alarms * * @param alarm The alarm whose active children must be displayed * @param pos The position in the table where the active children must be shown */ private void showActiveChildren(final AlarmTableEntry alarm, int pos) { if (categoryClient==null) { return; } new SwingWorker<Alarm[], Object>() { @Override protected Alarm[] doInBackground() throws Exception { Alarm[] ret= getActiveChildren(alarm.getAlarmId(), alarm.isNodeParent()); return ret; } @Override protected void done() { Alarm[] als=null; try { als=get(); } catch (Throwable t) { System.err.println("Error getting the active children list"+t.getMessage()); t.printStackTrace(); return; } if (als!=null) { for (Alarm al: als) { AlarmTableEntry newEntry = AlarmsReductionContainer.this.get(al.getAlarmId()); if (newEntry!=null) { if (indexWithReduction.indexOf(al.getAlarmId())<0) { indexWithReduction.add(al.getAlarmId()); } } } } super.done(); } }.execute(); } /** * Get the list of active children of a reduction of the alarm with the passed ID. * <P> * This method asks the {@link CategoryClient} for the children of alarm i.e. * it implies a CORBA call and must not be run into the swing EDT. * * @param alarmID The not <code>null</code> and not empty ID of the alarm * @param nodeReduction <code>true</code> if it is a node reduction * @return The array of children of the alarm reduced by the alarm with the passed ID * @throws AlarmClientException in case of error from the {@link CategoryClient} */ private Alarm[] getActiveChildren(final String alarmID, final boolean nodeReduction) throws AlarmClientException { if (alarmID==null || alarmID.isEmpty()) { throw new IllegalArgumentException("Invalid null or empty alarm ID"); } return categoryClient.getActiveChildren(alarmID, nodeReduction); } /** * @return the categoryClient */ public synchronized AlarmCategoryClient getCategoryClient() { return categoryClient; } /** * Check if the container has alarm not yet acknowledged. * <P> * if there are active 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. * * @return -1 if there are not alarm to acknowledge; * the highest priority of the alarm to acknowledge * @see AlarmContainer#hasNotAckAlarms() */ public int hasNotAckAlarms(boolean reduced) throws AlarmContainerException { if (!reduced) { return super.hasNotAckAlarms(); } int ret=Integer.MAX_VALUE; synchronized (indexWithReduction) { for (String id: indexWithReduction) { AlarmTableEntry entry = get(id); if (entry.getStatus().isActive() && entry.isNew() && entry.getPriority()<ret) { ret=entry.getPriority(); } if (ret==0) { break; } } } return (ret==Integer.MAX_VALUE)?-1:ret; } }