/* * (C) Copyright 2015 by fr3ts0n <erwin.scheuch-heilig@gmx.at> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ package com.fr3ts0n.pvs; import org.apache.log4j.Logger; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; // For logging ... /** * single Process variable containing all attributes of a process object * * @author $Author: erwin $ */ @SuppressWarnings("rawtypes") public class ProcessVar extends HashMap implements PvChangeListener, Serializable { /** * */ private static final long serialVersionUID = 7072161686290674442L; /** name of key attribute */ Object KeyAttribute = null; /** default key attribute name */ static final String DEF_KEYNAME = "key"; /** time of last change * */ public long lastChange = 0; /** type of last change * */ public int lastChangeType = PvChangeEvent.PV_ADDED; /** default change action */ protected int defaultAction = PvChangeEvent.PV_NOACTION; /** flag if to allow ChangeEvents to be fired */ protected boolean allowEvents = false; /** list of process var change listeners */ private transient Map<PvChangeListener, Integer> PvChangeListeners = Collections.synchronizedMap(new HashMap<PvChangeListener, Integer>()); /** Map of attribute changes */ private Map<Object, PvChangeEvent> changes = Collections.synchronizedMap(new HashMap<Object, PvChangeEvent>()); /** The logger object */ public static Logger log = Logger.getLogger(ProcessVar.class.getPackage().getName()); public ProcessVar() { clear(); } public ProcessVar(int initialSize) { super(initialSize); clear(); } /** construct with a existing Map */ public ProcessVar(Map map) { if (map != null) { putAll(map); } } /** * put all attributes from map into current ProcessVar * - use action for all notifications * * @param map data map to put into ProcessVar * @param action Action code to be used for notifications * @param allowChildEvents are child events (for each attribute) allowed? */ @SuppressWarnings("unchecked") public synchronized void putAll(Map map, int action, boolean allowChildEvents) { // remember flag for event creation boolean oldAllowEvents = allowEvents; // disable event creation for each map field // (we want to send one single event after the full map is handled) allowEvents = allowChildEvents; // remember old default action int oldAction = defaultAction; // set new action as the default action for all fields defaultAction = action; // put all fields to hashmap (using default action) super.putAll(map); // restore old default action defaultAction = oldAction; // enable event creation again allowEvents = oldAllowEvents; // now fire the one and only event for this map change firePvChanged(new PvChangeEvent(this, getKeyAttribute(), map.values().toArray(), action)); } /** * put all attributes from map into current ProcessVar * - use action for all notifications * * @param map data map to put into ProcessVar * @param action Action code to be used for notifications */ public synchronized void putAll(Map map, int action) { putAll(map, action, true); } /** * put all attributes from map into current ProcessVar * - use default action for all notifications * * @param map data map to put into ProcessVar */ @Override public synchronized void putAll(Map map) { putAll(map, defaultAction); } /** * handler for process variable changes * forwarding of child process variables to current handler */ public synchronized void pvChanged(PvChangeEvent event) { log.trace(toString() + ":Child PvChange:" + event.toString()); firePvChanged(new PvChangeEvent(this, ((ProcessVar) event.getSource()).getKeyValue(), event.getSource(), event.getType() | PvChangeEvent.PV_CHILDCHANGE)); } /** return String representation */ @Override public String toString() { return (getClass().getName() + "[" + getKeyValue() + "]"); } /** * set attribute of selected key to selected value * overridden method to allow notification of process var changes * * @param key key of attribute * @param value value of attribute * @param action type of action event @see PvChangeEvent * @return previous value of attribute */ @SuppressWarnings("unchecked") public synchronized Object put(Object key, Object value, int action) { Object oldvalue = null; // if new value is a child process variable, try to re-use previous one ... if (value instanceof ProcessVar) { // get previous PV oldvalue = get(key); if (oldvalue != null && oldvalue instanceof ProcessVar) { // PV is existing ((HashMap) oldvalue).putAll((Map) value); } else { // this will be a new child PV oldvalue = super.put(key, value); } } else { // NON child PV oldvalue = super.put(key, value); } if (oldvalue == null) { // new attribute -> PV_ADDED if (value != null) { action |= PvChangeEvent.PV_ADDED; // if we add a new child process variable, add listener for child if (value instanceof ProcessVar) { ((ProcessVar) value).addPvChangeListener(this); } } } else { // Attribute has changed -> PV_MODIFIED if (!oldvalue.equals(value)) { action |= PvChangeEvent.PV_MODIFIED; } else { // Attribute MANUAL_MOD confirmed -> PV_CONFIRMED PvChangeEvent lstChange = (PvChangeEvent) changes.get(key); if (lstChange != null && (lstChange.getType() & PvChangeEvent.PV_MANUAL_MOD) != 0) { action |= PvChangeEvent.PV_CONFIRMED; } } } firePvChanged(new PvChangeEvent(this, key, value, action)); // .. and return return (oldvalue); } /** * set attribute of selected key to selected value * overridden method to allow notification of process var changes * * @param key key of attribute * @param value value of attribute * @return previous value of attribute */ @Override public synchronized Object put(Object key, Object value) { // find out the type of the action int action = containsKey(key) ? defaultAction : PvChangeEvent.PV_ADDED; // and perform the put operation return (put(key, value, action)); } /** * get attribute of selected key * overridden method to allow synchronized access * * @param key key of attribute * @return value of attribute */ @Override public synchronized Object get(Object key) { return (super.get(key)); } /** * get attribute of selected key as int value * overridden method to allow synchronized access * * @param key key of attribute * @return value of attribute */ public synchronized int getAsInt(Object key) { int result = 0; Object val = get(key); try { if (val != null) { result = Integer.valueOf(val.toString()).intValue(); } } catch (NumberFormatException e) { // Intentionally do nothing } return (result); } /** * set attribute of selected key to selected value * overridden method to allow notification of process var changes * * @param key key of attribute * @param value value of attribute */ public synchronized void putAsInt(Object key, int value) { put(key, new Integer(value)); } /** * remove attribute with selected key * overridden method to allow notification of process var changes * * @param key key of attribute * @return previous value of attribute */ @Override public synchronized Object remove(Object key) { Object result = super.remove(key); if (result != null) { firePvChanged(new PvChangeEvent(this, key, null, PvChangeEvent.PV_DELETED)); } // if old object was a process variable, we need to remove change listener if (result != null && result instanceof ProcessVar) { ((ProcessVar) result).removePvChangeListener(this); } return (result); } /** * remove all attributes from process var * overridden clear method to allow notification of process var changes */ @Override public synchronized void clear() { // now really clear the hashmap super.clear(); // notify listeners of removal firePvChanged(new PvChangeEvent(this, null, null, PvChangeEvent.PV_CLEARED)); } /** get object/name of key attribute */ public Object getKeyAttribute() { return (KeyAttribute != null ? KeyAttribute : DEF_KEYNAME); } /** set object/name of key attribute */ public void setKeyAttribute(Object newKeyAttribute) { KeyAttribute = newKeyAttribute; } /** get value of key attribute */ public Object getKeyValue() { return (get(getKeyAttribute())); } /** set value of key attribute */ public void setKeyValue(Object newKeyValue) { put(getKeyAttribute(), newKeyValue); } /** * ensure there is a list of PvChangeListeners * * it may be null, if PV has been de-serialized */ private void ensurePvChangeListeners() { if (PvChangeListeners == null) PvChangeListeners = new HashMap<PvChangeListener, Integer>(); } /** * Handling for list of PvChangeListeners */ /** remove listener for Pv changes */ public synchronized void removePvChangeListener(PvChangeListener l) { ensurePvChangeListeners(); PvChangeListeners.remove(l); allowEvents = !PvChangeListeners.isEmpty(); log.trace("-PvListener:" + toString() + "->" + String.valueOf(l)); } /** * add listener for Pv changes with specified change events * * @param l event listener to be registered * @param eventMask events the listener wants to be notified about */ public synchronized void addPvChangeListener(PvChangeListener l, int eventMask) { ensurePvChangeListeners(); PvChangeListeners.put(l, new Integer(eventMask)); allowEvents = true; log.trace("+PvListener:" + toString() + "->" + String.valueOf(l)); } /** * add listener for Pv changes * * @param l event listener to be registered */ public synchronized void addPvChangeListener(PvChangeListener l) { addPvChangeListener(l, PvChangeEvent.PV_ALLEVENTS); } /** * fire a Pv Change event * * @param e the event to be fired */ public synchronized void firePvChanged(PvChangeEvent e) { if (allowEvents && e.getType() != PvChangeEvent.PV_NOACTION) { log.trace("PvChange:" + e.toString()); Integer evtMask; Map.Entry curr; ensurePvChangeListeners(); // loop through all registered listeners ... Set entries = PvChangeListeners.entrySet(); Iterator it = entries.iterator(); while (it.hasNext()) { curr = (Map.Entry) it.next(); if (curr.getKey() != null && curr.getKey() != this) { // check if listener wants to be notified by this event evtMask = (Integer) curr.getValue(); if ((evtMask.intValue() & e.getType()) != 0) { log.trace("Notify:" + curr); ((PvChangeListener) curr.getKey()).pvChanged(e); } } } // set time and type of last change lastChange = e.getTime(); lastChangeType = e.getType(); changes.put(e.getKey(), e); } } /** * Getter for property values. * * @return Value of property values. */ public Map getValueMap() { return this; } /** * Setter for property values. * * @param values New value of property values. */ public void setValueMap(Map values) { putAll(values); } }