Java tutorial
package jmri.jmrit.vsdecoder; /* * <hr> * This file is part of JMRI. * <P> * JMRI is free software; you can redistribute it and/or modify it under * the terms of version 2 of the GNU General Public License as published * by the Free Software Foundation. See the "COPYING" file for a copy * of this license. * <P> * JMRI 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. * <P> * * @author Mark Underwood Copyright (C) 2011 * @version $Revision$ */ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Set; import jmri.Block; import jmri.IdTag; import jmri.LocoAddress; import jmri.Manager; import jmri.NamedBean; import jmri.PhysicalLocationReporter; import jmri.Reporter; import jmri.jmrit.vsdecoder.listener.ListeningSpot; import jmri.jmrit.vsdecoder.listener.VSDListener; import jmri.jmrit.vsdecoder.swing.VSDManagerFrame; import jmri.util.FileUtil; import jmri.util.JmriJFrame; import jmri.util.PhysicalLocation; import org.jdom2.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // VSDecoderFactory // // Builds VSDecoders as needed. Handles loading from XML if needed. public class VSDecoderManager implements PropertyChangeListener { //private static final ResourceBundle rb = VSDecoderBundle.bundle(); private static final String vsd_property_change_name = "VSDecoder Manager"; //NOI18N protected jmri.NamedBeanHandleManager nbhm = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class); HashMap<String, VSDListener> listenerTable; // list of listeners HashMap<String, VSDecoder> decodertable; // list of active decoders by System ID HashMap<String, VSDecoder> decoderAddressMap; // List of active decoders by address HashMap<String, String> profiletable; // list of loaded profiles key = profile name, value = path List<String> reportertable; // list of Reporters we are following. // List of registered event listeners protected javax.swing.event.EventListenerList listenerList = new javax.swing.event.EventListenerList(); //private static VSDecoderManager instance = null; // sole instance of this class private static VSDecoderManagerThread thread = null; // thread for running the manager private VSDecoderPreferences vsdecoderPrefs; // local pointer to the preferences object private JmriJFrame managerFrame = null; private VSDecoder default_decoder = null; // shortcut pointer to the default decoder (do we need this?) private static int vsdecoderID = 0; //private static int listenerID = 0; // for future use // Unused? //private PhysicalLocation listener_position; // constructor - for kicking off by the VSDecoderManagerThread... // WARNING: Should only be called from static instance() public VSDecoderManager() { // Setup the decoder table listenerTable = new HashMap<String, VSDListener>(); decodertable = new HashMap<String, VSDecoder>(); decoderAddressMap = new HashMap<String, VSDecoder>(); profiletable = new HashMap<String, String>(); // key = profile name, value = path reportertable = new ArrayList<String>(); // Get preferences String dirname = FileUtil.getUserFilesPath() + "vsdecoder" + File.separator; // NOI18N FileUtil.createDirectory(dirname); vsdecoderPrefs = new VSDecoderPreferences(dirname + VSDecoderPreferences.VSDPreferencesFileName); // Listen to ReporterManager for Report List changes setupReporterManagerListener(); // Get a Listener (the only one for now) //VSDListener t = new VSDListener(getNextListenerID()); VSDListener t = new VSDListener(); listenerTable.put(t.getSystemName(), t); } public static VSDecoderManager instance() { if (thread == null) { thread = VSDecoderManagerThread.instance(true); } return (VSDecoderManagerThread.manager()); } public VSDecoderPreferences getVSDecoderPreferences() { return (vsdecoderPrefs); } public JmriJFrame provideManagerFrame() { if (managerFrame == null) { managerFrame = new VSDManagerFrame(); } return (managerFrame); } private String getNextVSDecoderID() { // vsdecoderID initialized to zero, pre-incremented before return... // first returned ID value is 1. return ("IAD:VSD:VSDecoderID" + (++vsdecoderID)); // NOI18N } // To be used in the future /* private String getNextListenerID() { // ListenerID initialized to zero, pre-incremented before return... // first returned ID value is 1. // Prefix is added by the VSDListener constructor return("VSDecoderID" + (++listenerID)); // NOI18N } */ @Deprecated public VSDecoder getVSDecoder(String profile_name) { VSDecoder vsd; String path; if (profiletable.containsKey(profile_name)) { path = profiletable.get(profile_name); log.debug("Profile " + profile_name + " is in table. Path = " + path); vsd = new VSDecoder(getNextVSDecoderID(), profile_name, path); decodertable.put(vsd.getID(), vsd); // poss. broken for duplicate profile names decoderAddressMap.put(vsd.getAddress().toString(), vsd); return (vsd); } else { // Don't have enough info to try to load from file. log.error("Requested profile not loaded: " + profile_name); return (null); } } public VSDecoder getVSDecoder(String profile_name, String path) { VSDecoder vsd = new VSDecoder(getNextVSDecoderID(), profile_name, path); decodertable.put(vsd.getID(), vsd); // poss. broken for duplicate profile names if (vsd.getAddress() != null) { decoderAddressMap.put(vsd.getAddress().toString(), vsd); } return (vsd); } /** * Provide or build a VSDecoder based on a provided configuration */ public VSDecoder getVSDecoder(VSDConfig config) { String path; String profile_name = config.getProfileName(); // First, check to see if we already have a VSDecoder on this Address //debugPrintDecoderList(); if (decoderAddressMap.containsKey(config.getLocoAddress().toString())) { return (decoderAddressMap.get(config.getLocoAddress().toString())); } if (profiletable.containsKey(profile_name)) { path = profiletable.get(profile_name); log.debug("Profile " + profile_name + " is in table. Path = " + path); config.setVSDPath(path); config.setID(getNextVSDecoderID()); VSDecoder vsd = new VSDecoder(config); decodertable.put(vsd.getID(), vsd); decoderAddressMap.put(vsd.getAddress().toString(), vsd); //debugPrintDecoderList(); return (vsd); } else { // Don't have enough info to try to load from file. log.error("Requested profile not loaded: " + profile_name); return (null); } } /* public void debugPrintDecoderList() { log.debug("Current Decoder List by System ID:"); Set<Map.Entry<String, VSDecoder>> ids = decodertable.entrySet(); Iterator<Map.Entry<String, VSDecoder>> idi = ids.iterator(); while (idi.hasNext()) { Map.Entry<String, VSDecoder> e = idi.next(); log.debug(" ID = " + e.getKey() + " Val = " + e.getValue().getAddress().toString()); } log.debug("Current Decoder List by Address:"); ids = decoderAddressMap.entrySet(); idi = ids.iterator(); while (idi.hasNext()) { Map.Entry<String, VSDecoder> e = idi.next(); log.debug(" ID = " + e.getKey() + " Val = " + e.getValue().getID()); } } */ public VSDecoder getVSDecoderByID(String id) { VSDecoder v = decodertable.get(id); if (v == null) { log.debug("No decoder in table! ID = " + id); } return (decodertable.get(id)); } public VSDecoder getVSDecoderByAddress(String sa) { if (sa == null) { log.debug("Decoder Address is Null"); return (null); } log.debug("Decoder Address: " + sa); VSDecoder rv = decoderAddressMap.get(sa); if (rv == null) { log.debug("Not found."); } else { log.debug("Found: " + rv.getAddress()); } return (rv); } /* public VSDecoder getVSDecoderByAddress(String sa) { // First, translate the string into a DccLocoAddress // no object if no address if (sa.equals("")) return null; DccLocoAddress da = null; // ask the Throttle Manager to handle this! LocoAddress.Protocol protocol; if(InstanceManager.throttleManagerInstance()!=null){ protocol = InstanceManager.throttleManagerInstance().getProtocolFromString(sa); da = (DccLocoAddress)InstanceManager.throttleManagerInstance().getAddress(sa, protocol); } // now look up the decoder if (da != null) { return getVSDecoderByAddress(da); } return(null); } */ public void setDefaultVSDecoder(VSDecoder d) { default_decoder = d; } public VSDecoder getDefaultVSDecoder() { return (default_decoder); } public ArrayList<String> getVSDProfileNames() { ArrayList<String> sl = new ArrayList<String>(); for (String p : profiletable.keySet()) { sl.add(p); } return (sl); } public Collection<VSDecoder> getVSDecoderList() { return (decodertable.values()); } public String getDefaultListenerName() { return (VSDListener.ListenerSysNamePrefix + "ListenerID1"); // NOI18N } public ListeningSpot getDefaultListenerLocation() { VSDListener l = listenerTable.get(getDefaultListenerName()); if (l != null) { return (l.getLocation()); } else { return (null); } } public void setListenerLocation(String id, ListeningSpot sp) { VSDListener l = listenerTable.get(id); log.debug("Set listener location " + sp + " listener: " + l); if (l != null) { l.setLocation(sp); } } public void setDecoderPositionByID(String id, PhysicalLocation p) { VSDecoder d = decodertable.get(id); if (d != null) { d.setPosition(p); } } public void setDecoderPositionByAddr(LocoAddress a, PhysicalLocation l) { // Find the addressed decoder // This is a bit hokey. Need a better way to index decoder by address // OK, this whole LocoAddress vs. DccLocoAddress thing has rendered this SUPER HOKEY. if (a == null) { log.warn("Decoder Address is Null"); return; } if (l == null) { log.warn("PhysicalLocation is Null"); return; } if (l.equals(PhysicalLocation.Origin)) { log.debug("Location : " + l.toString() + " ... ignoring."); // Physical location at origin means it hasn't been set. return; } log.debug("Decoder Address: " + a.getNumber()); for (VSDecoder d : decodertable.values()) { // Get the Decoder's address protocol. If it's a DCC_LONG or DCC_SHORT, convert to DCC // since the LnReprter can't tell the difference and will always report "DCC". if (d == null) { log.debug("VSdecoder null pointer!"); return; } LocoAddress pa = d.getAddress(); if (pa == null) { log.debug("Vsdecoder" + d + " address null!"); return; } LocoAddress.Protocol p = d.getAddress().getProtocol(); if (p == null) { log.debug("Vsdecoder" + d + " address = " + pa + " protocol null!"); return; } if ((p == LocoAddress.Protocol.DCC_LONG) || (p == LocoAddress.Protocol.DCC_SHORT)) { p = LocoAddress.Protocol.DCC; } if ((d.getAddress().getNumber() == a.getNumber()) && (p == a.getProtocol())) { d.setPosition(l); // Loop through all the decoders (assumes N will be "small"), in case // there are multiple decoders with the same address. This will be somewhat broken // if there's a DCC_SHORT and a DCC_LONG decoder with the same address number. //return; } } // decoder not found. Do nothing. return; } // VSDecoderManager Events public void addEventListener(VSDManagerListener listener) { listenerList.add(VSDManagerListener.class, listener); } public void removeEventListener(VSDManagerListener listener) { listenerList.remove(VSDManagerListener.class, listener); } void fireMyEvent(VSDManagerEvent evt) { //Object[] listeners = listenerList.getListenerList(); for (VSDManagerListener l : listenerList.getListeners(VSDManagerListener.class)) { l.eventAction(evt); } } /** * getProfilePath() * * Retrieve the Path for a given Profile name. */ public String getProfilePath(String profile) { return (profiletable.get(profile)); } /** * Load Profiles from a VSD file Not deprecated anymore. used by the new * ConfigDialog. */ public void loadProfiles(String path) { try { VSDFile vsdfile = new VSDFile(path); if (vsdfile.isInitialized()) { this.loadProfiles(vsdfile); } } catch (java.util.zip.ZipException e) { log.error("ZipException loading VSDecoder from " + path); // would be nice to pop up a dialog here... } catch (java.io.IOException ioe) { log.error("IOException loading VSDecoder from " + path); // would be nice to pop up a dialog here... } } protected void registerReporterListener(String sysName) { Reporter r = jmri.InstanceManager.reporterManagerInstance().getReporter(sysName); if (r == null) { return; } jmri.NamedBeanHandle<Reporter> h = nbhm.getNamedBeanHandle(sysName, r); if (h == null) { return; } // Make sure we aren't already registered. ArrayList<java.beans.PropertyChangeListener> ll = r.getPropertyChangeListeners(h.getName()); if (ll.isEmpty()) { r.addPropertyChangeListener(this, h.getName(), vsd_property_change_name); } } protected void registerBeanListener(Manager beanManager, String sysName) { NamedBean b = beanManager.getBeanBySystemName(sysName); if (b == null) { log.debug("No bean by name " + sysName); return; } jmri.NamedBeanHandle<NamedBean> h = nbhm.getNamedBeanHandle(sysName, b); if (h == null) { log.debug("no handle for bean " + b.getDisplayName()); return; } // Make sure we aren't already registered. ArrayList<java.beans.PropertyChangeListener> ll = b.getPropertyChangeListeners(h.getName()); if (ll.isEmpty()) { b.addPropertyChangeListener(this, h.getName(), vsd_property_change_name); log.debug("Added listener to bean " + b.getDisplayName() + " type " + b.getClass().getName()); } } protected void registerReporterListeners() { // Walk through the list of reporters for (String sysName : jmri.InstanceManager.reporterManagerInstance().getSystemNameList()) { registerReporterListener(sysName); } for (String sysname : jmri.InstanceManager.blockManagerInstance().getSystemNameList()) { registerBeanListener(jmri.InstanceManager.blockManagerInstance(), sysname); } } // This listener listens to the ReporterManager for changes to the list of Reporters. // Need to trap list length (name="length") changes and add listeners when new ones are added. private void setupReporterManagerListener() { // Register ourselves as a listener for changes to the Reporter list. For now, we won't do this. Just force a // save and reboot after reporters are added. We'll fix this later. // jmri.InstanceManager.reporterManagerInstance().addPropertyChangeListener(new PropertyChangeListener() { // public void propertyChange(PropertyChangeEvent event) { // log.debug("property change name " + event.getPropertyName() + " old " + event.getOldValue() + " new " + event.getNewValue()); // reporterManagerPropertyChange(event); // } // }); jmri.InstanceManager.reporterManagerInstance().addPropertyChangeListener(this); // Now, the Reporter Table might already be loaded and filled out, so we need to get all the Reporters and list them. // And add ourselves as a listener to them. for (String sysName : jmri.InstanceManager.reporterManagerInstance().getSystemNameList()) { registerReporterListener(sysName); } for (String sysname : jmri.InstanceManager.blockManagerInstance().getSystemNameList()) { registerBeanListener(jmri.InstanceManager.blockManagerInstance(), sysname); } } protected void shutdownDecoders() { // Shut down and destroy all running VSDecoders. Set<String> vk = decodertable.keySet(); Iterator<String> it = vk.iterator(); while (it.hasNext()) { VSDecoder v = decodertable.get(it.next()); v.shutdown(); } // Empty the DecoderTable decodertable.clear(); /* vk = decodertable.keySet(); it = vk.iterator(); while(it.hasNext()) { decodertable.remove(it.next()); } */ // Empty the AddressMap decoderAddressMap.clear(); /* vk = decoderAddressMap.keySet(); it = vk.iterator(); while(it.hasNext()) { decoderAddressMap.remove(it.next()); } */ } public void propertyChange(PropertyChangeEvent evt) { log.debug("property change type " + evt.getSource().getClass().getName() + " name " + evt.getPropertyName() + " old " + evt.getOldValue() + " new " + evt.getNewValue()); if (evt.getSource() instanceof jmri.ReporterManager) { reporterManagerPropertyChange(evt); } else if (evt.getSource() instanceof jmri.Reporter) { reporterPropertyChange(evt); } else if (evt.getSource() instanceof jmri.Block) { log.debug("Block property change! name = " + evt.getPropertyName() + " old= " + evt.getOldValue() + " new= " + evt.getNewValue()); blockPropertyChange(evt); } else if (evt.getSource() instanceof VSDManagerFrame) { if (evt.getPropertyName() .equals(VSDManagerFrame.PCIDMap.get(VSDManagerFrame.PropertyChangeID.REMOVE_DECODER))) { // Shut down the requested decoder and remove it from the manager's hash maps. // Unless there are "illegal" handles, this should put the decoder on the garbage heap. I think. String sa = (String) evt.getNewValue(); VSDecoder d = this.getVSDecoderByAddress(sa); log.debug("Removing Decoder " + sa + " ... " + d.getAddress()); d.shutdown(); decodertable.remove(d.getID()); decoderAddressMap.remove(sa); //debugPrintDecoderList(); } else if (evt.getPropertyName() .equals(VSDManagerFrame.PCIDMap.get(VSDManagerFrame.PropertyChangeID.CLOSE_WINDOW))) { // Note this assumes there is only one VSDManagerFrame open at a time. shutdownDecoders(); managerFrame = null; } } else { // Un-Handled source. Does nothing ... yet... } return; } public void blockPropertyChange(PropertyChangeEvent event) { // Needs to check the ID on the event, look up the appropriate VSDecoder, // get the location of the event source, and update the decoder's location. @SuppressWarnings("cast") // NOI18N String eventName = (String) event.getPropertyName(); if (event.getSource() instanceof PhysicalLocationReporter) { Block blk = (Block) event.getSource(); String repVal = null; // Depending on the type of Block Event, extract the needed report info from // the appropriate place... // "state" => Get loco address from Block's Reporter if present // "value" => Get loco address from event's newValue. if (eventName.equals("state")) { // NOI18N // Need to decide which reporter it is, so we can use different methods // to extract the address and the location. if ((Integer) event.getNewValue() == Block.OCCUPIED) { // Get this Block's Reporter's current/last report value. need to fix this - it could be /// an idtag type reporter. if (blk.getReporter() == null) { log.debug( "Block " + blk.getSystemName() + " has no reporter! Skipping state-type report."); return; } if (blk.isReportingCurrent()) { repVal = (String) blk.getReporter().getCurrentReport(); } else { repVal = (String) blk.getReporter().getLastReport(); } } else { log.debug("Ignoring report. not an OCCUPIED event."); return; } } else if (eventName.equals("value")) { if (event.getNewValue() instanceof String) { repVal = event.getNewValue().toString(); } // Else it will still be null from the declaration/assignment above. } else { log.debug("Not a supported Block event type. Ignoring."); return; } // Type of eventName. // Set the decoder's position. if (repVal == null) { log.warn("Report from Block " + blk.getUserName() + " is null!"); } if (blk.getDirection(repVal) == PhysicalLocationReporter.Direction.ENTER) { setDecoderPositionByAddr(blk.getLocoAddress(repVal), blk.getPhysicalLocation()); } return; } else { log.debug("Reporter doesn't support physical location reporting."); } // Reporting object implements PhysicalLocationReporter return; } public void reporterPropertyChange(PropertyChangeEvent event) { // Needs to check the ID on the event, look up the appropriate VSDecoder, // get the location of the event source, and update the decoder's location. @SuppressWarnings("cast") // NOI18N String eventName = (String) event.getPropertyName(); if ((event.getSource() instanceof PhysicalLocationReporter) && (eventName.equals("currentReport"))) { // NOI18N PhysicalLocationReporter arp = (PhysicalLocationReporter) event.getSource(); // Need to decide which reporter it is, so we can use different methods // to extract the address and the location. if (event.getNewValue() instanceof String) { String newValue = (String) event.getNewValue(); if (arp.getDirection(newValue) == PhysicalLocationReporter.Direction.ENTER) { setDecoderPositionByAddr(arp.getLocoAddress(newValue), arp.getPhysicalLocation(newValue)); } } else if (event.getNewValue() instanceof IdTag) { // newValue is of IdTag type. // Dcc4Pc, Ecos, // Assume Reporter "arp" is the most recent seen location IdTag newValue = (IdTag) event.getNewValue(); setDecoderPositionByAddr(arp.getLocoAddress(newValue.getTagID()), arp.getPhysicalLocation(null)); } else { log.debug("Reporter's return type is not supported."); // do nothing } } else { log.debug("Reporter doesn't support physical location reporting or isn't reporting new info."); } // Reporting object implements PhysicalLocationReporter return; } public void reporterManagerPropertyChange(PropertyChangeEvent event) { String eventName = event.getPropertyName(); log.debug("VSDecoder received Reporter Manager Property Change: " + eventName); if (eventName.equals("length")) { // NOI18N // Re-register for all the reporters. The registerReporterListener() will skip // any that we're already registered for. for (String sysName : jmri.InstanceManager.reporterManagerInstance().getSystemNameList()) { registerReporterListener(sysName); } // It could be that we lost a Reporter. But since we aren't keeping a list anymore // we don't care. } } public void loadProfiles(VSDFile vf) { Element root; String pname; if ((root = vf.getRoot()) == null) { return; } ArrayList<String> new_entries = new ArrayList<String>(); java.util.Iterator<Element> i = root.getChildren("profile").iterator(); // NOI18N while (i.hasNext()) { Element e = i.next(); log.debug(e.toString()); if ((pname = e.getAttributeValue("name")) != null) { // NOI18N profiletable.put(pname, vf.getName()); new_entries.add(pname); } } // debug /* for (String s : new_entries) { log.debug("New entry: " + s); } */ // /debug fireMyEvent(new VSDManagerEvent(this, VSDManagerEvent.EventType.PROFILE_LIST_CHANGE, new_entries)); } private static final Logger log = LoggerFactory.getLogger(VSDecoderManager.class.getName()); }