Changes for Alien RFID Java API
===============================
B = bug fix     F = new feature

v2.3.5
======
B : Serial discovery is a little more lenient with extra connection messages on
    the serial port (Bluetooth adapters announcing themselves, etc.). Also
    removed the code that turns AutoMode off (and back on again) before asking
    for ReaderType. This was only an issue in the very first readers.
B : MessageListenerService now explicitly close()es active sockets when halting
    them, which helps eliminate cases where a blocked read prevents the service
    from stopping.
F : SerialDiscoveryService:
    * No longer PC-centric with serial port names (COMx).
    * Deprecated the setMaxSerialPort() and getMaxSerialPort() methods.
    * Updated setSerialPortList() method, and cleaned up portname validators to
      deal with serial ports with Mac/Linux-style names.
B : AbstractReader.receiveString() now only attempts to read as many bytes as
    are reported available by the underlying driver. The InputStream.read()
    function *should* allow you to specify a large chunk to read and be happy
    only getting a smaller amount, but some serial drivers were found to hang
    when asked to read more bytes than were actually available.
F : SerialManager:
    * getSerialPortList() is now static.
    * Added new addPortName() method for manually adding serial ports that may
      not have been automatically discovered by the native libraries.
    * Added "public static void main" method, which prints out the list of
      discovered serial ports. Execute as "java -jar AlienRFID.jar SerialManager"
B : TagUtil:
    * Now correctly parses a tag direction (the ${DIR} token) of "0" (means no
      data) instead of just "+" and "-".
      

v2.3.4
======
F : Implemented new AlienClass1Reader methods for accessing new reader commands:
    public String getTagInfo();
    public String getAcqG2MaskAntenna();
    public void   setAcqG2MaskAntenna(String maskAntenna);
    public String getAcqG2SL();
    public void   setAcqG2SL(String acqG2SL);
    public int    getProgBlockSize();
    public void   setProgBlockSize(int progBlockSize);
    public int    getProgBlockAlign();
    public void   setProgBlockAlign(int progBlockAlign);
    

v2.3.3
======
B : MessageListenerService: Unchecked exceptions in the messageReceived() call-
    back are now caught and reported (if debug is turned on), rather than
    crashing the listener.
F : Added support for new AcqG2Ops and AcqG2OpsMode reader commands, including
    getters and setters for G2Ops data in the Tag class, and custom taglist
    token support in the taglist decoder.
    

v2.3.2
======
B : TagUtil:
    * Fixed a bug which caused an ArrayIndexOutOfBounds error when a
    taglist being parsed had two entries for the same tag on the same antenna,
    which happens only occasionally in TagStream data.
    * Added a static method to conveniently convert an entire parsed taglist to
    a single string.
       public static String taglistAsString(Tag[] taglist);

B : MessageListenerService: Now uses the non-blocking I/O classes for TCP, which
    eliminates the need to set the SO_TIMEOUT property on the socket. These
    classes were introduced with Java 1.4, so that's now the new minimum Java
    JRE version for this API.


v2.3.1
======
F : Tag: Added fields and associated accessor methods for new TagAuth, PCWord, and
    XPC data.
       void setTagAuth(String tagAuth);     String getTagAuth();
       void setPCWord(String pcWord);       String getPCWord();
       void setXPC(String XPC);             String getXPC();

F : TagUtil: Added support for parsing of new TagAuth, PCWord, and XPC custom
    taglist tokens.

F : AlienClass1Reader: Added new accessor methods for new reader commands:
      public String getMyData();
      public void   setMyData(String myData);
      public int[]  getTagStreamCountFilter();
      public int    getTagStreamCountFilterMinimum();
      public void   setTagStreamCountFilter(int minCount);
      public void   setTagStreamCountFilter(int minCount, int maxCount);
      public int    getAcquireTime();
      public void   setAcquireTime(int acquireTime);
      public String getProgG2NSI();
      public void   setProgG2NSI(String nsiData);
      public int    getProgSingulate();
      public void   setProgSingulate(int progSingulate);
      public String getAutoErrorOutput();
      public void   setAutoErrorOutput(String errorOutput);
      public int    getAutoProgError();
      public void   setAutoProgError(int autoProgError);
      public String getTagAuth();
      public void   setTagAuth(String tagAuth);
      public int    getProgEPCDataIncCount();
      public void   setProgEPCDataIncCount(int epcDataIncCount);
      public int    getProgUserDataIncCount();
      public void   setProgUserDataIncCount(int userDataIncCount);

F : AlienClass1Reader: Added new accessor methods to support independent RFLevel
    and RFAttenuation value for reading vs. writing. Existing getter methods for
    those properties correctly parse the reader reply if it has the two-value
    format, and they only return the first value (read RFLevel/RFAttenuation).
    	public int[]  getRFAttenuations();
    	public int[]  getRFAttenuations(int antenna);
    	public void   setRFAttenuations(int antenna, int readLevel, int writeLevel);
    	public int[]  getRFLevels();
    	public int[]  getRFLevels(int antenna);
    	public void   setRFLevels(int antenna, int readLevel, int writeLevel);
      

v2.3.0
======
B : Changed a reference to SocketTimeoutException (a 1.4 class) in the
    MessageListenerService to its parent InterruptedIOException, in order to
    encourage Java 1.3 compliance.

v2.2.8
======
F : MessageListenerService: created a constructor and accessor methods to allow
    you to specify which Ethernet interface/address to listen on, in case your
    computer has multiple interfaces (wireless, or virtual, for instance).
    	public MessageListenerService(int listenerPort, InetAddress listenerInterface);
    	public InetAddress getListenerInterface();
    	public void setListenerInterface(InetAddress listenerInterface);

B : Tag:
    * updateTag() now correctly updates the RSSI and Direction values.
    * Added static getters and setters for the speed smoothing time coefficient,
    as well as the zero-speed threshold limits (speeds within the threshold limits
    are considered to be zero).
    * The default speed smoothing coefficient was reduced from 5 to 0.05, which
    gives better smoothing on tag-read timescales (tens of milliseconds).

B : TagUtil.decodeCustomTag() now returns null when it can't parse some custom-
    formatted tag data, rather than returning a zero-initialized Tag object.

F : NetworkDiscoveryListenerService: Added getListenerPort() and setListenerPort()
    methods, to allow you to change the listener port without creating a new
    instance of NetworkDiscoveryListenerService. The new port won't take effect
    until you restart the listener.

B : SerialDiscoveryListenerService: Is now more careful when examining serial port
    names as they are reported by the OS. They must start with "COM" and be
    followed by a number. Need to consider support here for non-windows names.

F : DiscoveryItem.equals() usually just compared the DiscoveryItem's MAC address
    and connection method when comparing DiscoveryItems. If either of the
    DiscoveryItems doesn't have a MAC address, equals() will instead compare
    the IP addresses instead (not as reliable at correctly identifying readers).

F : AbstractReader (and all subclasses): Synchronized the doReaderCommand()
    method, which provides a degree of thread-safety when accessing the same
    AlienClass1Reader from different threads. The sendString() and receiveString()
    methods are still not thread-safe.
    
B : AlienClass1Reader: the getReaderBaudRate() and setReaderBaudRate() methods
    were incorrectly converting between the actual baud rates (115200, 57600,
    38800, etc.) to the older enumeration (0, 1, 2, etc.). The old methods mapped
    115200<-->4, 57600<-->3, 38400<-->2, 19200<-->1, 9600<-->0, while the new
    correct mappings are 115200<-->0, 57600<-->1, 38400<-->2, 19200<-->3, 9600<-->4.

v2.2.7
======
F : AlienClass1Reader: Now implements the newer reader commands for programming
    complete images of Alien Higgs tags:
      String getProgAlienImageMap();    void  setProgAlienImageMap(String imageMap);
      String getProgAlienImageNSI();    void  setProgAlienImageNSI(String nsiData);
      String getProgDataUnit();         void  setProgDataUnit(String);
      void   programAlienImage();
      void   g2Erase(int bank, int wordPtr, int wordLen);
    
B : TagUtil: Fixed a bug which threw a strange NumberFormatException while
    decoding a custom-formatted taglist with the ${MSEC2} custom variable in it.

B : Tag: When updating the speed and calculating the distance, the distance is
    no longer pegged on the lower end at 0. It will now continue and report
    negative distances.

F : Updated the AutoModeSetup and MessageListenerTest example programs, and created
    four new example programs demonstrating how to deal with streamed data and
    the new ITR functionality (speed, distance, signal strength):
    		TagStreamTest.java
    		IOStreamTest.java
    		ITRSpeedRangeTest.java
    		ITRSignalStrengthTest.java
    The two ITR example programs also nicely demonstrate how get the API to decode
    custom-formatted tag data for you.

B : AlienClass1Reader: The getAutoStartTrigger() and getAutoStopTrigger() methods
    now handle the format accepted by the ALR-x800/9900/9650 readers where the
    two values are separated by a space and no comma.

B : AbstractReader: receiveString(), when called with the "false" argument (to
    not block until receipt of a null character) now returns as soon as data is
    read. Previously, it only returned with the data that was read when there
    was no longer any data available.


v2.2.6
======
B : Tag class: References to "velocity" changed to "speed". Sorry, but we had to
    standardize the terminology of this new feature.
        getVelocity()       --> getSpeed()
        setVelocity()       --> setSpeed()
        getSmoothVelocity() --> getSmoothSpeed()

B : TagUtil: updated to account for a change to one of the new custom tokens.
    The new %v (velocity) token was changed to %s (speed).

B : TagTable: Changing the persistTime of the TagTable with the setPersistTime()
    method now pushes that change to all of the tags currently in the TagTable.
    Previously, the new value only applied to new tags added to the TagTable,
    while existing tags retained the old persistTime. 


v2.2.5
======
F : AlienClass1Reader now implements the following methods for new reader commands:
        byte[] g2Read (int bank, int wordPtr, int wordLen);
        void   g2Write(int bank, int wordPtr, byte[] bytes, int offset, int len);
        String upgradeNow();
				String upgradeNow(String upgradeAddress);
        String upgradeNowList();
        String upgradeNowList(String upgradeAddress);
        void   setAcquireG2TagData(String g2TagDataString);
        void   setAcquireG2TagData(int bank, int wordPtr, int wordCount);
        String getAcquireG2TagData();
        String heartbeatNow();
        void   setAcquireG2Target(String g2Target);
        String getAcquireG2Target();
        void   g2Wake();
        void   g2Wake(int numWakes);
        String getSpeedFilter();
        void   setSpeedFilter(String filterStr);
        void   setSpeedFilter(float S1, float S2);
        String getRSSIFilter();
        void   setRSSIFilter(String filterStr);
        void   setRSSIFilter(float R1, float R2);

F : TagUtil was extended to decode the additional G2 tag data in the taglist, as
    specified by the new AcqG2TagData command. It also parses velocity data in
    the tag data and an updated Tag class stores and reports this data.
    
F : Tag class can also smooth the velocity data over time, and integrate it
    over time to give you a position value. New related Tag methods are:
        void     setG2Data(int index, String data);
        String   getG2Data(int index);
        String[] getG2Data();
        void     setVelocity(double velocity);
        double   getVelocity();
        double   getSmoothVelocity();
        double   getSmoothPosition();
        
    Calling setVelocity() repeatedly with updated data for that particular tag
    causes Tag to maintain a "smoothed velocity" value that attempts to remove
    some of the jitter seen in the actual data. Repeated measurements can be
    integrated over time to get a rough idea of tag position, and this is
    returned by getSmoothPosition(). Note that it really helps to have milli-
    second resolution in the tag data in order to get good position estimates.

F : Tag class was extended to include fields with getter/setters for new data
    available in taglists (via custom format tokens).
        public static final int DIR_TOWARD = -1;
  		  public static final int DIR_AWAY   = +1;
			  public int    getDirection();
			  public void   setDirection(int direction);
  		  public double getRSSI();
			  public void   setRSSI(double rssi);
    
F : TagUtil has all-new decodeCustomTagList() method for decoding taglists with
    a custom formatting. There are two methods for decoding custom taglists.
      1. Fetch the tagList string and TagListCustomFormat setting from the reader,
         and hand them both to the decodeCustomTagList(String, String) method.
         The TagUtil class will use the supplied TagListCustomFormat to figure
         out how to decode the taglist string.
      2. Pre-load the TagListCustomFormat from the reader into the TagUtil class
         with the setCustomFormatString() method. You only have to do this once,
         and the static TagUtil class will pre-compile a pattern matcher for the
         format string, making subsequent calls to decodeCustomTagList(String),
         with only the taglist string supplied, much faster.

F : AlienClass1Reader - updated getCustomTagList() method, taking advantage of
    TagUtil's new decoder (see above). If the TagUtil class hasn't been loaded
    with a TaglistCustomFormat string, then AlienClass1Reader will first fetch
    it from the reader and load it into TagUtil, before calling TagUtil's
    decodeCustomTagList() method.

F : MessageListenerService - added property, isCustomTaglist, which instructs
    it to use the new TagUtil.decodeCustomTagList() method instead of
    TagUtil.decodeTagList(). This allows the MessageListenerService to handle
    custom-formatted data coming in over a notify or stream channel. You must
    first tell TagUtil what custom format string to use when decoding, with the
    static method, setCustomFormatString(). To get and set this property, use
    MessageListenerService.setIsCustomTagList(boolean) and
    MessageListenerService.isCustomTagList().
         
F : TagTableListener - added new tagRenewed(Tag) method, in addition to the
    tagAdded(Tag) and tagRemoved(Tag) methods. This will break anyone's code
    that may be using this class, since the interface signatures have changed!

F : AbstractReader (and all subclasses):
    Added a new property, with getter/setter methods, to control the meaning of
    the receive timeout (controlled by get/setTimeOutMillis()).
        void setTimeOutMode(int timeoutMode);
        int  getTimeOutMode();
        
    The original (and now default) behavior was to timeout the reading of a
    response if the entire response was not received within the timeout period.
    You now have to option of timing out only if there have been no characters
    read within the timeout period. Long responses will therefore not timeout,
    as long as some data is received periodically (during an upgrade, for
    instance). Use the constants, defined by AbstractReader, when getting/setting:
        public static final int TIMEOUTMODE_COMMAND; // default
        public static final int TIMEOUTMODE_CHARACTER;
         

v2.2.4
======
F : NetworkDiscoveryListener now sends out a "heartbeat ping" UDP packet when
starting up. This will prompt readers that can respond to UDP packets to send
out a heartbeat right away, which greatly improves discovery times.

F : SerialDiscoveryListenerService now has the option to use different serial
baud rates for reader discovery, in case users choose to use a baud rate other
than 115,200 baud for their reader communication.

B : Discovered a bug in the JRE when parsing date and time strings, so TagUtil
now uses a custom-made set of functions instead, parseDateAndTime(),
parseDate(), and parseTime(). These new functions are about twice as fast as
the SimpleDateFormat's parser.


v2.2.3
======
B : MessageListenerService now properly detects and decodes XML headers from tag-
and io-streams, including setting the "reason" field of the resulting Message
objects. It did this previously only when the stream format was text.


v2.2.2
======
F : AlienClass1Reader now implements the following new program-related commands:
        programEPC(), programEPC(String)
        programUser(), programUser(String)
        programAccessPwd(), programAccessPwd(String)
        programKillPwd(), programKillPwd(String)
        programAndLockEPC(), programAndLockEPC(String)
        programAndLockUser(), programAndLockUser(String)
        getProgG2LockType(), setProgG2LockType(String)
        lockEPC(), lockEPC(String)
        lockUser(), lockUser(String)
        lockAccessPwd(), lockAccessPwd(String)
        lockKillPwd(), lockKillPwd(String)
        unlockEPC(), unlockEPC(String)
        unlockUser(), unlockUser(String)
        unlockAccessPwd(), unlockAccessPwd(String)
        unlockKillPwd(), unlockKillPwd(String)


v2.2.1
======
F : Created new com.alien.enterpriseRFID.externalio package containing the
    classes, ExternalIO and ExternalIOUtil (along the lines of Tag and TagUtil).
    AlienClass1Reader now has a getIOList() method similar to getTagList() that
    returns an array of ExternalIO objects.
B : Deprecated Message.getXML() and Message.setXML() in favor of the new methods
    getRawData() and setRawData().
F : Added IOList storage in the Message class.
B : Fixed some errors in the JavaDocs.
F : Added support for new ProgEPCDataInc, ProgUserDataInc reader commands.
B : Fixed a bug in MessageListenerEngine that could cause a listener thread to
    never die after the reader disconnects. This can lead to a proliferation of
    zombie threads that consume memory.


v2.2.0
======
F : AlienClass1Reader now uses "alien" for the default username, and "password"
    for the default password. It is no longer required to call setUsername() and
    setPassword() before opening a TCP connection to the reader, unless the
    reader has a username or password different from the default.


v2.1.14
=======
F : Message class now has a hostname field, and associated getters/setters.
F : MessageListenerService now throws an IOException if it fails to bind to
    the chosen listenerPort. MessageListenerService now runs as a daemon (it
    won't prevent the JVM from shutting down, as long as all user threads
    are closed.
F : MessageListenerService is now fully threaded. When it accepts a socket
    connection from a reader, it spawns a MessageListenerEngine on a separate
    thread, which handles the socket connection and relays messages back to
    the user's MessageListener via the MessageListenerService.
F : MessageListenerService now handles streamed data from ALR-x800 readers
    that support TagStream and IOStream data. When a reader starts one of
    these streams, it optionally sends a header to the service to identify
    itself. This info is used to populate the resulting Message object. No
    attempt is made at this time to parse the data that is streamed - use
    Message.getXML() (even if it isn't formatted using XML) to see the contents
    of each streaming event. The Message.reason field will indicate if it is
    data from a stream, using Message.REASON_TAGSTREAM and Message.REASON_IOSTREAM.
F : Implemented new AlienClass1Reader methods, to support new reader functions:
        getReaderBaudRate(), setReaderBaudRate()
        getWWWPort(), setWWWPort()
        getAutoStopPause, setAutoStopPause()
        clearIOList()
        getIOListDump()
        getIOType(), setIOType()
        getIOListFormat(), setIOListFormat()
        setDefaultIOListFormat()
        getIOListCustomFormat(), setIOListCustomFormat()
        getNotifyKeepAliveTime(), setNotifyKeepAliveTime()
        getNotifyInclude(), setNotifyInclude()
        getNotifyQueueLimit(), setNotifyQueueLimit()
        getStreamHeader(), setStreamHeader()
        getTagStreamMode(), setTagStreamMode()
        getTagStreamAddress(), setTagStreamAddress()
        getTagStreamKeepAliveTime(), setTagStreamKeepAliveTime()
        getTagStreamFormat(), setTagStreamFormat()
        getTagStreamCustomFormat(), setTagStreamCustomFormat()
        getIOStreamMode(), setIOStreamMode()
        getIOStreamAddress(), setIOStreamAddress()
        getIOStreamKeepAliveTime(), setIOStreamKeepAliveTime()
        getIOStreamFormat(), setIOStreamFormat()
        getIOStreamCustomFormat(), setIOStreamCustomFormat()
        getAcquireG2Selects(), setAcquireG2Selects()
        getAcquireG2Session(), setAcquireG2Session()
        getAcqC1Mask(), setAcqC1Mask(), setC1MaskBits(), setC1TagMask()
        getAcqG2Mask(), setAcqG2Mask(), setG2MaskBits(), setG2TagMask()
        getAcqG2AccessPwd(), setAcqG2AccessPwd()
        getRFAttenuation(int antenna), setRFAttenunation(int antenna, int atten)
        getRFLevel(int antenna), setRFLevel(int antenna, int level)

v2.1.13
=======
B : Fixed a problem when stopping and restarting a MessageListenerService.
    Stopping wasn't happening because the run() method was blocked at accept().
    Implemented SO_TIMEOUT on the server socket to loosen things up.
F : NetworkDiscoveryListenerService is now smarter about hashing reader
    heartbeats. If the reader broadcasts its MAC address in the heartbeat,
    then the service hashes its list of discovered readers on the MAC address
    and connection type (TCP vs. serial). If the MAC address is not provided,
    it hashes the list on the reader's address and connection type. Previously,
    the hash included fields such as ReaderName and ReaderType, which would
    cause ReaderAdded/ReaderDropped event when the ReaderName changed.
F : All of the setter methods in DiscoveryItem now return true/false, indicating
    whether that parameter has been changed from the old value.
F : DiscoveryItem now has a public method, update(), that takes a newer
    DiscoveryItem as an argument, and copies the values from the given
    DiscoveryItem that have changed since the last heartbeat. update() sets
    a new public boolean field, "isUpdated", if any of the values are different.
    When the NetworkDiscoveryListenerService receives a new heartbeat from a
    known reader, it calls update() against the existing DiscoveryItem in its
    reader list, passing in the DiscoveryItem representing the new heartbeat.
    This way, when the DiscoveryItem is passed to a DiscoveryListener's
    readerRenewed() method, that listener can check the isUpdated flag to
    determine if any of the reader properties have changed - allowing user
    interfaces, etc. to be updated.


v2.1.12
=======
B : Increased the buffer size for incoming heartbeats. Readers with very long
    ReaderNames were able to overrun the buffer.


v2.1.11
=======
B : Fixed a bug in AlienClass1Reader.getAcquireC1Cycles() that caused a
    stack overflow error to occur.
B : Fixed TagUtil so that it doesn't require at least 12 characters in a TagID.
    It is possible for tags (Gen2 tags, particularly) to have any ID length
    (or no ID at all).
F : AlienClassOEMReader now supports Gen2.


v2.1.10
=======
F : DiscoveryItem now handles the extra ReaderVersion information that is
    transmitted by ALR-x800 readers.
F : Implemented the following new AlienClass1Reader functions:
	getInfo(),
        getHostname(), setHostname(),
	getNetworkUpgrade(), setNetworkupgrade(),
        getUpgradeAddress(), setUpgradeAddress(),
        getUpgradeIPAddress(), setUpgradeIPAddress(),
        getUpgradePort(), setUpgradePort(),
        ping(String), ping(String, int),
        getHeartbeatAddress(), setHeartbeatAddress(),
        getHeartbeatCount(), setHeartbeatCount(),
        getNotifyRetryCount(), setNotifyRetryCount(),
        getNotifyRetryPause(), setNotifyRetryPause(),
        getProgC1KillPwd(), setProgC1KillPwd(),
        getProgG2KillPwd(), setProgG2KillPwd(),
	macroList(), macroView(), macroRun(),
        macroDel(), macroDelAll(),
        macroStartRec(), macroStopRec()


v2.1.9
======
F : MessageListenerService now continues to listen on a socket and read
    additional notification messages, as long as there is data. This is to
    support the ALR-x800 reader that can buffer a sequence of failed
    notifications and send them all in one socket connection.


v2.1.8
======
B : NetworkDiscoveryListenerService was extracting a reader's address from
    the heartbeat's DatagramPacket, rather than then the address reported by
    the reader explicitly in its heartbeat body. This was subject to some
    limitations in Windows systems whereby two heartbeats received from readers
    with the same hostname occasionally were being mixed up, and querying one
    DatagramPacket for its inet info returned information for the other reader.
F : Implemeted functions for accessing new reader commands: RFModulation, LBT,
    LBTLimit, LBTValue, TagListMillis, and ProgProtocol.


v2.1.7
======
F : Created new AlienReaderConnectionRefusedException class, which is thrown
    anytime a connection to the reader is refused - likely because someone is
    already connected to the reader. This class is a subclass of
    AlienReaderConnectionException.

v2.1.6
======
B : Fixed util.XMLReader to handle <?xml version="1.0" encoding="UTF-8"?>
    header lines in reader messages (ALR-9800).
B : ALR-9800 XML taglists give Protocol element as an integer, not string.
    Updated TagUtil to handle both ways.
F : MessageListenerService now delivers an ErrorMessage to a MessageListener
    when it has socket trouble, instead of sleeping for 5 minutes. If there
    is no MessageListener registered, it sleeps for 1 minute and retries.

v2.1.5
======
F : Added MACAddress info to DiscoveryItem. For readers that do not include
    this information in the Heartbeat, this value will be null.
F : Added MACAddress info to Message classes. For readers that do not include
    this information in the Heartbeat, this value will be null.
F : Added support for ALR-9800 functions, including: RFLevel, AcqC1___
    functions, AcqC0___ functions, AcqG2___ functions, AutoStartPause.
F : Extended com.alien.enterpriseRFID.notify.Message to create a new
    ErrorMessage class. Any problems that the MessageListenerService has when
    decoding a reader's notification message will be communicated to a
    registered MessageListener through this class. Previously, problems were
    only reported to stdout. Use a construct in your MessageReceived() such as
    "if (message instanceof ErrorMessage) {...}" to handle these conditions.

v2.1.4
======
F : Modified various Tag classes to decode each tag's protocol and Tx/Rx
    antennas from a reader's TagList.

v2.1.3
======
B : MessageListenerService no longer attempts to resolve the reader's name with
    DNS everytime it connects to deliver a notification message.

v2.1.2
======
B : getTagList() no longer attempts to parse "terse" taglist formats. It was
    too easy for the API to interpret responses to other commands as tag data.
B : AlienClassOEMReader no longer gets out of sync with command/response pairs
    when encountering an error ComType within an inventory.
F : Added new API logging mechanism in AlienClass1Reader.
    setLogLevel(reader.ON) causes the class to log each method called to
    stdout. This is useful for debugging complicated apps.

v2.1.1
======
AbstractReader (and all reader classes)
F : Opening a socket connection to a reader now respects the value of
    currentTimeOutMillis when creating the socket. Instead of waiting for
    Java's Socket class to give up (which can take more than 20 seconds), a new
    threaded version provides a variable timeout (default is 10 seconds).
B : When the library takes ownership of a COM port, it now uses "the Alien RFID
    Java Library" as the application name, instead of "Alien RFID Reader".
B : AbstractReader.close() now sends a "q" to networked readers, rather than
    relying on the reader to detect the dropped socket.

AlienClass1Reader
F : New getCustomTaglist() method added to AlienClass1Reader, which uses the
    reader's CustomTaglistFormat to properly decode taglists in a custom format.
B : Fixed a bug where readers with ReaderName starting with "Error" could not be
    open()-ed. Any parameter value that starts with "Error" (ReaderName,
    TagListCustomFormat, MailServer, etc.) may still return an exception when
    that value is retrieved.
F : Implemented saveSettings(), reboot(), and get|setNotifyHeader() methods.
B : getTagList() methods no longer automatically combine tag data from different
    antennas into one entry.
B : setFactorySettings() automatically waits for the reader to reboot, but only
    the reader reports that it is going to reboot. Older ALR-9780's and all
    ALR-9750's don't automatically reboot after FactorySettings.

AlienClassOEMREader
B : AlienClassOEMReader.getReaderType now includes the correct model number, as
    well as the number of antennas and frequency band.
F : AlienClassOEMReader surfaces more java methods for issuing reader commands,
    and supports more command-line interface commands.
B : Changed AlienClassOEMReader's default acquire properties and PersistTime to
    match that of the other readers. Fixed a bug which caused a PersistTime other
    than -1 to be treated as 0.
B : The AlienClassOEMReader and AlienDLEObject classes no longer combine
    tag/antenna data when returning a taglist.

MessageListenerService & TagUtil
F : TagUtil and MessageListenerService have been improved to handle decoding of
    taglists in the "Text" and new "Terse" formats. Notification messages
    without headers are also supported.
B : Fixed a bug where pinging a MessageListenerService from a reader caused the
    listener to fail - resulting in the listener port becoming tied up.

SerialDiscoveryListenerService
F : Implemented setSerialPortList() and getSerialPortList() to allow for
    scanning of only specified serial ports. Provides work-around for situations
    where a device on a serial port doesn't handle being poked in the eye.

DiscoveryItem
F : DiscoveryItem has been modified so that it recognizes the ALR-977x
    ReaderType as valid (returning an AlienClass1Reader).


v2.1.0
======
B : Put a 300-character limit on the data send to the reader to avoid blowing
    its input buffer.
F : Deprecated resetAutoMode() in favor of autoModeReset().
F : Made improvements to AlienClassOEMReader: fixed setMask() bug, ReaderVersion
    now uses numAntennas and frequency from the module, improved "Set
    ReaderCommand" and "RC" CLI commands.
F : Added new setMaskBits() method to AlienClass1Reader, and
    fromBinaryStringMSB() method to Converters to support it.
F : Added new setFactorySettings(boolean waitForReboot) and waitForReboot()
    methods. This is to support the new reader behavior where it reboots after
    a FactorySettings command. By default, setFactorySettings() now waits for a
    reboot.
F : Added methods for new reader commands: "get|set ProgAntenna", "get|set
    TagType", "get|set InitExternalOutput", "get|set InvertExternalInput",
    "get|set InvertExternalOutput", and "get MaxAntenna", "get|set
    ReaderNumber", "get Uptime".


v2.0.4
======
B : Fixed TagUtil's date parser to use 24-hour instead of 12-hour mode. Added
F : new toTerseString() method to DiscoveryItem. Implemented all of the program
    commands, and others that are new.


v2.0.0
======
F : Added support for new 4-port readers.