We Stealz Your Dataz
authorTJ <hacker@iam.tj>
Sat, 2 May 2015 10:50:55 +0000 (11:50 +0100)
committerTJ <hacker@iam.tj>
Sat, 2 May 2015 10:50:55 +0000 (11:50 +0100)
46 files changed:
.gitignore [new file with mode: 0644]
WSYD_TestData.csv [new file with mode: 0644]
build.xml [new file with mode: 0644]
index.html [new file with mode: 0644]
index.md [new file with mode: 0644]
lib/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar [new file with mode: 0644]
lib/absolutelayout/AbsoluteLayout.jar [new file with mode: 0644]
lib/junit/junit-3.8.2-api.zip [new file with mode: 0644]
lib/junit/junit-3.8.2.jar [new file with mode: 0644]
lib/junit_4/junit-4.10-javadoc.jar [new file with mode: 0644]
lib/junit_4/junit-4.10-sources.jar [new file with mode: 0644]
lib/junit_4/junit-4.10.jar [new file with mode: 0644]
lib/nblibraries.properties [new file with mode: 0644]
manifest.mf [new file with mode: 0644]
nbproject/build-impl.xml [new file with mode: 0644]
nbproject/genfiles.properties [new file with mode: 0644]
nbproject/project.properties [new file with mode: 0644]
nbproject/project.xml [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member_Comparator_UserID.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member_Comparator_UserName.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/logging/PacketHandler.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/logging/TableModelHandler.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/message/MessageAbstract.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/message/MessageLogRecord.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/message/MessagePresence.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/net/Network.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEvent.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEventGenerator.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEventListener.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerAbstract.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerTCP.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerUDP.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerUDPMulticast.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/net/WSYD_SocketAddress.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/libs/net/problem.txt [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/management/ServerManagement.form [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/management/ServerManagement.java [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/resources/ScroogledKeepCalmMug.icon.png [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/resources/ScroogledKeepCalmMug.png [new file with mode: 0644]
src/uk/ac/ntu/n0521366/wsyd/server/ServerSocial.java [new file with mode: 0644]
test/uk/ac/ntu/n0521366/wsyd/libs/WSYD_MemberTest.java [new file with mode: 0644]
test/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member_Comparator_UserIDTest.java [new file with mode: 0644]
test/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member_Comparator_UserNameTest.java [new file with mode: 0644]
test/uk/ac/ntu/n0521366/wsyd/libs/net/WSYD_SocketAddressTest.java [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..74033bd
--- /dev/null
@@ -0,0 +1,4 @@
+build/*
+dist/*
+nbproject/private
+
diff --git a/WSYD_TestData.csv b/WSYD_TestData.csv
new file mode 100644 (file)
index 0000000..00244dc
--- /dev/null
@@ -0,0 +1,7 @@
+# 0     , 1       , 2       , 3              , 4  , 5        , 6        , 7      , 8                  , 9
+# userID, userName, password, currentLocation, bio, birthDate, interests, friends, friendsRequestsSent, friendsRequestsReceived
+1,TJ,letmein,Vale of Belvoir,Hacking all the way,1965-06-01,Computers~Driving~DIY~Gardening,2~3,4,5
+2,Eddie,letmein,Nottingham,Be glad when exams are over,1993-04-05,Computers~Games~Gym~Music,1~5,0,3
+3,David,letmein,Lufbra, DIY is a pain,1981-03-21,Computers~DIY~Music,1,2,0
+4,Jerry,letmein,Granby,Zzzzzz,1936-09-27,Farming,0,0,1
+5,Silver,letmein,Kennel,Woof!,2011-04-27,Hunting,2,1,0
diff --git a/build.xml b/build.xml
new file mode 100644 (file)
index 0000000..757ee5e
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- You may freely edit this file. See commented blocks below for -->
+<!-- some examples of how to customize the build. -->
+<!-- (If you delete it and reopen the project it will be recreated.) -->
+<!-- By default, only the Clean and Build commands use this build script. -->
+<!-- Commands such as Run, Debug, and Test only use this build script if -->
+<!-- the Compile on Save feature is turned off for the project. -->
+<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
+<!-- in the project's Project Properties dialog box.-->
+<project name="WSYD" default="default" basedir=".">
+    <description>Builds, tests, and runs the project WSYD.</description>
+    <import file="nbproject/build-impl.xml"/>
+    <!--
+
+    There exist several targets which are by default empty and which can be 
+    used for execution of your tasks. These targets are usually executed 
+    before and after some main targets. They are: 
+
+      -pre-init:                 called before initialization of project properties
+      -post-init:                called after initialization of project properties
+      -pre-compile:              called before javac compilation
+      -post-compile:             called after javac compilation
+      -pre-compile-single:       called before javac compilation of single file
+      -post-compile-single:      called after javac compilation of single file
+      -pre-compile-test:         called before javac compilation of JUnit tests
+      -post-compile-test:        called after javac compilation of JUnit tests
+      -pre-compile-test-single:  called before javac compilation of single JUnit test
+      -post-compile-test-single: called after javac compilation of single JUunit test
+      -pre-jar:                  called before JAR building
+      -post-jar:                 called after JAR building
+      -post-clean:               called after cleaning build products
+
+    (Targets beginning with '-' are not intended to be called on their own.)
+
+    Example of inserting an obfuscator after compilation could look like this:
+
+        <target name="-post-compile">
+            <obfuscate>
+                <fileset dir="${build.classes.dir}"/>
+            </obfuscate>
+        </target>
+
+    For list of available properties check the imported 
+    nbproject/build-impl.xml file. 
+
+
+    Another way to customize the build is by overriding existing main targets.
+    The targets of interest are: 
+
+      -init-macrodef-javac:     defines macro for javac compilation
+      -init-macrodef-junit:     defines macro for junit execution
+      -init-macrodef-debug:     defines macro for class debugging
+      -init-macrodef-java:      defines macro for class execution
+      -do-jar:                  JAR building
+      run:                      execution of project 
+      -javadoc-build:           Javadoc generation
+      test-report:              JUnit report generation
+
+    An example of overriding the target for project execution could look like this:
+
+        <target name="run" depends="WSYD-impl.jar">
+            <exec dir="bin" executable="launcher.exe">
+                <arg file="${dist.jar}"/>
+            </exec>
+        </target>
+
+    Notice that the overridden target depends on the jar target and not only on 
+    the compile target as the regular run target does. Again, for a list of available 
+    properties which you can use, check the target you are overriding in the
+    nbproject/build-impl.xml file. 
+
+    -->
+</project>
diff --git a/index.html b/index.html
new file mode 100644 (file)
index 0000000..dd51390
--- /dev/null
@@ -0,0 +1,759 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+  <meta charset="utf-8"/>
+  <title>
+   Made with Remarkable!
+  </title>
+  <link href="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.1/styles/github.min.css" rel="stylesheet"/>
+  <script src="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.1/highlight.min.js">
+  </script>
+  <script>
+   hljs.initHighlightingOnLoad();
+  </script>
+  <script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML" type="text/javascript">
+  </script>
+  <script type="text/javascript">
+   MathJax.Hub.Config({"showProcessingMessages" : false,"messageStyle" : "none","tex2jax": { inlineMath: [ [ "$", "$" ] ] }});
+  </script>
+  <style type="text/css">
+   @import url(http://fonts.googleapis.com/css?family=Indie+Flower);body{font-family:Indie Flower,Daniel,cursive}h1,h2{text-align:center}blockquote{margin:1.5em 10px;padding:.5em 10px}blockquote:before{color:#000;content:open-quote;font-size:2em;line-height:.1em;margin-right:.25em;vertical-align:-.4em}blockquote:after{color:#000;content:close-quote;font-size:2em;line-height:.1em;margin-left:.25em;vertical-align:-.4em}blockquote p{display:inline}hr{border:0;border-top:dashed 2px gray}table{padding:0}table tr{border-top:1px solid #ccc;background-color:#fff;margin:0;padding:0}table tr:nth-child(2n){background-color:#aaa}table tr th{font-weight:700;border:1px solid #ccc;text-align:left;margin:0;padding:6px 13px}table tr td{border:1px solid #ccc;text-align:left;margin:0;padding:6px 13px}table tr td :first-child,table tr th :first-child{margin-top:0}table tr td:last-child,table tr th :last-child{margin-bottom:0}
+  </style>
+ </head>
+ <body>
+  <h1 id="we-stealz-your-dataz-wsyd-social-network">
+   We Stealz Your Dataz (WSYD) social network
+  </h1>
+  <p>
+   This document is created as
+   <code>
+    index.md
+   </code>
+   in Markdown text. It can be manually converted to HTML using pandoc with the command:
+  </p>
+  <pre><code>pandoc --standalone --from=markdown --to=html5 --output=index.html index.md
+</code></pre>
+  <h2 id="introduction">
+   Introduction
+  </h2>
+  <p>
+   In this Java project I wanted to challenge myself to use some of the same concepts I’ve used in previous C++ projects in order to contrast and understand the different approaches taken in both languages.
+  </p>
+  <p>
+   The project criteria already suggested to me that the design would need to implement
+   <strong>
+    multi-threading
+   </strong>
+   and as there was no specific requirement to use a database I opted to use
+   <strong>
+    object serialization
+   </strong>
+   which would provide two useful functions: persisting the member data to file and passing pure java objects over the network.
+  </p>
+  <p>
+   I adopted the
+   <strong>
+    Javadoc
+   </strong>
+   standard for all my code annotations and as a result the
+   <strong>
+    <a href="dist/javadoc/index.html">
+     entire code base is consistently documented
+    </a>
+   </strong>
+   in the same style as the Java API in HTML without requiring me to write separate documentation.
+  </p>
+  <p>
+   I used
+   <a href="Made with Remarkable!">
+    <strong>
+     Remarkable
+    </strong>
+   </a>
+   , a
+   <a href="http://daringfireball.net/projects/markdown/syntax">
+    <strong>
+     Markdown text
+    </strong>
+   </a>
+   editor with live HTML preview, to create this report and allow the report and the Javadoc to link together as one HTML web.
+  </p>
+  <p>
+   After becoming familiar with the NetBeans IDE I explored its Test-Driven Development (TDD) support using
+   <strong>
+    <a href="http://junit.org">
+     Junit
+    </a>
+   </strong>
+   and found it provided valuable feedback on bugs in my code that were not obvious before the tests were written.
+  </p>
+  <p>
+   I set out to implement the assessment criteria specified for a
+   <strong>
+    First class
+   </strong>
+   result.
+  </p>
+  <h2 id="design">
+   Design
+  </h2>
+  <p>
+   The criteria requires a minimum of three separate executable processes: GUI User client, Social Server and Chat Server. I opted to add a fourth: GUI Server Management client. This allowed me to implement the Social and Chat servers as true
+   <strong>
+    background service daemons
+   </strong>
+   and have them communicate with the GUI Server Management client over the network.
+  </p>
+  <p>
+   As well as displaying log messages received from the servers the GUI Server Management client allows the
+   <strong>
+    independent restarting or stopping
+   </strong>
+   of each server.
+  </p>
+  <p>
+   The GUI User client makes extensive use of Swing components. The NetBeans IDE is responsible for generating most of the GUI skeleton code for handling user actions via
+   <code>
+    ActionEvent
+   </code>
+   and
+   <code>
+    PropertyChange
+   </code>
+   event listeners and several function-specific child windows and dialogs for presentation during log-in, profile editing, chat and configuration. After the GUI skeleton code was complete I added support for the network services background threads and then implemented the links between them and the the GUI components.
+  </p>
+  <p>
+   Since the criteria requires a range of network functionality shared across clients and servers I implemented a
+   <strong>
+    <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/package-frame.html">
+     common network library
+    </a>
+   </strong>
+   . This network library, and the server services it implements, needs to support
+   <strong>
+    multi-threading
+   </strong>
+   especially on the client since listening for incoming connections is an
+   <strong>
+    operation that blocks
+   </strong>
+   in the operating system kernel. If those blocking operations were performed on the GUI Event Dispatch thread it would cause
+   <strong>
+    poor response
+   </strong>
+   in the user interface.
+  </p>
+  <p>
+   Fortunately the
+   <a href="http://docs.oracle.com/javase/7/docs/api/javax/swing/SwingWorker.html">
+    <code>
+     javax.swing.SwingWorker
+    </code>
+   </a>
+   class was designed especially for this purpose. As it implements the
+   <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html">
+    <code>
+     java.lang.Runnable
+    </code>
+   </a>
+   interface too it made the
+   <strong>
+    perfect base class
+   </strong>
+   for my own
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerAbstract.html">
+    <code>
+     NetworkServerAbstract
+    </code>
+   </a>
+   class because I could use it for both service daemon (non-GUI) and client (Swing GUI) applications.
+  </p>
+  <p>
+   I considered adopting an existing network protocol (or designing my own) for the communications between servers and clients and clients with clients. I considered Hyper Text Transport Protocol (HTTP) for the server to client connections. For client chat I looked at Internet Relay Chat (IRC) and its related Client To Client Protocol (CTCP) and the Direct Client Connect (DCC) sub-protocol (that supports file transfer) which together implement a server-mediated peer-to-peer (P2P) approach as a chat solution that has been in use since the 1990s.
+  </p>
+  <p>
+   The problem I found was that each
+   <em>
+    type
+   </em>
+   of communication would require a different protocol to be implemented. As well as requiring a lot of work to implement this had the risk of introducing
+   <strong>
+    subtle hard-to-find runtime bugs
+   </strong>
+   in the live communications.
+  </p>
+  <p>
+   I investigated using some form of pre-existing
+   <a href="http://blog.codepath.com/2013/01/06/asynchronous-processing-in-web-applications-part-2-developers-need-to-understand-message-queues/">
+    <code>
+     Message Queue
+    </code>
+   </a>
+   implementation such as Java
+   <code>
+    Enterprise Edition (EE)
+   </code>
+   <a href="http://docs.oracle.com/javaee/6/tutorial/doc/bncdq.html">
+    <code>
+     Java Message Service (JMS)
+    </code>
+   </a>
+   but decided most of these would be too over-engineered and complex for my requirements and therefore I would
+   <strong>
+    create a simple message queue
+   </strong>
+   implementation myself.
+  </p>
+  <p>
+   I’d already decided I’d use serialization for persisting member data and after looking at some examples of how easy it is to
+   <strong>
+    serialize Java objects
+   </strong>
+   over any kind of stream, including
+   <strong>
+    network streams
+   </strong>
+   , I realised it could make network communications reliable and consistent with minimal additional code both at run-time and at coding time since Java provides serialization as part of the Virtual Machine. The only requirement to support serialization is that each class of object that would be passed over the network should implement
+   <a href="http://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html">
+    <code>
+     java.io.Serializable
+    </code>
+   </a>
+   - which usually only requires a single addition:
+  </p>
+  <pre><code>public MyClass extends SomeClass implements java.io.Serializable {
+</code></pre>
+  <p>
+   This design decision makes it as
+   <strong>
+    easy to pass an image or other file-type over the network
+   </strong>
+   as it does to send simple text and with 100% reliability. In my implementation I wrapped each object being sent over the network in a
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html">
+    <code>
+     NetworkMessage
+    </code>
+   </a>
+   wrapper class that contains additional information such as sender and target service and the
+   <em>
+    intent
+   </em>
+   of the message.
+  </p>
+  <p>
+   A further issue I considered was
+   <strong>
+    how the clients would discover the servers
+   </strong>
+   . Since in the lab setting there will be no facility for adding Domain Name System (DNS) records to the local DNS server zone file it would require either:
+  </p>
+  <ol>
+   <li>
+    hard-coding an IP address into the source-code and rebuilding the code each time the project was demonstrated
+   </li>
+   <li>
+    providing for the user to enter the IP address manually when each application starts
+   </li>
+   <li>
+    Using some form of automatic network discovery
+   </li>
+  </ol>
+  <p>
+   I decided to opt for (3) implemented using
+   <a href="http://docs.oracle.com/javase/7/docs/api/java/net/MulticastSocket.html">
+    <code>
+     java.net.MulticastSocket
+    </code>
+   </a>
+   with a fall-back to (2) in case automatic discovery fails.
+  </p>
+  <p>
+   Clients will create long-running Transmission Control Protocol (TCP) connections to the server for passing serialized
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html">
+    <code>
+     NetworkMessage
+    </code>
+   </a>
+   objects back and forth, starting with log-in.
+  </p>
+  <p>
+   Clients can discover each other via the Organisation-scope Multicast group, or if it isn’t available, by requests to the Chat server which will mediate chat authorisations.
+  </p>
+  <p>
+   I implemented the asynchronous update from server to client requirement with User Datagram Protocol (UDP). If the Multicast group isn’t active the update service will fall-back to using Unicast to each known client.
+  </p>
+  <p>
+   To avoid using something other than object serialization for sending notifications via UDP packets (which do not have support for streams) I used the
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html">
+    <code>
+     NetworkMessage
+    </code>
+   </a>
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html#serialize(uk.ac.ntu.n0521366.wsyd.libs.net.NetworkMessage)">
+    <code>
+     serialize()
+    </code>
+   </a>
+   and
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html#deserialize(byte[])">
+    <code>
+     deserialize()
+    </code>
+   </a>
+   methods to convert the object to and from a
+   <code>
+    byte[]
+   </code>
+   that can be transported by
+   <a href="http://docs.oracle.com/javase/7/docs/api/java/net/DatagramPacket.html">
+    <code>
+     java.net.DatagramPacket
+    </code>
+   </a>
+   .
+  </p>
+  <h2 id="functionality">
+   Functionality
+  </h2>
+  <p>
+   <mark>
+    TODO:
+   </mark>
+   Describe each application’s core functionality and stand-out features
+  </p>
+  <h3 id="social-server">
+   Social Server
+  </h3>
+  <h3 id="chat-sever">
+   Chat Sever
+  </h3>
+  <h3 id="gui-server-management-client">
+   GUI Server Management client
+  </h3>
+  <h3 id="gui-user-client">
+   GUI User client
+  </h3>
+  <h2 id="code">
+   Code
+  </h2>
+  <p>
+   I began by designing and creating the skeleton GUI client using the NetBeans IDE.
+  </p>
+  <p>
+   Questions that came up during GUI client design led to coding
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member.html">
+    <code>
+     WSYD_Member
+    </code>
+   </a>
+   , the class that wraps the data structure representing each member of the WSYD social network. As well as data storage I implemented a static CSV parser
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member.html#createWSYD_Member(java.lang.String)">
+    <code>
+     createWYSD_Member()
+    </code>
+   </a>
+   , the inverse method
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member.html#toString()">
+    <code>
+     toString()
+    </code>
+   </a>
+   , and support for the
+   <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html">
+    <code>
+     java.lang.Comparable
+    </code>
+   </a>
+   interface in
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member.html#compareTo(uk.ac.ntu.n0521366.wsyd.libs.WSYD_Member)">
+    <code>
+     compareTo()
+    </code>
+   </a>
+   . I inherited serialization for free because all the attribute classes already support the
+   <a href="http://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html">
+    <code>
+     java.io.Serializable
+    </code>
+   </a>
+   interface.
+  </p>
+  <p>
+   The networking library was the next task. I investigated Java’s networking support and looked at several code snippets and examples at sites such as
+   <em>
+    StackOverflow
+   </em>
+   as well as the official Java API documentation. I realised there were two requirements: the network services themselves, and the object passing.
+  </p>
+  <p>
+   Objects being passed need to encode the destination. For example, a “
+   <em>
+    Friend Is Online
+   </em>
+   ” notification needs to be handled by a client method that knows what to do with that information. The network services only deal with transport not context. Therefore
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html">
+    <code>
+     NetworkMessage
+    </code>
+   </a>
+   objects know their
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html#getSender()">
+    <code>
+     sender
+    </code>
+   </a>
+   ,
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html#setTarget(java.lang.String)">
+    <code>
+     target
+    </code>
+   </a>
+   and the
+   <strong>
+    <code>
+     intent
+    </code>
+   </strong>
+   -
+   <code>
+    java.lang.String
+   </code>
+   s that allow received messages to be dispatched by the server service to the correct handler and for acknowledgements to be sent in return.
+  </p>
+  <p>
+   The mechanism for passing received objects from the server service to the GUI (or server process) implements a custom event
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEvent.html">
+    <code>
+     NetworkMessageEvent
+    </code>
+   </a>
+   which extends
+   <a href="http://docs.oracle.com/javase/7/docs/api/java/util.EventObject.html">
+    <code>
+     java.util.EventObject
+    </code>
+   </a>
+   and adds  interface
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEventGenerator.html">
+    <code>
+     NetworkMessageEventGenerator
+    </code>
+   </a>
+   to
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerAbstract.html">
+    <code>
+     NetworkServerAbstract
+    </code>
+   </a>
+   which registers objects of classes that implement the
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEventListener.html">
+    <code>
+     NetworkMessageEventListener
+    </code>
+   </a>
+   interface. These objects are notified when a new
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html">
+    <code>
+     NetworkMessage
+    </code>
+   </a>
+   is received.
+  </p>
+  <p>
+   The network server classes all inherit their core functionality from
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerAbstract.html">
+    <code>
+     NetworkServerAbstract
+    </code>
+   </a>
+   . Common functionality implemented here includes
+   <a href="http://docs.oracle.com/javase/7/docs/api/javax/swing/SwingWorker.html">
+    <code>
+     javax.swing.SwingWorker
+    </code>
+   </a>
+   multi-threading,
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEvent.html">
+    <code>
+     NetworkMessageEvent
+    </code>
+   </a>
+   dispatching, the socket listener main loop in
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerAbstract.html#doInBackground()">
+    <code>
+     doInBackground()
+    </code>
+   </a>
+   , logging, and unsolicited outbound message queuing via
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerAbstract.html#queueMessage(uk.ac.ntu.n0521366.wsyd.libs.net.NetworkMessage)">
+    <code>
+     queueMessage()
+    </code>
+   </a>
+   and
+   <a href="dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerAbstract.html#dequeueMessage()">
+    <code>
+     dequeueMessage()
+    </code>
+   </a>
+   .
+  </p>
+  <p>
+   The implementing classes
+   <code>
+    NetworkServerTCP
+   </code>
+   ,
+   <code>
+    NetworkServerUDP
+   </code>
+   and
+   <code>
+    NetworkServerMulticast
+   </code>
+   add minimal code via method overrides to support protocol specific functionality.
+  </p>
+  <p>
+   <mark>
+    TODO:
+   </mark>
+   discuss other classes and notable features and coding/learning experiences
+  </p>
+  <p>
+   I used NetBeans
+   <strong>
+    Action Items
+   </strong>
+   to keep track of
+   <code>
+    TODO:
+   </code>
+   and
+   <code>
+    FIXME:
+   </code>
+   outstanding tasks, and
+   <code>
+    XXX:
+   </code>
+   to call attention to important notes.
+  </p>
+  <h2 id="testing">
+   Testing
+  </h2>
+  <p>
+   I used the NetBeans-generated
+   <code>
+    Junit version 4
+   </code>
+   test class templates as the basis for my
+   <strong>
+    Unit Tests
+   </strong>
+   , creating
+   <strong>
+    close to 100% coverage
+   </strong>
+   of the non-network library code. I didn’t write tests for network connections functionality although they would have been very useful due to the amount of time, and the difficulty, I estimated it would require to develop them - much more than writing the network library itself.
+  </p>
+  <p>
+   <mark>
+    TODO:
+   </mark>
+   add examples of bugs the testing revealed and experiences in writing the more complicated test cases.
+  </p>
+  <h2 id="bibliography">
+   Bibliography
+  </h2>
+  <p>
+   This is a list of many, but not all, of the very many resources I looked at in developing this project. Some were of more use than others but all of these contributed something to the development, design and coding.
+  </p>
+  <h3 id="references">
+   References
+  </h3>
+  <p>
+   <a href="http://en.wikipedia.org/wiki/Multicast_address#Administratively_Scoped_IPv4_Multicast_addresses">
+    Administratively Scoped IPv4 Multicast Addresses
+   </a>
+  </p>
+  <h3 id="html-formatted-note-taking">
+   HTML formatted note taking
+  </h3>
+  <p>
+   <a href="http://pandoc.org/demo/example9/pandocs-markdown.html">
+    Pandoc extended Markdown syntax
+   </a>
+  </p>
+  <p>
+   <a href="http://daringfireball.net/projects/markdown/syntax">
+    Markdown syntax
+   </a>
+  </p>
+  <h3 id="netbeans-ide">
+   NetBeans IDE
+  </h3>
+  <p>
+   <a href="http://stackoverflow.com/questions/15922390/netbeans-how-to-change-author">
+    How to configure \@author tag
+   </a>
+  </p>
+  <p>
+   <a href="https://netbeans.org/kb/docs/java/junit-intro.html#Exercise_20">
+    Writing JUnit tests
+   </a>
+  </p>
+  <h3 id="java-documentation">
+   Java documentation
+  </h3>
+  <p>
+   <a href="http://docs.oracle.com/javase/7/docs/index.html">
+    Java Platform Standard Edition 7 Documentation
+   </a>
+  </p>
+  <p>
+   <a href="http://docs.oracle.com/javase/7/docs/api/">
+    Java SE 7 API
+   </a>
+  </p>
+  <p>
+   <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html">
+    Controlling Access to Members of a Class
+   </a>
+  </p>
+  <h3 id="java-generics">
+   Java Generics
+  </h3>
+  <p>
+   <a href="https://docs.oracle.com/javase/tutorial/java/generics/">
+    Java Tutorials: Generics
+   </a>
+  </p>
+  <p>
+   <a href="https://docs.oracle.com/javase/tutorial/extra/generics/index.html">
+    Java Tutorials: Generics (by Gilad Bracha)
+   </a>
+  </p>
+  <h3 id="serialization">
+   Serialization
+  </h3>
+  <p>
+   <a href="http://www.javaworld.com/article/2077539/learn-java/java-tip-40--object-transport-via-datagram-packets.html">
+    Serialize Object over UDP
+   </a>
+  </p>
+  <p>
+   <a href="http://howtodoinjava.com/2012/11/08/a-guide-to-object-cloning-in-java/">
+    Deep cloning techniques
+   </a>
+  </p>
+  <p>
+   <a href="http://stackoverflow.com/questions/8642012/why-should-a-comparator-implement-serializable">
+    Why should a Comparator implement Serializable
+   </a>
+  </p>
+  <h3 id="multi-threading">
+   Multi-threading
+  </h3>
+  <p>
+   <a href="http://wenda.soso.io/questions/2765593/using-swingworker-to-implement-a-udp-client-server">
+    Using SwingWorker to implement a UDP Client/Server
+   </a>
+  </p>
+  <p>
+   <a href="http://www.tutorialspoint.com/javaexamples/net_multisoc.htm">
+    Multi-threaded Network Server
+   </a>
+  </p>
+  <p>
+   <a href="http://stackoverflow.com/questions/782265/how-do-i-use-swingworker-in-java">
+    How do I use SwingWorker in Java
+   </a>
+  </p>
+  <p>
+   <a href="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/">
+    Concurrency in Swing (multi-threading for background tasks)
+   </a>
+  </p>
+  <p>
+   <a href="https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html">
+    Synchronized Methods
+   </a>
+  </p>
+  <p>
+   <a href="https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html">
+    Synchronized Statements
+   </a>
+  </p>
+  <h3 id="network">
+   Network
+  </h3>
+  <p>
+   <a href="https://docs.oracle.com/javase/tutorial/networking/nifs/parameters.html">
+    Getting Network Interface Parameters
+   </a>
+  </p>
+  <p>
+   <a href="http://www.markhneedham.com/blog/2013/07/14/java-testing-a-socket-is-listening-on-all-network-interfaceswildcard-interface/">
+    Testing a socket is listening on all network interfaces/wildcard interface
+   </a>
+  </p>
+  <p>
+   <a href="https://blogs.oracle.com/CoreJavaTechTips/entry/socket_logging">
+    Socket Logging (network logger)
+   </a>
+  </p>
+  <h3 id="tutorials">
+   Tutorials
+  </h3>
+  <p>
+   <a href="http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html">
+    How to Write Doc Comments for the Javadoc Tool
+   </a>
+  </p>
+  <p>
+   <a href="http://examples.javacodegeeks.com/core-java/io/java-imageio-write-image-to-file/">
+    Write Image to File
+   </a>
+  </p>
+  <p>
+   <a href="http://stackoverflow.com/questions/1318980/how-to-iterate-over-a-treemap">
+    Iterating over a TreeMap
+   </a>
+  </p>
+  <p>
+   <a href="http://stackoverflow.com/questions/285177/how-do-i-call-one-constructor-from-another-in-java">
+    How do I call one constructor from another in Java
+   </a>
+  </p>
+  <p>
+   <a href="http://stackoverflow.com/questions/3108297/james-goslings-explanation-of-why-javas-byte-is-signed">
+    James Gosling’s explanation of why Java’s byte is signed
+   </a>
+  </p>
+  <p>
+   <a href="http://stackoverflow.com/questions/7300676/when-to-use-flush-in-java">
+    When to use flush() in Java
+   </a>
+  </p>
+  <p>
+   <a href="http://stackoverflow.com/questions/1816673/how-do-i-check-if-a-file-exists-in-java">
+    How do I check if a file exists in Java
+   </a>
+  </p>
+  <p>
+   <a href="http://stackoverflow.com/questions/767372/java-string-equals-versus">
+    Java String.equals() versus ==
+   </a>
+  </p>
+  <p>
+   <a href="http://stackoverflow.com/questions/3983360/calculating-byte-size-of-java-object">
+    Calculating byte-size of Java object
+   </a>
+  </p>
+  <p>
+   <a href="http://www.javaworld.com/article/2077333/core-java/mr-happy-object-teaches-custom-events.html">
+    Mr. Happy Object teaches custom events
+   </a>
+  </p>
+ </body>
+</html>
\ No newline at end of file
diff --git a/index.md b/index.md
new file mode 100644 (file)
index 0000000..a0641cd
--- /dev/null
+++ b/index.md
@@ -0,0 +1,205 @@
+# We Stealz Your Dataz (WSYD) social network
+
+This document is created as `index.md` in Markdown text. It can be manually converted to HTML using pandoc with the command:
+
+    pandoc --standalone --from=markdown --to=html5 --output=index.html index.md
+
+
+## Introduction
+
+In this Java project I wanted to challenge myself to use some of the same concepts I've used in previous C++ projects in order to contrast and understand the different approaches taken in both languages.
+
+The project criteria already suggested to me that the design would need to implement **multi-threading** and as there was no specific requirement to use a database I opted to use **object serialization** which would provide two useful functions: persisting the member data to file and passing pure java objects over the network.
+
+I adopted the **Javadoc** standard for all my code annotations and as a result the **[entire code base is consistently documented](dist/javadoc/index.html)** in the same style as the Java API in HTML without requiring me to write separate documentation.
+
+I used [**Remarkable**](Made with Remarkable!), a [**Markdown text**](http://daringfireball.net/projects/markdown/syntax) editor with live HTML preview, to create this report and allow the report and the Javadoc to link together as one HTML web.
+
+After becoming familiar with the NetBeans IDE I explored its Test-Driven Development (TDD) support using **[Junit](http://junit.org)** and found it provided valuable feedback on bugs in my code that were not obvious before the tests were written.
+
+I set out to implement the assessment criteria specified for a **First class** result.
+
+## Design
+
+The criteria requires a minimum of three separate executable processes: GUI User client, Social Server and Chat Server. I opted to add a fourth: GUI Server Management client. This allowed me to implement the Social and Chat servers as true **background service daemons** and have them communicate with the GUI Server Management client over the network.
+
+As well as displaying log messages received from the servers the GUI Server Management client allows the **independent restarting or stopping** of each server.
+
+The GUI User client makes extensive use of Swing components. The NetBeans IDE is responsible for generating most of the GUI skeleton code for handling user actions via `ActionEvent` and `PropertyChange` event listeners and several function-specific child windows and dialogs for presentation during log-in, profile editing, chat and configuration. After the GUI skeleton code was complete I added support for the network services background threads and then implemented the links between them and the the GUI components.
+
+Since the criteria requires a range of network functionality shared across clients and servers I implemented a **[common network library](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/package-frame.html)**. This network library, and the server services it implements, needs to support **multi-threading** especially on the client since listening for incoming connections is an **operation that blocks** in the operating system kernel. If those blocking operations were performed on the GUI Event Dispatch thread it would cause **poor response** in the user interface.
+
+Fortunately the [`javax.swing.SwingWorker`](http://docs.oracle.com/javase/7/docs/api/javax/swing/SwingWorker.html) class was designed especially for this purpose. As it implements the [`java.lang.Runnable`](http://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html) interface too it made the **perfect base class** for my own [`NetworkServerAbstract`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerAbstract.html) class because I could use it for both service daemon (non-GUI) and client (Swing GUI) applications.
+
+I considered adopting an existing network protocol (or designing my own) for the communications between servers and clients and clients with clients. I considered Hyper Text Transport Protocol (HTTP) for the server to client connections. For client chat I looked at Internet Relay Chat (IRC) and its related Client To Client Protocol (CTCP) and the Direct Client Connect (DCC) sub-protocol (that supports file transfer) which together implement a server-mediated peer-to-peer (P2P) approach as a chat solution that has been in use since the 1990s.
+
+The problem I found was that each *type* of communication would require a different protocol to be implemented. As well as requiring a lot of work to implement this had the risk of introducing **subtle hard-to-find runtime bugs** in the live communications.
+
+I investigated using some form of pre-existing [`Message Queue`](http://blog.codepath.com/2013/01/06/asynchronous-processing-in-web-applications-part-2-developers-need-to-understand-message-queues/)  implementation such as Java `Enterprise Edition (EE)` [`Java Message Service (JMS)`](http://docs.oracle.com/javaee/6/tutorial/doc/bncdq.html) but decided most of these would be too over-engineered and complex for my requirements and therefore I would **create a simple message queue** implementation myself.
+
+I'd already decided I'd use serialization for persisting member data and after looking at some examples of how easy it is to **serialize Java objects** over any kind of stream, including **network streams**, I realised it could make network communications reliable and consistent with minimal additional code both at run-time and at coding time since Java provides serialization as part of the Virtual Machine. The only requirement to support serialization is that each class of object that would be passed over the network should implement [`java.io.Serializable`](http://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html) - which usually only requires a single addition:
+
+    public MyClass extends SomeClass implements java.io.Serializable {
+
+This design decision makes it as **easy to pass an image or other file-type over the network** as it does to send simple text and with 100% reliability. In my implementation I wrapped each object being sent over the network in a [`NetworkMessage`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html) wrapper class that contains additional information such as sender and target service and the *intent* of the message.
+
+A further issue I considered was **how the clients would discover the servers**. Since in the lab setting there will be no facility for adding Domain Name System (DNS) records to the local DNS server zone file it would require either:
+
+ 1. hard-coding an IP address into the source-code and rebuilding the code each time the project was demonstrated
+ 2. providing for the user to enter the IP address manually when each application starts
+ 3. Using some form of automatic network discovery
+
+I decided to opt for (3) implemented using [`java.net.MulticastSocket`](http://docs.oracle.com/javase/7/docs/api/java/net/MulticastSocket.html) with a fall-back to (2) in case automatic discovery fails.
+
+Clients will create long-running Transmission Control Protocol (TCP) connections to the server for passing serialized [`NetworkMessage`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html) objects back and forth, starting with log-in.
+
+Clients can discover each other via the Organisation-scope Multicast group, or if it isn't available, by requests to the Chat server which will mediate chat authorisations.
+
+I implemented the asynchronous update from server to client requirement with User Datagram Protocol (UDP). If the Multicast group isn't active the update service will fall-back to using Unicast to each known client.
+
+To avoid using something other than object serialization for sending notifications via UDP packets (which do not have support for streams) I used the [`NetworkMessage`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html)  [`serialize()`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html#serialize(uk.ac.ntu.n0521366.wsyd.libs.net.NetworkMessage)) and [`deserialize()`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html#deserialize(byte[])) methods to convert the object to and from a `byte[]` that can be transported by [`java.net.DatagramPacket`](http://docs.oracle.com/javase/7/docs/api/java/net/DatagramPacket.html).
+
+
+## Functionality
+
+==TODO:== Describe each application's core functionality and stand-out features
+
+### Social Server
+
+### Chat Sever
+
+### GUI Server Management client
+
+### GUI User client
+
+## Code
+
+I began by designing and creating the skeleton GUI client using the NetBeans IDE.
+
+Questions that came up during GUI client design led to coding [`WSYD_Member`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member.html), the class that wraps the data structure representing each member of the WSYD social network. As well as data storage I implemented a static CSV parser [`createWYSD_Member()`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member.html#createWSYD_Member(java.lang.String)), the inverse method [`toString()`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member.html#toString()), and support for the [`java.lang.Comparable`](http://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html) interface in [`compareTo()`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member.html#compareTo(uk.ac.ntu.n0521366.wsyd.libs.WSYD_Member)). I inherited serialization for free because all the attribute classes already support the [`java.io.Serializable`](http://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html) interface.
+
+The networking library was the next task. I investigated Java's networking support and looked at several code snippets and examples at sites such as *StackOverflow* as well as the official Java API documentation. I realised there were two requirements: the network services themselves, and the object passing.
+
+Objects being passed need to encode the destination. For example, a "*Friend Is Online*" notification needs to be handled by a client method that knows what to do with that information. The network services only deal with transport not context. Therefore [`NetworkMessage`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html) objects know their [`sender`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html#getSender()), [`target`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html#setTarget(java.lang.String)) and the **`intent`** - `java.lang.String`s that allow received messages to be dispatched by the server service to the correct handler and for acknowledgements to be sent in return.
+
+The mechanism for passing received objects from the server service to the GUI (or server process) implements a custom event [`NetworkMessageEvent`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEvent.html) which extends [`java.util.EventObject`](http://docs.oracle.com/javase/7/docs/api/java/util.EventObject.html) and adds  interface [`NetworkMessageEventGenerator`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEventGenerator.html) to [`NetworkServerAbstract`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerAbstract.html) which registers objects of classes that implement the [`NetworkMessageEventListener`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEventListener.html) interface. These objects are notified when a new [`NetworkMessage`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.html) is received. 
+
+The network server classes all inherit their core functionality from [`NetworkServerAbstract`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerAbstract.html). Common functionality implemented here includes [`javax.swing.SwingWorker`](http://docs.oracle.com/javase/7/docs/api/javax/swing/SwingWorker.html) multi-threading, [`NetworkMessageEvent`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEvent.html) dispatching, the socket listener main loop in [`doInBackground()`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerAbstract.html#doInBackground()), logging, and unsolicited outbound message queuing via [`queueMessage()`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerAbstract.html#queueMessage(uk.ac.ntu.n0521366.wsyd.libs.net.NetworkMessage)) and [`dequeueMessage()`](dist/javadoc/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerAbstract.html#dequeueMessage()).
+
+The implementing classes `NetworkServerTCP`, `NetworkServerUDP` and `NetworkServerMulticast` add minimal code via method overrides to support protocol specific functionality.
+
+==TODO:== discuss other classes and notable features and coding/learning experiences
+
+I used NetBeans **Action Items** to keep track of `TODO:` and `FIXME:` outstanding tasks, and `XXX:` to call attention to important notes.
+
+## Testing
+
+I used the NetBeans-generated `JUnit version 4` test class templates as the basis for my **Unit Tests**, creating **close to 100% coverage** of the non-network library code. I didn't write tests for network connections functionality although they would have been very useful due to the amount of time, and the difficulty, I estimated it would require to develop them - much more than writing the network library itself.
+
+==TODO:== add examples of bugs the testing revealed and experiences in writing the more complicated test cases.
+
+
+## Bibliography
+
+This is a list of many, but not all, of the very many resources I looked at in developing this project. Some were of more use than others but all of these contributed something to the development, design and coding.
+
+### References
+
+[Administratively Scoped IPv4 Multicast Addresses](http://en.wikipedia.org/wiki/Multicast_address#Administratively_Scoped_IPv4_Multicast_addresses)
+
+
+### HTML formatted note taking
+
+[Pandoc extended Markdown syntax](http://pandoc.org/demo/example9/pandocs-markdown.html)
+
+[Markdown syntax](http://daringfireball.net/projects/markdown/syntax)
+
+
+### NetBeans IDE
+
+[How to configure \@author tag](http://stackoverflow.com/questions/15922390/netbeans-how-to-change-author)
+
+[Writing JUnit tests](https://netbeans.org/kb/docs/java/junit-intro.html#Exercise_20)
+
+
+### Java documentation
+
+[Java Platform Standard Edition 7 Documentation](http://docs.oracle.com/javase/7/docs/index.html)
+
+[Java SE 7 API](http://docs.oracle.com/javase/7/docs/api/)
+
+[Controlling Access to Members of a Class](https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html)
+
+
+### Java Generics
+
+[Java Tutorials: Generics](https://docs.oracle.com/javase/tutorial/java/generics/)
+
+[Java Tutorials: Generics (by Gilad Bracha)](https://docs.oracle.com/javase/tutorial/extra/generics/index.html)
+
+
+### Serialization
+
+[Serialize Object over UDP](http://www.javaworld.com/article/2077539/learn-java/java-tip-40--object-transport-via-datagram-packets.html)
+
+[Deep cloning techniques](http://howtodoinjava.com/2012/11/08/a-guide-to-object-cloning-in-java/)
+
+[Why should a Comparator implement Serializable](http://stackoverflow.com/questions/8642012/why-should-a-comparator-implement-serializable)
+
+
+
+### Multi-threading
+
+[Using SwingWorker to implement a UDP Client/Server](http://wenda.soso.io/questions/2765593/using-swingworker-to-implement-a-udp-client-server)
+
+[Multi-threaded Network Server](http://www.tutorialspoint.com/javaexamples/net_multisoc.htm)
+
+[How do I use SwingWorker in Java](http://stackoverflow.com/questions/782265/how-do-i-use-swingworker-in-java)
+
+[Concurrency in Swing (multi-threading for background tasks)](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/)
+
+[Synchronized Methods](https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html)
+
+[Synchronized Statements](https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html)
+
+
+### Network
+
+[Getting Network Interface Parameters](https://docs.oracle.com/javase/tutorial/networking/nifs/parameters.html)
+
+[Testing a socket is listening on all network interfaces/wildcard interface](http://www.markhneedham.com/blog/2013/07/14/java-testing-a-socket-is-listening-on-all-network-interfaceswildcard-interface/)
+
+[Socket Logging (network logger)](https://blogs.oracle.com/CoreJavaTechTips/entry/socket_logging)
+
+
+### Tutorials
+
+[How to Write Doc Comments for the Javadoc Tool](http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html)
+
+[Write Image to File](http://examples.javacodegeeks.com/core-java/io/java-imageio-write-image-to-file/)
+
+[Iterating over a TreeMap](http://stackoverflow.com/questions/1318980/how-to-iterate-over-a-treemap)
+
+[How do I call one constructor from another in Java](http://stackoverflow.com/questions/285177/how-do-i-call-one-constructor-from-another-in-java)
+
+[James Gosling's explanation of why Java's byte is signed](http://stackoverflow.com/questions/3108297/james-goslings-explanation-of-why-javas-byte-is-signed)
+
+[When to use flush() in Java](http://stackoverflow.com/questions/7300676/when-to-use-flush-in-java)
+
+[How do I check if a file exists in Java](http://stackoverflow.com/questions/1816673/how-do-i-check-if-a-file-exists-in-java)
+
+[Java String.equals() versus ==](http://stackoverflow.com/questions/767372/java-string-equals-versus)
+
+[Calculating byte-size of Java object](http://stackoverflow.com/questions/3983360/calculating-byte-size-of-java-object)
+
+[Mr. Happy Object teaches custom events](http://www.javaworld.com/article/2077333/core-java/mr-happy-object-teaches-custom-events.html)
+
+[Multiple Swing Timers](https://www.daniweb.com/software-development/java/threads/350345/multipe-swing-timers)
+
+[Launching a browser for a web page](http://docs.oracle.com/javase/6/docs/api/java/awt/Desktop.html#browse%28java.net.URI%29)
+
+[Jbutton that does not show focus](http://www.java2s.com/Code/Java/Swing-JFC/CreateaJButtonthatdoesnotshowfocus.htm)
+
+[Set JFrame iconImage in NetBeans](http://www.areaofthoughts.com/2011/08/netbeans-jframe-properties-iconimage.html)
+
+[JTable scrolling to a specific row](http://stackoverflow.com/questions/853020/jtable-scrolling-to-a-specified-row-index)
\ No newline at end of file
diff --git a/lib/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar b/lib/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar
new file mode 100644 (file)
index 0000000..2cc00f0
Binary files /dev/null and b/lib/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar differ
diff --git a/lib/absolutelayout/AbsoluteLayout.jar b/lib/absolutelayout/AbsoluteLayout.jar
new file mode 100644 (file)
index 0000000..667ffd2
Binary files /dev/null and b/lib/absolutelayout/AbsoluteLayout.jar differ
diff --git a/lib/junit/junit-3.8.2-api.zip b/lib/junit/junit-3.8.2-api.zip
new file mode 100644 (file)
index 0000000..3970a2a
Binary files /dev/null and b/lib/junit/junit-3.8.2-api.zip differ
diff --git a/lib/junit/junit-3.8.2.jar b/lib/junit/junit-3.8.2.jar
new file mode 100644 (file)
index 0000000..c8f711d
Binary files /dev/null and b/lib/junit/junit-3.8.2.jar differ
diff --git a/lib/junit_4/junit-4.10-javadoc.jar b/lib/junit_4/junit-4.10-javadoc.jar
new file mode 100644 (file)
index 0000000..d9184c4
Binary files /dev/null and b/lib/junit_4/junit-4.10-javadoc.jar differ
diff --git a/lib/junit_4/junit-4.10-sources.jar b/lib/junit_4/junit-4.10-sources.jar
new file mode 100644 (file)
index 0000000..75cdd86
Binary files /dev/null and b/lib/junit_4/junit-4.10-sources.jar differ
diff --git a/lib/junit_4/junit-4.10.jar b/lib/junit_4/junit-4.10.jar
new file mode 100644 (file)
index 0000000..954851e
Binary files /dev/null and b/lib/junit_4/junit-4.10.jar differ
diff --git a/lib/nblibraries.properties b/lib/nblibraries.properties
new file mode 100644 (file)
index 0000000..5de11c8
--- /dev/null
@@ -0,0 +1,21 @@
+libs.absolutelayout.classpath=\
+    ${base}/absolutelayout/AbsoluteLayout.jar
+libs.absolutelayout.displayName=Absolute Layout
+libs.CopyLibs.classpath=\
+    ${base}/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar
+libs.CopyLibs.displayName=CopyLibs Task
+libs.CopyLibs.prop-version=2.0
+libs.junit.classpath=\
+    ${base}/junit/junit-3.8.2.jar
+libs.junit.displayName=JUnit 3.8.2
+libs.junit.javadoc=\
+    ${base}/junit/junit-3.8.2-api.zip
+libs.junit.prop-maven-dependencies=junit:junit:3.8.2:jar
+libs.junit_4.classpath=\
+    ${base}/junit_4/junit-4.10.jar
+libs.junit_4.displayName=JUnit 4.10
+libs.junit_4.javadoc=\
+    ${base}/junit_4/junit-4.10-javadoc.jar
+libs.junit_4.prop-maven-dependencies=junit:junit:4.10:jar
+libs.junit_4.src=\
+    ${base}/junit_4/junit-4.10-sources.jar
diff --git a/manifest.mf b/manifest.mf
new file mode 100644 (file)
index 0000000..328e8e5
--- /dev/null
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+X-COMMENT: Main-Class will be added automatically by build
+
diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml
new file mode 100644 (file)
index 0000000..d78efdf
--- /dev/null
@@ -0,0 +1,1438 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+*** GENERATED FROM project.xml - DO NOT EDIT  ***
+***         EDIT ../build.xml INSTEAD         ***
+
+For the purpose of easier reading the script
+is divided into following sections:
+
+  - initialization
+  - compilation
+  - jar
+  - execution
+  - debugging
+  - javadoc
+  - test compilation
+  - test execution
+  - test debugging
+  - applet
+  - cleanup
+
+        -->
+<project xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:jaxrpc="http://www.netbeans.org/ns/j2se-project/jax-rpc" basedir=".." default="default" name="WSYD-impl">
+    <fail message="Please build using Ant 1.8.0 or higher.">
+        <condition>
+            <not>
+                <antversion atleast="1.8.0"/>
+            </not>
+        </condition>
+    </fail>
+    <target depends="test,jar,javadoc" description="Build and test whole project." name="default"/>
+    <!-- 
+                ======================
+                INITIALIZATION SECTION 
+                ======================
+            -->
+    <target name="-pre-init">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target depends="-pre-init" name="-init-private">
+        <property file="nbproject/private/config.properties"/>
+        <property file="nbproject/private/configs/${config}.properties"/>
+        <property file="nbproject/private/private.properties"/>
+    </target>
+    <target name="-pre-init-libraries">
+        <property location="./lib/nblibraries.properties" name="libraries.path"/>
+        <dirname file="${libraries.path}" property="libraries.dir.nativedirsep"/>
+        <pathconvert dirsep="/" property="libraries.dir">
+            <path path="${libraries.dir.nativedirsep}"/>
+        </pathconvert>
+        <basename file="${libraries.path}" property="libraries.basename" suffix=".properties"/>
+        <available file="${libraries.dir}/${libraries.basename}-private.properties" property="private.properties.available"/>
+    </target>
+    <target depends="-pre-init-libraries" if="private.properties.available" name="-init-private-libraries">
+        <loadproperties encoding="ISO-8859-1" srcfile="${libraries.dir}/${libraries.basename}-private.properties">
+            <filterchain>
+                <replacestring from="$${base}" to="${libraries.dir}"/>
+                <escapeunicode/>
+            </filterchain>
+        </loadproperties>
+    </target>
+    <target depends="-pre-init,-init-private,-init-private-libraries" name="-init-libraries">
+        <loadproperties encoding="ISO-8859-1" srcfile="${libraries.path}">
+            <filterchain>
+                <replacestring from="$${base}" to="${libraries.dir}"/>
+                <escapeunicode/>
+            </filterchain>
+        </loadproperties>
+    </target>
+    <target depends="-pre-init,-init-private,-init-libraries" name="-init-user">
+        <property file="${user.properties.file}"/>
+        <!-- The two properties below are usually overridden -->
+        <!-- by the active platform. Just a fallback. -->
+        <property name="default.javac.source" value="1.4"/>
+        <property name="default.javac.target" value="1.4"/>
+    </target>
+    <target depends="-pre-init,-init-private,-init-libraries,-init-user" name="-init-project">
+        <property file="nbproject/configs/${config}.properties"/>
+        <property file="nbproject/project.properties"/>
+    </target>
+    <target depends="-pre-init,-init-private,-init-libraries,-init-user,-init-project,-init-macrodef-property" name="-do-init">
+        <property name="platform.java" value="${java.home}/bin/java"/>
+        <available file="${manifest.file}" property="manifest.available"/>
+        <condition property="splashscreen.available">
+            <and>
+                <not>
+                    <equals arg1="${application.splash}" arg2="" trim="true"/>
+                </not>
+                <available file="${application.splash}"/>
+            </and>
+        </condition>
+        <condition property="main.class.available">
+            <and>
+                <isset property="main.class"/>
+                <not>
+                    <equals arg1="${main.class}" arg2="" trim="true"/>
+                </not>
+            </and>
+        </condition>
+        <condition property="profile.available">
+            <and>
+                <isset property="javac.profile"/>
+                <length length="0" string="${javac.profile}" when="greater"/>
+                <matches pattern="1\.[89](\..*)?" string="${javac.source}"/>
+            </and>
+        </condition>
+        <condition property="do.archive">
+            <or>
+                <not>
+                    <istrue value="${jar.archive.disabled}"/>
+                </not>
+                <istrue value="${not.archive.disabled}"/>
+            </or>
+        </condition>
+        <condition property="do.mkdist">
+            <and>
+                <isset property="do.archive"/>
+                <isset property="libs.CopyLibs.classpath"/>
+                <not>
+                    <istrue value="${mkdist.disabled}"/>
+                </not>
+            </and>
+        </condition>
+        <condition property="do.archive+manifest.available">
+            <and>
+                <isset property="manifest.available"/>
+                <istrue value="${do.archive}"/>
+            </and>
+        </condition>
+        <condition property="do.archive+main.class.available">
+            <and>
+                <isset property="main.class.available"/>
+                <istrue value="${do.archive}"/>
+            </and>
+        </condition>
+        <condition property="do.archive+splashscreen.available">
+            <and>
+                <isset property="splashscreen.available"/>
+                <istrue value="${do.archive}"/>
+            </and>
+        </condition>
+        <condition property="do.archive+profile.available">
+            <and>
+                <isset property="profile.available"/>
+                <istrue value="${do.archive}"/>
+            </and>
+        </condition>
+        <condition property="have.tests">
+            <or>
+                <available file="${test.src.dir}"/>
+            </or>
+        </condition>
+        <condition property="have.sources">
+            <or>
+                <available file="${src.dir}"/>
+            </or>
+        </condition>
+        <condition property="netbeans.home+have.tests">
+            <and>
+                <isset property="netbeans.home"/>
+                <isset property="have.tests"/>
+            </and>
+        </condition>
+        <condition property="no.javadoc.preview">
+            <and>
+                <isset property="javadoc.preview"/>
+                <isfalse value="${javadoc.preview}"/>
+            </and>
+        </condition>
+        <property name="run.jvmargs" value=""/>
+        <property name="run.jvmargs.ide" value=""/>
+        <property name="javac.compilerargs" value=""/>
+        <property name="work.dir" value="${basedir}"/>
+        <condition property="no.deps">
+            <and>
+                <istrue value="${no.dependencies}"/>
+            </and>
+        </condition>
+        <property name="javac.debug" value="true"/>
+        <property name="javadoc.preview" value="true"/>
+        <property name="application.args" value=""/>
+        <property name="source.encoding" value="${file.encoding}"/>
+        <property name="runtime.encoding" value="${source.encoding}"/>
+        <condition property="javadoc.encoding.used" value="${javadoc.encoding}">
+            <and>
+                <isset property="javadoc.encoding"/>
+                <not>
+                    <equals arg1="${javadoc.encoding}" arg2=""/>
+                </not>
+            </and>
+        </condition>
+        <property name="javadoc.encoding.used" value="${source.encoding}"/>
+        <property name="includes" value="**"/>
+        <property name="excludes" value=""/>
+        <property name="do.depend" value="false"/>
+        <condition property="do.depend.true">
+            <istrue value="${do.depend}"/>
+        </condition>
+        <path id="endorsed.classpath.path" path="${endorsed.classpath}"/>
+        <condition else="" property="endorsed.classpath.cmd.line.arg" value="-Xbootclasspath/p:'${toString:endorsed.classpath.path}'">
+            <and>
+                <isset property="endorsed.classpath"/>
+                <not>
+                    <equals arg1="${endorsed.classpath}" arg2="" trim="true"/>
+                </not>
+            </and>
+        </condition>
+        <condition else="" property="javac.profile.cmd.line.arg" value="-profile ${javac.profile}">
+            <isset property="profile.available"/>
+        </condition>
+        <condition else="false" property="jdkBug6558476">
+            <and>
+                <matches pattern="1\.[56]" string="${java.specification.version}"/>
+                <not>
+                    <os family="unix"/>
+                </not>
+            </and>
+        </condition>
+        <property name="javac.fork" value="${jdkBug6558476}"/>
+        <property name="jar.index" value="false"/>
+        <property name="jar.index.metainf" value="${jar.index}"/>
+        <property name="copylibs.rebase" value="true"/>
+        <available file="${meta.inf.dir}/persistence.xml" property="has.persistence.xml"/>
+        <condition property="junit.available">
+            <or>
+                <available classname="org.junit.Test" classpath="${run.test.classpath}"/>
+                <available classname="junit.framework.Test" classpath="${run.test.classpath}"/>
+            </or>
+        </condition>
+        <condition property="testng.available">
+            <available classname="org.testng.annotations.Test" classpath="${run.test.classpath}"/>
+        </condition>
+        <condition property="junit+testng.available">
+            <and>
+                <istrue value="${junit.available}"/>
+                <istrue value="${testng.available}"/>
+            </and>
+        </condition>
+        <condition else="testng" property="testng.mode" value="mixed">
+            <istrue value="${junit+testng.available}"/>
+        </condition>
+        <condition else="" property="testng.debug.mode" value="-mixed">
+            <istrue value="${junit+testng.available}"/>
+        </condition>
+    </target>
+    <target name="-post-init">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target depends="-pre-init,-init-private,-init-libraries,-init-user,-init-project,-do-init" name="-init-check">
+        <fail unless="src.dir">Must set src.dir</fail>
+        <fail unless="test.src.dir">Must set test.src.dir</fail>
+        <fail unless="build.dir">Must set build.dir</fail>
+        <fail unless="dist.dir">Must set dist.dir</fail>
+        <fail unless="build.classes.dir">Must set build.classes.dir</fail>
+        <fail unless="dist.javadoc.dir">Must set dist.javadoc.dir</fail>
+        <fail unless="build.test.classes.dir">Must set build.test.classes.dir</fail>
+        <fail unless="build.test.results.dir">Must set build.test.results.dir</fail>
+        <fail unless="build.classes.excludes">Must set build.classes.excludes</fail>
+        <fail unless="dist.jar">Must set dist.jar</fail>
+    </target>
+    <target name="-init-macrodef-property">
+        <macrodef name="property" uri="http://www.netbeans.org/ns/j2se-project/1">
+            <attribute name="name"/>
+            <attribute name="value"/>
+            <sequential>
+                <property name="@{name}" value="${@{value}}"/>
+            </sequential>
+        </macrodef>
+    </target>
+    <target depends="-init-ap-cmdline-properties" if="ap.supported.internal" name="-init-macrodef-javac-with-processors">
+        <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${src.dir}" name="srcdir"/>
+            <attribute default="${build.classes.dir}" name="destdir"/>
+            <attribute default="${javac.classpath}" name="classpath"/>
+            <attribute default="${javac.processorpath}" name="processorpath"/>
+            <attribute default="${build.generated.sources.dir}/ap-source-output" name="apgeneratedsrcdir"/>
+            <attribute default="${includes}" name="includes"/>
+            <attribute default="${excludes}" name="excludes"/>
+            <attribute default="${javac.debug}" name="debug"/>
+            <attribute default="${empty.dir}" name="sourcepath"/>
+            <attribute default="${empty.dir}" name="gensrcdir"/>
+            <element name="customize" optional="true"/>
+            <sequential>
+                <property location="${build.dir}/empty" name="empty.dir"/>
+                <mkdir dir="${empty.dir}"/>
+                <mkdir dir="@{apgeneratedsrcdir}"/>
+                <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" fork="${javac.fork}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}" tempdir="${java.io.tmpdir}">
+                    <src>
+                        <dirset dir="@{gensrcdir}" erroronmissingdir="false">
+                            <include name="*"/>
+                        </dirset>
+                    </src>
+                    <classpath>
+                        <path path="@{classpath}"/>
+                    </classpath>
+                    <compilerarg line="${endorsed.classpath.cmd.line.arg}"/>
+                    <compilerarg line="${javac.profile.cmd.line.arg}"/>
+                    <compilerarg line="${javac.compilerargs}"/>
+                    <compilerarg value="-processorpath"/>
+                    <compilerarg path="@{processorpath}:${empty.dir}"/>
+                    <compilerarg line="${ap.processors.internal}"/>
+                    <compilerarg line="${annotation.processing.processor.options}"/>
+                    <compilerarg value="-s"/>
+                    <compilerarg path="@{apgeneratedsrcdir}"/>
+                    <compilerarg line="${ap.proc.none.internal}"/>
+                    <customize/>
+                </javac>
+            </sequential>
+        </macrodef>
+    </target>
+    <target depends="-init-ap-cmdline-properties" name="-init-macrodef-javac-without-processors" unless="ap.supported.internal">
+        <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${src.dir}" name="srcdir"/>
+            <attribute default="${build.classes.dir}" name="destdir"/>
+            <attribute default="${javac.classpath}" name="classpath"/>
+            <attribute default="${javac.processorpath}" name="processorpath"/>
+            <attribute default="${build.generated.sources.dir}/ap-source-output" name="apgeneratedsrcdir"/>
+            <attribute default="${includes}" name="includes"/>
+            <attribute default="${excludes}" name="excludes"/>
+            <attribute default="${javac.debug}" name="debug"/>
+            <attribute default="${empty.dir}" name="sourcepath"/>
+            <attribute default="${empty.dir}" name="gensrcdir"/>
+            <element name="customize" optional="true"/>
+            <sequential>
+                <property location="${build.dir}/empty" name="empty.dir"/>
+                <mkdir dir="${empty.dir}"/>
+                <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" fork="${javac.fork}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}" tempdir="${java.io.tmpdir}">
+                    <src>
+                        <dirset dir="@{gensrcdir}" erroronmissingdir="false">
+                            <include name="*"/>
+                        </dirset>
+                    </src>
+                    <classpath>
+                        <path path="@{classpath}"/>
+                    </classpath>
+                    <compilerarg line="${endorsed.classpath.cmd.line.arg}"/>
+                    <compilerarg line="${javac.profile.cmd.line.arg}"/>
+                    <compilerarg line="${javac.compilerargs}"/>
+                    <customize/>
+                </javac>
+            </sequential>
+        </macrodef>
+    </target>
+    <target depends="-init-macrodef-javac-with-processors,-init-macrodef-javac-without-processors" name="-init-macrodef-javac">
+        <macrodef name="depend" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${src.dir}" name="srcdir"/>
+            <attribute default="${build.classes.dir}" name="destdir"/>
+            <attribute default="${javac.classpath}" name="classpath"/>
+            <sequential>
+                <depend cache="${build.dir}/depcache" destdir="@{destdir}" excludes="${excludes}" includes="${includes}" srcdir="@{srcdir}">
+                    <classpath>
+                        <path path="@{classpath}"/>
+                    </classpath>
+                </depend>
+            </sequential>
+        </macrodef>
+        <macrodef name="force-recompile" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${build.classes.dir}" name="destdir"/>
+            <sequential>
+                <fail unless="javac.includes">Must set javac.includes</fail>
+                <pathconvert pathsep="${line.separator}" property="javac.includes.binary">
+                    <path>
+                        <filelist dir="@{destdir}" files="${javac.includes}"/>
+                    </path>
+                    <globmapper from="*.java" to="*.class"/>
+                </pathconvert>
+                <tempfile deleteonexit="true" property="javac.includesfile.binary"/>
+                <echo file="${javac.includesfile.binary}" message="${javac.includes.binary}"/>
+                <delete>
+                    <files includesfile="${javac.includesfile.binary}"/>
+                </delete>
+                <delete>
+                    <fileset file="${javac.includesfile.binary}"/>
+                </delete>
+            </sequential>
+        </macrodef>
+    </target>
+    <target if="${junit.available}" name="-init-macrodef-junit-init">
+        <condition else="false" property="nb.junit.batch" value="true">
+            <and>
+                <istrue value="${junit.available}"/>
+                <not>
+                    <isset property="test.method"/>
+                </not>
+            </and>
+        </condition>
+        <condition else="false" property="nb.junit.single" value="true">
+            <and>
+                <istrue value="${junit.available}"/>
+                <isset property="test.method"/>
+            </and>
+        </condition>
+    </target>
+    <target name="-init-test-properties">
+        <property name="test.binaryincludes" value="&lt;nothing&gt;"/>
+        <property name="test.binarytestincludes" value=""/>
+        <property name="test.binaryexcludes" value=""/>
+    </target>
+    <target if="${nb.junit.single}" name="-init-macrodef-junit-single" unless="${nb.junit.batch}">
+        <macrodef name="junit" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${includes}" name="includes"/>
+            <attribute default="${excludes}" name="excludes"/>
+            <attribute default="**" name="testincludes"/>
+            <attribute default="" name="testmethods"/>
+            <element name="customize" optional="true"/>
+            <sequential>
+                <property name="junit.forkmode" value="perTest"/>
+                <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
+                    <test methods="@{testmethods}" name="@{testincludes}" todir="${build.test.results.dir}"/>
+                    <syspropertyset>
+                        <propertyref prefix="test-sys-prop."/>
+                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
+                    </syspropertyset>
+                    <formatter type="brief" usefile="false"/>
+                    <formatter type="xml"/>
+                    <jvmarg value="-ea"/>
+                    <customize/>
+                </junit>
+            </sequential>
+        </macrodef>
+    </target>
+    <target depends="-init-test-properties" if="${nb.junit.batch}" name="-init-macrodef-junit-batch" unless="${nb.junit.single}">
+        <macrodef name="junit" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${includes}" name="includes"/>
+            <attribute default="${excludes}" name="excludes"/>
+            <attribute default="**" name="testincludes"/>
+            <attribute default="" name="testmethods"/>
+            <element name="customize" optional="true"/>
+            <sequential>
+                <property name="junit.forkmode" value="perTest"/>
+                <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
+                    <batchtest todir="${build.test.results.dir}">
+                        <fileset dir="${test.src.dir}" excludes="@{excludes},${excludes}" includes="@{includes}">
+                            <filename name="@{testincludes}"/>
+                        </fileset>
+                        <fileset dir="${build.test.classes.dir}" excludes="@{excludes},${excludes},${test.binaryexcludes}" includes="${test.binaryincludes}">
+                            <filename name="${test.binarytestincludes}"/>
+                        </fileset>
+                    </batchtest>
+                    <syspropertyset>
+                        <propertyref prefix="test-sys-prop."/>
+                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
+                    </syspropertyset>
+                    <formatter type="brief" usefile="false"/>
+                    <formatter type="xml"/>
+                    <jvmarg value="-ea"/>
+                    <customize/>
+                </junit>
+            </sequential>
+        </macrodef>
+    </target>
+    <target depends="-init-macrodef-junit-init,-init-macrodef-junit-single, -init-macrodef-junit-batch" if="${junit.available}" name="-init-macrodef-junit"/>
+    <target if="${testng.available}" name="-init-macrodef-testng">
+        <macrodef name="testng" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${includes}" name="includes"/>
+            <attribute default="${excludes}" name="excludes"/>
+            <attribute default="**" name="testincludes"/>
+            <attribute default="" name="testmethods"/>
+            <element name="customize" optional="true"/>
+            <sequential>
+                <condition else="" property="testng.methods.arg" value="@{testincludes}.@{testmethods}">
+                    <isset property="test.method"/>
+                </condition>
+                <union id="test.set">
+                    <fileset dir="${test.src.dir}" excludes="@{excludes},**/*.xml,${excludes}" includes="@{includes}">
+                        <filename name="@{testincludes}"/>
+                    </fileset>
+                </union>
+                <taskdef classname="org.testng.TestNGAntTask" classpath="${run.test.classpath}" name="testng"/>
+                <testng classfilesetref="test.set" failureProperty="tests.failed" listeners="org.testng.reporters.VerboseReporter" methods="${testng.methods.arg}" mode="${testng.mode}" outputdir="${build.test.results.dir}" suitename="WSYD" testname="TestNG tests" workingDir="${work.dir}">
+                    <xmlfileset dir="${build.test.classes.dir}" includes="@{testincludes}"/>
+                    <propertyset>
+                        <propertyref prefix="test-sys-prop."/>
+                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
+                    </propertyset>
+                    <customize/>
+                </testng>
+            </sequential>
+        </macrodef>
+    </target>
+    <target name="-init-macrodef-test-impl">
+        <macrodef name="test-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${includes}" name="includes"/>
+            <attribute default="${excludes}" name="excludes"/>
+            <attribute default="**" name="testincludes"/>
+            <attribute default="" name="testmethods"/>
+            <element implicit="true" name="customize" optional="true"/>
+            <sequential>
+                <echo>No tests executed.</echo>
+            </sequential>
+        </macrodef>
+    </target>
+    <target depends="-init-macrodef-junit" if="${junit.available}" name="-init-macrodef-junit-impl">
+        <macrodef name="test-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${includes}" name="includes"/>
+            <attribute default="${excludes}" name="excludes"/>
+            <attribute default="**" name="testincludes"/>
+            <attribute default="" name="testmethods"/>
+            <element implicit="true" name="customize" optional="true"/>
+            <sequential>
+                <j2seproject3:junit excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
+                    <customize/>
+                </j2seproject3:junit>
+            </sequential>
+        </macrodef>
+    </target>
+    <target depends="-init-macrodef-testng" if="${testng.available}" name="-init-macrodef-testng-impl">
+        <macrodef name="test-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${includes}" name="includes"/>
+            <attribute default="${excludes}" name="excludes"/>
+            <attribute default="**" name="testincludes"/>
+            <attribute default="" name="testmethods"/>
+            <element implicit="true" name="customize" optional="true"/>
+            <sequential>
+                <j2seproject3:testng excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
+                    <customize/>
+                </j2seproject3:testng>
+            </sequential>
+        </macrodef>
+    </target>
+    <target depends="-init-macrodef-test-impl,-init-macrodef-junit-impl,-init-macrodef-testng-impl" name="-init-macrodef-test">
+        <macrodef name="test" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${includes}" name="includes"/>
+            <attribute default="${excludes}" name="excludes"/>
+            <attribute default="**" name="testincludes"/>
+            <attribute default="" name="testmethods"/>
+            <sequential>
+                <j2seproject3:test-impl excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
+                    <customize>
+                        <classpath>
+                            <path path="${run.test.classpath}"/>
+                        </classpath>
+                        <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
+                        <jvmarg line="${run.jvmargs}"/>
+                        <jvmarg line="${run.jvmargs.ide}"/>
+                    </customize>
+                </j2seproject3:test-impl>
+            </sequential>
+        </macrodef>
+    </target>
+    <target if="${junit.available}" name="-init-macrodef-junit-debug" unless="${nb.junit.batch}">
+        <macrodef name="junit-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${includes}" name="includes"/>
+            <attribute default="${excludes}" name="excludes"/>
+            <attribute default="**" name="testincludes"/>
+            <attribute default="" name="testmethods"/>
+            <element name="customize" optional="true"/>
+            <sequential>
+                <property name="junit.forkmode" value="perTest"/>
+                <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
+                    <test methods="@{testmethods}" name="@{testincludes}" todir="${build.test.results.dir}"/>
+                    <syspropertyset>
+                        <propertyref prefix="test-sys-prop."/>
+                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
+                    </syspropertyset>
+                    <formatter type="brief" usefile="false"/>
+                    <formatter type="xml"/>
+                    <jvmarg value="-ea"/>
+                    <jvmarg line="${debug-args-line}"/>
+                    <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
+                    <customize/>
+                </junit>
+            </sequential>
+        </macrodef>
+    </target>
+    <target depends="-init-test-properties" if="${nb.junit.batch}" name="-init-macrodef-junit-debug-batch">
+        <macrodef name="junit-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${includes}" name="includes"/>
+            <attribute default="${excludes}" name="excludes"/>
+            <attribute default="**" name="testincludes"/>
+            <attribute default="" name="testmethods"/>
+            <element name="customize" optional="true"/>
+            <sequential>
+                <property name="junit.forkmode" value="perTest"/>
+                <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
+                    <batchtest todir="${build.test.results.dir}">
+                        <fileset dir="${test.src.dir}" excludes="@{excludes},${excludes}" includes="@{includes}">
+                            <filename name="@{testincludes}"/>
+                        </fileset>
+                        <fileset dir="${build.test.classes.dir}" excludes="@{excludes},${excludes},${test.binaryexcludes}" includes="${test.binaryincludes}">
+                            <filename name="${test.binarytestincludes}"/>
+                        </fileset>
+                    </batchtest>
+                    <syspropertyset>
+                        <propertyref prefix="test-sys-prop."/>
+                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
+                    </syspropertyset>
+                    <formatter type="brief" usefile="false"/>
+                    <formatter type="xml"/>
+                    <jvmarg value="-ea"/>
+                    <jvmarg line="${debug-args-line}"/>
+                    <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
+                    <customize/>
+                </junit>
+            </sequential>
+        </macrodef>
+    </target>
+    <target depends="-init-macrodef-junit-debug,-init-macrodef-junit-debug-batch" if="${junit.available}" name="-init-macrodef-junit-debug-impl">
+        <macrodef name="test-debug-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${includes}" name="includes"/>
+            <attribute default="${excludes}" name="excludes"/>
+            <attribute default="**" name="testincludes"/>
+            <attribute default="" name="testmethods"/>
+            <element implicit="true" name="customize" optional="true"/>
+            <sequential>
+                <j2seproject3:junit-debug excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
+                    <customize/>
+                </j2seproject3:junit-debug>
+            </sequential>
+        </macrodef>
+    </target>
+    <target if="${testng.available}" name="-init-macrodef-testng-debug">
+        <macrodef name="testng-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${main.class}" name="testClass"/>
+            <attribute default="" name="testMethod"/>
+            <element name="customize2" optional="true"/>
+            <sequential>
+                <condition else="-testclass @{testClass}" property="test.class.or.method" value="-methods @{testClass}.@{testMethod}">
+                    <isset property="test.method"/>
+                </condition>
+                <condition else="-suitename WSYD -testname @{testClass} ${test.class.or.method}" property="testng.cmd.args" value="@{testClass}">
+                    <matches pattern=".*\.xml" string="@{testClass}"/>
+                </condition>
+                <delete dir="${build.test.results.dir}" quiet="true"/>
+                <mkdir dir="${build.test.results.dir}"/>
+                <j2seproject3:debug classname="org.testng.TestNG" classpath="${debug.test.classpath}">
+                    <customize>
+                        <customize2/>
+                        <jvmarg value="-ea"/>
+                        <arg line="${testng.debug.mode}"/>
+                        <arg line="-d ${build.test.results.dir}"/>
+                        <arg line="-listener org.testng.reporters.VerboseReporter"/>
+                        <arg line="${testng.cmd.args}"/>
+                    </customize>
+                </j2seproject3:debug>
+            </sequential>
+        </macrodef>
+    </target>
+    <target depends="-init-macrodef-testng-debug" if="${testng.available}" name="-init-macrodef-testng-debug-impl">
+        <macrodef name="testng-debug-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${main.class}" name="testClass"/>
+            <attribute default="" name="testMethod"/>
+            <element implicit="true" name="customize2" optional="true"/>
+            <sequential>
+                <j2seproject3:testng-debug testClass="@{testClass}" testMethod="@{testMethod}">
+                    <customize2/>
+                </j2seproject3:testng-debug>
+            </sequential>
+        </macrodef>
+    </target>
+    <target depends="-init-macrodef-junit-debug-impl" if="${junit.available}" name="-init-macrodef-test-debug-junit">
+        <macrodef name="test-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${includes}" name="includes"/>
+            <attribute default="${excludes}" name="excludes"/>
+            <attribute default="**" name="testincludes"/>
+            <attribute default="" name="testmethods"/>
+            <attribute default="${main.class}" name="testClass"/>
+            <attribute default="" name="testMethod"/>
+            <sequential>
+                <j2seproject3:test-debug-impl excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
+                    <customize>
+                        <classpath>
+                            <path path="${run.test.classpath}"/>
+                        </classpath>
+                        <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
+                        <jvmarg line="${run.jvmargs}"/>
+                        <jvmarg line="${run.jvmargs.ide}"/>
+                    </customize>
+                </j2seproject3:test-debug-impl>
+            </sequential>
+        </macrodef>
+    </target>
+    <target depends="-init-macrodef-testng-debug-impl" if="${testng.available}" name="-init-macrodef-test-debug-testng">
+        <macrodef name="test-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${includes}" name="includes"/>
+            <attribute default="${excludes}" name="excludes"/>
+            <attribute default="**" name="testincludes"/>
+            <attribute default="" name="testmethods"/>
+            <attribute default="${main.class}" name="testClass"/>
+            <attribute default="" name="testMethod"/>
+            <sequential>
+                <j2seproject3:testng-debug-impl testClass="@{testClass}" testMethod="@{testMethod}">
+                    <customize2>
+                        <syspropertyset>
+                            <propertyref prefix="test-sys-prop."/>
+                            <mapper from="test-sys-prop.*" to="*" type="glob"/>
+                        </syspropertyset>
+                    </customize2>
+                </j2seproject3:testng-debug-impl>
+            </sequential>
+        </macrodef>
+    </target>
+    <target depends="-init-macrodef-test-debug-junit,-init-macrodef-test-debug-testng" name="-init-macrodef-test-debug"/>
+    <!--
+                pre NB7.2 profiling section; consider it deprecated
+            -->
+    <target depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile, -profile-init-check" if="profiler.info.jvmargs.agent" name="profile-init"/>
+    <target if="profiler.info.jvmargs.agent" name="-profile-pre-init">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target if="profiler.info.jvmargs.agent" name="-profile-post-init">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target if="profiler.info.jvmargs.agent" name="-profile-init-macrodef-profile">
+        <macrodef name="resolve">
+            <attribute name="name"/>
+            <attribute name="value"/>
+            <sequential>
+                <property name="@{name}" value="${env.@{value}}"/>
+            </sequential>
+        </macrodef>
+        <macrodef name="profile">
+            <attribute default="${main.class}" name="classname"/>
+            <element name="customize" optional="true"/>
+            <sequential>
+                <property environment="env"/>
+                <resolve name="profiler.current.path" value="${profiler.info.pathvar}"/>
+                <java classname="@{classname}" dir="${profiler.info.dir}" fork="true" jvm="${profiler.info.jvm}">
+                    <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
+                    <jvmarg value="${profiler.info.jvmargs.agent}"/>
+                    <jvmarg line="${profiler.info.jvmargs}"/>
+                    <env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
+                    <arg line="${application.args}"/>
+                    <classpath>
+                        <path path="${run.classpath}"/>
+                    </classpath>
+                    <syspropertyset>
+                        <propertyref prefix="run-sys-prop."/>
+                        <mapper from="run-sys-prop.*" to="*" type="glob"/>
+                    </syspropertyset>
+                    <customize/>
+                </java>
+            </sequential>
+        </macrodef>
+    </target>
+    <target depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile" if="profiler.info.jvmargs.agent" name="-profile-init-check">
+        <fail unless="profiler.info.jvm">Must set JVM to use for profiling in profiler.info.jvm</fail>
+        <fail unless="profiler.info.jvmargs.agent">Must set profiler agent JVM arguments in profiler.info.jvmargs.agent</fail>
+    </target>
+    <!--
+                end of pre NB7.2 profiling section
+            -->
+    <target depends="-init-debug-args" name="-init-macrodef-nbjpda">
+        <macrodef name="nbjpdastart" uri="http://www.netbeans.org/ns/j2se-project/1">
+            <attribute default="${main.class}" name="name"/>
+            <attribute default="${debug.classpath}" name="classpath"/>
+            <attribute default="" name="stopclassname"/>
+            <sequential>
+                <nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
+                    <classpath>
+                        <path path="@{classpath}"/>
+                    </classpath>
+                </nbjpdastart>
+            </sequential>
+        </macrodef>
+        <macrodef name="nbjpdareload" uri="http://www.netbeans.org/ns/j2se-project/1">
+            <attribute default="${build.classes.dir}" name="dir"/>
+            <sequential>
+                <nbjpdareload>
+                    <fileset dir="@{dir}" includes="${fix.classes}">
+                        <include name="${fix.includes}*.class"/>
+                    </fileset>
+                </nbjpdareload>
+            </sequential>
+        </macrodef>
+    </target>
+    <target name="-init-debug-args">
+        <property name="version-output" value="java version &quot;${ant.java.version}"/>
+        <condition property="have-jdk-older-than-1.4">
+            <or>
+                <contains string="${version-output}" substring="java version &quot;1.0"/>
+                <contains string="${version-output}" substring="java version &quot;1.1"/>
+                <contains string="${version-output}" substring="java version &quot;1.2"/>
+                <contains string="${version-output}" substring="java version &quot;1.3"/>
+            </or>
+        </condition>
+        <condition else="-Xdebug" property="debug-args-line" value="-Xdebug -Xnoagent -Djava.compiler=none">
+            <istrue value="${have-jdk-older-than-1.4}"/>
+        </condition>
+        <condition else="dt_socket" property="debug-transport-by-os" value="dt_shmem">
+            <os family="windows"/>
+        </condition>
+        <condition else="${debug-transport-by-os}" property="debug-transport" value="${debug.transport}">
+            <isset property="debug.transport"/>
+        </condition>
+    </target>
+    <target depends="-init-debug-args" name="-init-macrodef-debug">
+        <macrodef name="debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${main.class}" name="classname"/>
+            <attribute default="${debug.classpath}" name="classpath"/>
+            <element name="customize" optional="true"/>
+            <sequential>
+                <java classname="@{classname}" dir="${work.dir}" fork="true">
+                    <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
+                    <jvmarg line="${debug-args-line}"/>
+                    <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
+                    <jvmarg value="-Dfile.encoding=${runtime.encoding}"/>
+                    <redirector errorencoding="${runtime.encoding}" inputencoding="${runtime.encoding}" outputencoding="${runtime.encoding}"/>
+                    <jvmarg line="${run.jvmargs}"/>
+                    <jvmarg line="${run.jvmargs.ide}"/>
+                    <classpath>
+                        <path path="@{classpath}"/>
+                    </classpath>
+                    <syspropertyset>
+                        <propertyref prefix="run-sys-prop."/>
+                        <mapper from="run-sys-prop.*" to="*" type="glob"/>
+                    </syspropertyset>
+                    <customize/>
+                </java>
+            </sequential>
+        </macrodef>
+    </target>
+    <target name="-init-macrodef-java">
+        <macrodef name="java" uri="http://www.netbeans.org/ns/j2se-project/1">
+            <attribute default="${main.class}" name="classname"/>
+            <attribute default="${run.classpath}" name="classpath"/>
+            <attribute default="jvm" name="jvm"/>
+            <element name="customize" optional="true"/>
+            <sequential>
+                <java classname="@{classname}" dir="${work.dir}" fork="true">
+                    <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
+                    <jvmarg value="-Dfile.encoding=${runtime.encoding}"/>
+                    <redirector errorencoding="${runtime.encoding}" inputencoding="${runtime.encoding}" outputencoding="${runtime.encoding}"/>
+                    <jvmarg line="${run.jvmargs}"/>
+                    <jvmarg line="${run.jvmargs.ide}"/>
+                    <classpath>
+                        <path path="@{classpath}"/>
+                    </classpath>
+                    <syspropertyset>
+                        <propertyref prefix="run-sys-prop."/>
+                        <mapper from="run-sys-prop.*" to="*" type="glob"/>
+                    </syspropertyset>
+                    <customize/>
+                </java>
+            </sequential>
+        </macrodef>
+    </target>
+    <target name="-init-macrodef-copylibs">
+        <macrodef name="copylibs" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute default="${manifest.file}" name="manifest"/>
+            <element name="customize" optional="true"/>
+            <sequential>
+                <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
+                <pathconvert property="run.classpath.without.build.classes.dir">
+                    <path path="${run.classpath}"/>
+                    <map from="${build.classes.dir.resolved}" to=""/>
+                </pathconvert>
+                <pathconvert pathsep=" " property="jar.classpath">
+                    <path path="${run.classpath.without.build.classes.dir}"/>
+                    <chainedmapper>
+                        <flattenmapper/>
+                        <filtermapper>
+                            <replacestring from=" " to="%20"/>
+                        </filtermapper>
+                        <globmapper from="*" to="lib/*"/>
+                    </chainedmapper>
+                </pathconvert>
+                <taskdef classname="org.netbeans.modules.java.j2seproject.copylibstask.CopyLibs" classpath="${libs.CopyLibs.classpath}" name="copylibs"/>
+                <copylibs compress="${jar.compress}" excludeFromCopy="${copylibs.excludes}" index="${jar.index}" indexMetaInf="${jar.index.metainf}" jarfile="${dist.jar}" manifest="@{manifest}" rebase="${copylibs.rebase}" runtimeclasspath="${run.classpath.without.build.classes.dir}">
+                    <fileset dir="${build.classes.dir}" excludes="${dist.archive.excludes}"/>
+                    <manifest>
+                        <attribute name="Class-Path" value="${jar.classpath}"/>
+                        <customize/>
+                    </manifest>
+                </copylibs>
+            </sequential>
+        </macrodef>
+    </target>
+    <target name="-init-presetdef-jar">
+        <presetdef name="jar" uri="http://www.netbeans.org/ns/j2se-project/1">
+            <jar compress="${jar.compress}" index="${jar.index}" jarfile="${dist.jar}">
+                <j2seproject1:fileset dir="${build.classes.dir}" excludes="${dist.archive.excludes}"/>
+            </jar>
+        </presetdef>
+    </target>
+    <target name="-init-ap-cmdline-properties">
+        <property name="annotation.processing.enabled" value="true"/>
+        <property name="annotation.processing.processors.list" value=""/>
+        <property name="annotation.processing.processor.options" value=""/>
+        <property name="annotation.processing.run.all.processors" value="true"/>
+        <property name="javac.processorpath" value="${javac.classpath}"/>
+        <property name="javac.test.processorpath" value="${javac.test.classpath}"/>
+        <condition property="ap.supported.internal" value="true">
+            <not>
+                <matches pattern="1\.[0-5](\..*)?" string="${javac.source}"/>
+            </not>
+        </condition>
+    </target>
+    <target depends="-init-ap-cmdline-properties" if="ap.supported.internal" name="-init-ap-cmdline-supported">
+        <condition else="" property="ap.processors.internal" value="-processor ${annotation.processing.processors.list}">
+            <isfalse value="${annotation.processing.run.all.processors}"/>
+        </condition>
+        <condition else="" property="ap.proc.none.internal" value="-proc:none">
+            <isfalse value="${annotation.processing.enabled}"/>
+        </condition>
+    </target>
+    <target depends="-init-ap-cmdline-properties,-init-ap-cmdline-supported" name="-init-ap-cmdline">
+        <property name="ap.cmd.line.internal" value=""/>
+    </target>
+    <target depends="-pre-init,-init-private,-init-libraries,-init-user,-init-project,-do-init,-post-init,-init-check,-init-macrodef-property,-init-macrodef-javac,-init-macrodef-test,-init-macrodef-test-debug,-init-macrodef-nbjpda,-init-macrodef-debug,-init-macrodef-java,-init-presetdef-jar,-init-ap-cmdline" name="init"/>
+    <!--
+                ===================
+                COMPILATION SECTION
+                ===================
+            -->
+    <target name="-deps-jar-init" unless="built-jar.properties">
+        <property location="${build.dir}/built-jar.properties" name="built-jar.properties"/>
+        <delete file="${built-jar.properties}" quiet="true"/>
+    </target>
+    <target if="already.built.jar.${basedir}" name="-warn-already-built-jar">
+        <echo level="warn" message="Cycle detected: WSYD was already built"/>
+    </target>
+    <target depends="init,-deps-jar-init" name="deps-jar" unless="no.deps">
+        <mkdir dir="${build.dir}"/>
+        <touch file="${built-jar.properties}" verbose="false"/>
+        <property file="${built-jar.properties}" prefix="already.built.jar."/>
+        <antcall target="-warn-already-built-jar"/>
+        <propertyfile file="${built-jar.properties}">
+            <entry key="${basedir}" value=""/>
+        </propertyfile>
+    </target>
+    <target depends="init,-check-automatic-build,-clean-after-automatic-build" name="-verify-automatic-build"/>
+    <target depends="init" name="-check-automatic-build">
+        <available file="${build.classes.dir}/.netbeans_automatic_build" property="netbeans.automatic.build"/>
+    </target>
+    <target depends="init" if="netbeans.automatic.build" name="-clean-after-automatic-build">
+        <antcall target="clean"/>
+    </target>
+    <target depends="init,deps-jar" name="-pre-pre-compile">
+        <mkdir dir="${build.classes.dir}"/>
+    </target>
+    <target name="-pre-compile">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target if="do.depend.true" name="-compile-depend">
+        <pathconvert property="build.generated.subdirs">
+            <dirset dir="${build.generated.sources.dir}" erroronmissingdir="false">
+                <include name="*"/>
+            </dirset>
+        </pathconvert>
+        <j2seproject3:depend srcdir="${src.dir}:${build.generated.subdirs}"/>
+    </target>
+    <target depends="init,deps-jar,-pre-pre-compile,-pre-compile, -copy-persistence-xml,-compile-depend" if="have.sources" name="-do-compile">
+        <j2seproject3:javac gensrcdir="${build.generated.sources.dir}"/>
+        <copy todir="${build.classes.dir}">
+            <fileset dir="${src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
+        </copy>
+    </target>
+    <target if="has.persistence.xml" name="-copy-persistence-xml">
+        <mkdir dir="${build.classes.dir}/META-INF"/>
+        <copy todir="${build.classes.dir}/META-INF">
+            <fileset dir="${meta.inf.dir}" includes="persistence.xml orm.xml"/>
+        </copy>
+    </target>
+    <target name="-post-compile">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile,-do-compile,-post-compile" description="Compile project." name="compile"/>
+    <target name="-pre-compile-single">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target depends="init,deps-jar,-pre-pre-compile" name="-do-compile-single">
+        <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
+        <j2seproject3:force-recompile/>
+        <j2seproject3:javac excludes="" gensrcdir="${build.generated.sources.dir}" includes="${javac.includes}" sourcepath="${src.dir}"/>
+    </target>
+    <target name="-post-compile-single">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile-single,-do-compile-single,-post-compile-single" name="compile-single"/>
+    <!--
+                ====================
+                JAR BUILDING SECTION
+                ====================
+            -->
+    <target depends="init" name="-pre-pre-jar">
+        <dirname file="${dist.jar}" property="dist.jar.dir"/>
+        <mkdir dir="${dist.jar.dir}"/>
+    </target>
+    <target name="-pre-jar">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target depends="init" if="do.archive" name="-do-jar-create-manifest" unless="manifest.available">
+        <tempfile deleteonexit="true" destdir="${build.dir}" property="tmp.manifest.file"/>
+        <touch file="${tmp.manifest.file}" verbose="false"/>
+    </target>
+    <target depends="init" if="do.archive+manifest.available" name="-do-jar-copy-manifest">
+        <tempfile deleteonexit="true" destdir="${build.dir}" property="tmp.manifest.file"/>
+        <copy file="${manifest.file}" tofile="${tmp.manifest.file}"/>
+    </target>
+    <target depends="init,-do-jar-create-manifest,-do-jar-copy-manifest" if="do.archive+main.class.available" name="-do-jar-set-mainclass">
+        <manifest file="${tmp.manifest.file}" mode="update">
+            <attribute name="Main-Class" value="${main.class}"/>
+        </manifest>
+    </target>
+    <target depends="init,-do-jar-create-manifest,-do-jar-copy-manifest" if="do.archive+profile.available" name="-do-jar-set-profile">
+        <manifest file="${tmp.manifest.file}" mode="update">
+            <attribute name="Profile" value="${javac.profile}"/>
+        </manifest>
+    </target>
+    <target depends="init,-do-jar-create-manifest,-do-jar-copy-manifest" if="do.archive+splashscreen.available" name="-do-jar-set-splashscreen">
+        <basename file="${application.splash}" property="splashscreen.basename"/>
+        <mkdir dir="${build.classes.dir}/META-INF"/>
+        <copy failonerror="false" file="${application.splash}" todir="${build.classes.dir}/META-INF"/>
+        <manifest file="${tmp.manifest.file}" mode="update">
+            <attribute name="SplashScreen-Image" value="META-INF/${splashscreen.basename}"/>
+        </manifest>
+    </target>
+    <target depends="init,-init-macrodef-copylibs,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen" if="do.mkdist" name="-do-jar-copylibs">
+        <j2seproject3:copylibs manifest="${tmp.manifest.file}"/>
+        <echo level="info">To run this application from the command line without Ant, try:</echo>
+        <property location="${dist.jar}" name="dist.jar.resolved"/>
+        <echo level="info">java -jar "${dist.jar.resolved}"</echo>
+    </target>
+    <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen" if="do.archive" name="-do-jar-jar" unless="do.mkdist">
+        <j2seproject1:jar manifest="${tmp.manifest.file}"/>
+        <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
+        <property location="${dist.jar}" name="dist.jar.resolved"/>
+        <pathconvert property="run.classpath.with.dist.jar">
+            <path path="${run.classpath}"/>
+            <map from="${build.classes.dir.resolved}" to="${dist.jar.resolved}"/>
+        </pathconvert>
+        <condition else="" property="jar.usage.message" value="To run this application from the command line without Ant, try:${line.separator}${platform.java} -cp ${run.classpath.with.dist.jar} ${main.class}">
+            <isset property="main.class.available"/>
+        </condition>
+        <condition else="debug" property="jar.usage.level" value="info">
+            <isset property="main.class.available"/>
+        </condition>
+        <echo level="${jar.usage.level}" message="${jar.usage.message}"/>
+    </target>
+    <target depends="-do-jar-copylibs" if="do.archive" name="-do-jar-delete-manifest">
+        <delete>
+            <fileset file="${tmp.manifest.file}"/>
+        </delete>
+    </target>
+    <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen,-do-jar-jar,-do-jar-delete-manifest" name="-do-jar-without-libraries"/>
+    <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen,-do-jar-copylibs,-do-jar-delete-manifest" name="-do-jar-with-libraries"/>
+    <target name="-post-jar">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target depends="init,compile,-pre-jar,-do-jar-without-libraries,-do-jar-with-libraries,-post-jar" name="-do-jar"/>
+    <target depends="init,compile,-pre-jar,-do-jar,-post-jar" description="Build JAR." name="jar"/>
+    <!--
+                =================
+                EXECUTION SECTION
+                =================
+            -->
+    <target depends="init,compile" description="Run a main class." name="run">
+        <j2seproject1:java>
+            <customize>
+                <arg line="${application.args}"/>
+            </customize>
+        </j2seproject1:java>
+    </target>
+    <target name="-do-not-recompile">
+        <property name="javac.includes.binary" value=""/>
+    </target>
+    <target depends="init,compile-single" name="run-single">
+        <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
+        <j2seproject1:java classname="${run.class}"/>
+    </target>
+    <target depends="init,compile-test-single" name="run-test-with-main">
+        <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
+        <j2seproject1:java classname="${run.class}" classpath="${run.test.classpath}"/>
+    </target>
+    <!--
+                =================
+                DEBUGGING SECTION
+                =================
+            -->
+    <target depends="init" if="netbeans.home" name="-debug-start-debugger">
+        <j2seproject1:nbjpdastart name="${debug.class}"/>
+    </target>
+    <target depends="init" if="netbeans.home" name="-debug-start-debugger-main-test">
+        <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${debug.class}"/>
+    </target>
+    <target depends="init,compile" name="-debug-start-debuggee">
+        <j2seproject3:debug>
+            <customize>
+                <arg line="${application.args}"/>
+            </customize>
+        </j2seproject3:debug>
+    </target>
+    <target depends="init,compile,-debug-start-debugger,-debug-start-debuggee" description="Debug project in IDE." if="netbeans.home" name="debug"/>
+    <target depends="init" if="netbeans.home" name="-debug-start-debugger-stepinto">
+        <j2seproject1:nbjpdastart stopclassname="${main.class}"/>
+    </target>
+    <target depends="init,compile,-debug-start-debugger-stepinto,-debug-start-debuggee" if="netbeans.home" name="debug-stepinto"/>
+    <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-single">
+        <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
+        <j2seproject3:debug classname="${debug.class}"/>
+    </target>
+    <target depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-single" if="netbeans.home" name="debug-single"/>
+    <target depends="init,compile-test-single" if="netbeans.home" name="-debug-start-debuggee-main-test">
+        <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
+        <j2seproject3:debug classname="${debug.class}" classpath="${debug.test.classpath}"/>
+    </target>
+    <target depends="init,compile-test-single,-debug-start-debugger-main-test,-debug-start-debuggee-main-test" if="netbeans.home" name="debug-test-with-main"/>
+    <target depends="init" name="-pre-debug-fix">
+        <fail unless="fix.includes">Must set fix.includes</fail>
+        <property name="javac.includes" value="${fix.includes}.java"/>
+    </target>
+    <target depends="init,-pre-debug-fix,compile-single" if="netbeans.home" name="-do-debug-fix">
+        <j2seproject1:nbjpdareload/>
+    </target>
+    <target depends="init,-pre-debug-fix,-do-debug-fix" if="netbeans.home" name="debug-fix"/>
+    <!--
+                =================
+                PROFILING SECTION
+                =================
+            -->
+    <!--
+                pre NB7.2 profiler integration
+            -->
+    <target depends="profile-init,compile" description="Profile a project in the IDE." if="profiler.info.jvmargs.agent" name="-profile-pre72">
+        <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
+        <nbprofiledirect>
+            <classpath>
+                <path path="${run.classpath}"/>
+            </classpath>
+        </nbprofiledirect>
+        <profile/>
+    </target>
+    <target depends="profile-init,compile-single" description="Profile a selected class in the IDE." if="profiler.info.jvmargs.agent" name="-profile-single-pre72">
+        <fail unless="profile.class">Must select one file in the IDE or set profile.class</fail>
+        <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
+        <nbprofiledirect>
+            <classpath>
+                <path path="${run.classpath}"/>
+            </classpath>
+        </nbprofiledirect>
+        <profile classname="${profile.class}"/>
+    </target>
+    <target depends="profile-init,compile-single" if="profiler.info.jvmargs.agent" name="-profile-applet-pre72">
+        <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
+        <nbprofiledirect>
+            <classpath>
+                <path path="${run.classpath}"/>
+            </classpath>
+        </nbprofiledirect>
+        <profile classname="sun.applet.AppletViewer">
+            <customize>
+                <arg value="${applet.url}"/>
+            </customize>
+        </profile>
+    </target>
+    <target depends="profile-init,compile-test-single" if="profiler.info.jvmargs.agent" name="-profile-test-single-pre72">
+        <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
+        <nbprofiledirect>
+            <classpath>
+                <path path="${run.test.classpath}"/>
+            </classpath>
+        </nbprofiledirect>
+        <junit dir="${profiler.info.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" jvm="${profiler.info.jvm}" showoutput="true">
+            <env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
+            <jvmarg value="${profiler.info.jvmargs.agent}"/>
+            <jvmarg line="${profiler.info.jvmargs}"/>
+            <test name="${profile.class}"/>
+            <classpath>
+                <path path="${run.test.classpath}"/>
+            </classpath>
+            <syspropertyset>
+                <propertyref prefix="test-sys-prop."/>
+                <mapper from="test-sys-prop.*" to="*" type="glob"/>
+            </syspropertyset>
+            <formatter type="brief" usefile="false"/>
+            <formatter type="xml"/>
+        </junit>
+    </target>
+    <!--
+                end of pre NB72 profiling section
+            -->
+    <target if="netbeans.home" name="-profile-check">
+        <condition property="profiler.configured">
+            <or>
+                <contains casesensitive="true" string="${run.jvmargs.ide}" substring="-agentpath:"/>
+                <contains casesensitive="true" string="${run.jvmargs.ide}" substring="-javaagent:"/>
+            </or>
+        </condition>
+    </target>
+    <target depends="-profile-check,-profile-pre72" description="Profile a project in the IDE." if="profiler.configured" name="profile" unless="profiler.info.jvmargs.agent">
+        <startprofiler/>
+        <antcall target="run"/>
+    </target>
+    <target depends="-profile-check,-profile-single-pre72" description="Profile a selected class in the IDE." if="profiler.configured" name="profile-single" unless="profiler.info.jvmargs.agent">
+        <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
+        <startprofiler/>
+        <antcall target="run-single"/>
+    </target>
+    <target depends="-profile-test-single-pre72" description="Profile a selected test in the IDE." name="profile-test-single"/>
+    <target depends="-profile-check" description="Profile a selected test in the IDE." if="profiler.configured" name="profile-test" unless="profiler.info.jvmargs">
+        <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail>
+        <startprofiler/>
+        <antcall target="test-single"/>
+    </target>
+    <target depends="-profile-check" description="Profile a selected class in the IDE." if="profiler.configured" name="profile-test-with-main">
+        <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
+        <startprofiler/>
+        <antcal target="run-test-with-main"/>
+    </target>
+    <target depends="-profile-check,-profile-applet-pre72" if="profiler.configured" name="profile-applet" unless="profiler.info.jvmargs.agent">
+        <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
+        <startprofiler/>
+        <antcall target="run-applet"/>
+    </target>
+    <!--
+                ===============
+                JAVADOC SECTION
+                ===============
+            -->
+    <target depends="init" if="have.sources" name="-javadoc-build">
+        <mkdir dir="${dist.javadoc.dir}"/>
+        <condition else="" property="javadoc.endorsed.classpath.cmd.line.arg" value="-J${endorsed.classpath.cmd.line.arg}">
+            <and>
+                <isset property="endorsed.classpath.cmd.line.arg"/>
+                <not>
+                    <equals arg1="${endorsed.classpath.cmd.line.arg}" arg2=""/>
+                </not>
+            </and>
+        </condition>
+        <condition else="" property="bug5101868workaround" value="*.java">
+            <matches pattern="1\.[56](\..*)?" string="${java.version}"/>
+        </condition>
+        <javadoc additionalparam="-J-Dfile.encoding=${file.encoding} ${javadoc.additionalparam}" author="${javadoc.author}" charset="UTF-8" destdir="${dist.javadoc.dir}" docencoding="UTF-8" encoding="${javadoc.encoding.used}" failonerror="true" noindex="${javadoc.noindex}" nonavbar="${javadoc.nonavbar}" notree="${javadoc.notree}" private="${javadoc.private}" source="${javac.source}" splitindex="${javadoc.splitindex}" use="${javadoc.use}" useexternalfile="true" version="${javadoc.version}" windowtitle="${javadoc.windowtitle}">
+            <classpath>
+                <path path="${javac.classpath}"/>
+            </classpath>
+            <fileset dir="${src.dir}" excludes="${bug5101868workaround},${excludes}" includes="${includes}">
+                <filename name="**/*.java"/>
+            </fileset>
+            <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
+                <include name="**/*.java"/>
+                <exclude name="*.java"/>
+            </fileset>
+            <arg line="${javadoc.endorsed.classpath.cmd.line.arg}"/>
+        </javadoc>
+        <copy todir="${dist.javadoc.dir}">
+            <fileset dir="${src.dir}" excludes="${excludes}" includes="${includes}">
+                <filename name="**/doc-files/**"/>
+            </fileset>
+            <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
+                <include name="**/doc-files/**"/>
+            </fileset>
+        </copy>
+    </target>
+    <target depends="init,-javadoc-build" if="netbeans.home" name="-javadoc-browse" unless="no.javadoc.preview">
+        <nbbrowse file="${dist.javadoc.dir}/index.html"/>
+    </target>
+    <target depends="init,-javadoc-build,-javadoc-browse" description="Build Javadoc." name="javadoc"/>
+    <!--
+                =========================
+                TEST COMPILATION SECTION
+                =========================
+            -->
+    <target depends="init,compile" if="have.tests" name="-pre-pre-compile-test">
+        <mkdir dir="${build.test.classes.dir}"/>
+    </target>
+    <target name="-pre-compile-test">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target if="do.depend.true" name="-compile-test-depend">
+        <j2seproject3:depend classpath="${javac.test.classpath}" destdir="${build.test.classes.dir}" srcdir="${test.src.dir}"/>
+    </target>
+    <target depends="init,deps-jar,compile,-pre-pre-compile-test,-pre-compile-test,-compile-test-depend" if="have.tests" name="-do-compile-test">
+        <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" processorpath="${javac.test.processorpath}" srcdir="${test.src.dir}"/>
+        <copy todir="${build.test.classes.dir}">
+            <fileset dir="${test.src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
+        </copy>
+    </target>
+    <target name="-post-compile-test">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test,-do-compile-test,-post-compile-test" name="compile-test"/>
+    <target name="-pre-compile-test-single">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target depends="init,deps-jar,compile,-pre-pre-compile-test,-pre-compile-test-single" if="have.tests" name="-do-compile-test-single">
+        <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
+        <j2seproject3:force-recompile destdir="${build.test.classes.dir}"/>
+        <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" excludes="" includes="${javac.includes}" processorpath="${javac.test.processorpath}" sourcepath="${test.src.dir}" srcdir="${test.src.dir}"/>
+        <copy todir="${build.test.classes.dir}">
+            <fileset dir="${test.src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
+        </copy>
+    </target>
+    <target name="-post-compile-test-single">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test-single,-do-compile-test-single,-post-compile-test-single" name="compile-test-single"/>
+    <!--
+                =======================
+                TEST EXECUTION SECTION
+                =======================
+            -->
+    <target depends="init" if="have.tests" name="-pre-test-run">
+        <mkdir dir="${build.test.results.dir}"/>
+    </target>
+    <target depends="init,compile-test,-pre-test-run" if="have.tests" name="-do-test-run">
+        <j2seproject3:test includes="${includes}" testincludes="**/*Test.java"/>
+    </target>
+    <target depends="init,compile-test,-pre-test-run,-do-test-run" if="have.tests" name="-post-test-run">
+        <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
+    </target>
+    <target depends="init" if="have.tests" name="test-report"/>
+    <target depends="init" if="netbeans.home+have.tests" name="-test-browse"/>
+    <target depends="init,compile-test,-pre-test-run,-do-test-run,test-report,-post-test-run,-test-browse" description="Run unit tests." name="test"/>
+    <target depends="init" if="have.tests" name="-pre-test-run-single">
+        <mkdir dir="${build.test.results.dir}"/>
+    </target>
+    <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-do-test-run-single">
+        <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail>
+        <j2seproject3:test excludes="" includes="${test.includes}" testincludes="${test.includes}"/>
+    </target>
+    <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single" if="have.tests" name="-post-test-run-single">
+        <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
+    </target>
+    <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single,-post-test-run-single" description="Run single unit test." name="test-single"/>
+    <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-do-test-run-single-method">
+        <fail unless="test.class">Must select some files in the IDE or set test.class</fail>
+        <fail unless="test.method">Must select some method in the IDE or set test.method</fail>
+        <j2seproject3:test excludes="" includes="${javac.includes}" testincludes="${test.class}" testmethods="${test.method}"/>
+    </target>
+    <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single-method" if="have.tests" name="-post-test-run-single-method">
+        <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
+    </target>
+    <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single-method,-post-test-run-single-method" description="Run single unit test." name="test-single-method"/>
+    <!--
+                =======================
+                TEST DEBUGGING SECTION
+                =======================
+            -->
+    <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-debug-start-debuggee-test">
+        <fail unless="test.class">Must select one file in the IDE or set test.class</fail>
+        <j2seproject3:test-debug excludes="" includes="${javac.includes}" testClass="${test.class}" testincludes="${javac.includes}"/>
+    </target>
+    <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-debug-start-debuggee-test-method">
+        <fail unless="test.class">Must select one file in the IDE or set test.class</fail>
+        <fail unless="test.method">Must select some method in the IDE or set test.method</fail>
+        <j2seproject3:test-debug excludes="" includes="${javac.includes}" testClass="${test.class}" testMethod="${test.method}" testincludes="${test.class}" testmethods="${test.method}"/>
+    </target>
+    <target depends="init,compile-test" if="netbeans.home+have.tests" name="-debug-start-debugger-test">
+        <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${test.class}"/>
+    </target>
+    <target depends="init,compile-test-single,-debug-start-debugger-test,-debug-start-debuggee-test" name="debug-test"/>
+    <target depends="init,compile-test-single,-debug-start-debugger-test,-debug-start-debuggee-test-method" name="debug-test-method"/>
+    <target depends="init,-pre-debug-fix,compile-test-single" if="netbeans.home" name="-do-debug-fix-test">
+        <j2seproject1:nbjpdareload dir="${build.test.classes.dir}"/>
+    </target>
+    <target depends="init,-pre-debug-fix,-do-debug-fix-test" if="netbeans.home" name="debug-fix-test"/>
+    <!--
+                =========================
+                APPLET EXECUTION SECTION
+                =========================
+            -->
+    <target depends="init,compile-single" name="run-applet">
+        <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
+        <j2seproject1:java classname="sun.applet.AppletViewer">
+            <customize>
+                <arg value="${applet.url}"/>
+            </customize>
+        </j2seproject1:java>
+    </target>
+    <!--
+                =========================
+                APPLET DEBUGGING  SECTION
+                =========================
+            -->
+    <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-applet">
+        <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
+        <j2seproject3:debug classname="sun.applet.AppletViewer">
+            <customize>
+                <arg value="${applet.url}"/>
+            </customize>
+        </j2seproject3:debug>
+    </target>
+    <target depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-applet" if="netbeans.home" name="debug-applet"/>
+    <!--
+                ===============
+                CLEANUP SECTION
+                ===============
+            -->
+    <target name="-deps-clean-init" unless="built-clean.properties">
+        <property location="${build.dir}/built-clean.properties" name="built-clean.properties"/>
+        <delete file="${built-clean.properties}" quiet="true"/>
+    </target>
+    <target if="already.built.clean.${basedir}" name="-warn-already-built-clean">
+        <echo level="warn" message="Cycle detected: WSYD was already built"/>
+    </target>
+    <target depends="init,-deps-clean-init" name="deps-clean" unless="no.deps">
+        <mkdir dir="${build.dir}"/>
+        <touch file="${built-clean.properties}" verbose="false"/>
+        <property file="${built-clean.properties}" prefix="already.built.clean."/>
+        <antcall target="-warn-already-built-clean"/>
+        <propertyfile file="${built-clean.properties}">
+            <entry key="${basedir}" value=""/>
+        </propertyfile>
+    </target>
+    <target depends="init" name="-do-clean">
+        <delete dir="${build.dir}"/>
+        <delete dir="${dist.dir}" followsymlinks="false" includeemptydirs="true"/>
+    </target>
+    <target name="-post-clean">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target depends="init,deps-clean,-do-clean,-post-clean" description="Clean build products." name="clean"/>
+    <target name="-check-call-dep">
+        <property file="${call.built.properties}" prefix="already.built."/>
+        <condition property="should.call.dep">
+            <and>
+                <not>
+                    <isset property="already.built.${call.subproject}"/>
+                </not>
+                <available file="${call.script}"/>
+            </and>
+        </condition>
+    </target>
+    <target depends="-check-call-dep" if="should.call.dep" name="-maybe-call-dep">
+        <ant antfile="${call.script}" inheritall="false" target="${call.target}">
+            <propertyset>
+                <propertyref prefix="transfer."/>
+                <mapper from="transfer.*" to="*" type="glob"/>
+            </propertyset>
+        </ant>
+    </target>
+</project>
diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties
new file mode 100644 (file)
index 0000000..2d6f5c2
--- /dev/null
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=1eb19475
+build.xml.script.CRC32=d46d0af0
+build.xml.stylesheet.CRC32=8064a381@1.75.2.48
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=1eb19475
+nbproject/build-impl.xml.script.CRC32=97fbc70c
+nbproject/build-impl.xml.stylesheet.CRC32=876e7a8f@1.75.2.48
diff --git a/nbproject/project.properties b/nbproject/project.properties
new file mode 100644 (file)
index 0000000..d0bb991
--- /dev/null
@@ -0,0 +1,95 @@
+annotation.processing.enabled=true
+annotation.processing.enabled.in.editor=false
+annotation.processing.processors.list=
+annotation.processing.run.all.processors=true
+annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
+application.desc=Social Networking and Chat client and server
+application.title=WSYD
+application.vendor=TJ <hacker@iam.tj>
+auxiliary.org-netbeans-spi-editor-hints-projects.perProjectHintSettingsFile=nbproject/cfg_hints.xml
+build.classes.dir=${build.dir}/classes
+build.classes.excludes=**/*.java,**/*.form
+# This directory is removed when the project is cleaned:
+build.dir=build
+build.generated.dir=${build.dir}/generated
+build.generated.sources.dir=${build.dir}/generated-sources
+# Only compile against the classpath explicitly listed here:
+build.sysclasspath=ignore
+build.test.classes.dir=${build.dir}/test/classes
+build.test.results.dir=${build.dir}/test/results
+# Uncomment to specify the preferred debugger connection transport:
+#debug.transport=dt_socket
+debug.classpath=\
+    ${run.classpath}
+debug.test.classpath=\
+    ${run.test.classpath}
+# Files in build.classes.dir which should be excluded from distribution jar
+dist.archive.excludes=
+# This directory is removed when the project is cleaned:
+dist.dir=dist
+dist.jar=${dist.dir}/WSYD.jar
+dist.javadoc.dir=${dist.dir}/javadoc
+endorsed.classpath=
+excludes=
+includes=**
+jar.archive.disabled=${jnlp.enabled}
+jar.compress=false
+jar.index=${jnlp.enabled}
+javac.classpath=\
+    ${libs.absolutelayout.classpath}
+# Space-separated list of extra javac options
+javac.compilerargs=-Xlint:unchecked
+javac.deprecation=false
+javac.processorpath=\
+    ${javac.classpath}
+javac.source=1.7
+javac.target=1.7
+javac.test.classpath=\
+    ${javac.classpath}:\
+    ${build.classes.dir}:\
+    ${libs.junit_4.classpath}
+javac.test.processorpath=\
+    ${javac.test.classpath}
+javadoc.additionalparam=
+javadoc.author=true
+javadoc.encoding=${source.encoding}
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=true
+javadoc.splitindex=false
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=We Stealz Your Dataz
+jnlp.codebase.type=no.codebase
+jnlp.descriptor=application
+jnlp.enabled=false
+jnlp.mixed.code=default
+jnlp.offline-allowed=false
+jnlp.signed=false
+jnlp.signing=
+jnlp.signing.alias=
+jnlp.signing.keystore=
+main.class=
+# Optional override of default Codebase manifest attribute, use to prevent RIAs from being repurposed
+manifest.custom.codebase=
+# Optional override of default Permissions manifest attribute (supported values: sandbox, all-permissions)
+manifest.custom.permissions=
+manifest.file=manifest.mf
+meta.inf.dir=${src.dir}/META-INF
+mkdist.disabled=false
+platform.active=default_platform
+project.license=mit
+run.classpath=\
+    ${javac.classpath}:\
+    ${build.classes.dir}
+# Space-separated list of JVM arguments used when running the project.
+# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
+# To set system properties for unit tests define test-sys-prop.name=value:
+run.jvmargs=
+run.test.classpath=\
+    ${javac.test.classpath}:\
+    ${build.test.classes.dir}
+source.encoding=UTF-8
+src.dir=src
+test.src.dir=test
diff --git a/nbproject/project.xml b/nbproject/project.xml
new file mode 100644 (file)
index 0000000..27ca082
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.netbeans.org/ns/project/1">
+    <type>org.netbeans.modules.java.j2seproject</type>
+    <configuration>
+        <data xmlns="http://www.netbeans.org/ns/j2se-project/3">
+            <name>WSYD</name>
+            <source-roots>
+                <root id="src.dir"/>
+            </source-roots>
+            <test-roots>
+                <root id="test.src.dir"/>
+            </test-roots>
+        </data>
+        <libraries xmlns="http://www.netbeans.org/ns/ant-project-libraries/1">
+            <definitions>./lib/nblibraries.properties</definitions>
+        </libraries>
+    </configuration>
+</project>
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member.java b/src/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member.java
new file mode 100644 (file)
index 0000000..621e147
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.TreeSet;
+import java.util.logging.Level;
+
+/**
+ * Contains personal data about a member.
+ * 
+ * _userID contains a key that uniquely identifies this member. This should be used by other
+ * classes and friend references to ensure data is not duplicated unnecessarily.
+ * 
+ * The object can be serialized over a stream to a File or Network connection and added to a sorted collection.
+ * It can be added to ordered Collections and provides its own compareTo() using userName.
+ *
+ * It is supported by 2 Comparators:
+ * 
+ * WSYD_Member_Comparator_UserID supports sorted Maps with userID as the sort key
+ * WSYD_Member_Comparator_UserName supports sorted Sets and Collections with userName as the sort key
+ *
+ * @author TJ <hacker@iam.tj>
+ */
+public class WSYD_Member implements java.io.Serializable, java.lang.Comparable<WSYD_Member> {
+    public long _userID;
+    public String _userName;
+    public String _password;
+    public String _currentLocation;
+    public String _bio;
+    public Date _birthDate;
+    public TreeSet<String> _interests;
+    public TreeSet<Long> _friends;
+    public TreeSet<Long> _friendsRequestsSent;
+    public TreeSet<Long> _friendsRequestsReceived;
+    
+    public static enum Fields {
+        USER_ID, USER_NAME, PASSWORD, CURRENT_LOCATION, BIO, BIRTH_DATE, INTERESTS, FRIENDS, FRIENDS_REQUESTS_SENT, FRIENDS_REQUESTS_RECEIVED
+    }
+    
+    /**
+     * Default constructor
+     */
+    public WSYD_Member() {}
+    
+    /**
+     * Construct a complete member
+     * 
+     * @param userID unique key
+     * @param userName user's preferred name
+     * @param password password for authentication
+     * @param currentLocation Geographic area
+     * @param bio Personal biography
+     * @param birthDate date of birth
+     * @param interests titles of personal interests
+     * @param friends IDs of all accepted friends
+     * @param friendsRequestsSent IDs of pending friends requests 
+     * @param friendsRequestsReceived IDs of pending friends requests sent
+     */
+    public WSYD_Member(long userID, String userName, String password, String currentLocation, String bio,
+                       Date birthDate,
+                       TreeSet<String> interests,
+                       TreeSet<Long> friends,
+                       TreeSet<Long> friendsRequestsSent,
+                       TreeSet<Long> friendsRequestsReceived
+    )
+    {
+        _userID = userID;
+        _userName = userName;
+        _password = password;
+        _currentLocation = currentLocation;
+        _bio = bio;
+        _birthDate = birthDate;
+        _interests = interests;
+        _friends = friends;
+        _friendsRequestsSent = friendsRequestsSent;
+        _friendsRequestsReceived = friendsRequestsReceived;
+    }
+
+    public static WSYD_Member createWSYD_Member(String line) throws IllegalArgumentException {
+        WSYD_Member result = null;
+        
+        if (line.startsWith("#")) {
+            return result; // ignore comment lines
+        }
+        result = new WSYD_Member();
+        // split line into array of fields
+        String[] fields = line.split(",");
+        if (fields.length != 10)
+            throw new IllegalArgumentException();
+
+        // convert each field from a String to the correct type using the Fields enum
+        result._userID = new Long(fields[WSYD_Member.Fields.USER_ID.ordinal()]);
+        result._userName = fields[WSYD_Member.Fields.USER_NAME.ordinal()];
+        result._password = fields[WSYD_Member.Fields.PASSWORD.ordinal()];
+        result._currentLocation = fields[WSYD_Member.Fields.CURRENT_LOCATION.ordinal()];
+        result._bio = fields[WSYD_Member.Fields.BIO.ordinal()];
+        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+        try {
+            result._birthDate = df.parse(fields[WSYD_Member.Fields.BIRTH_DATE.ordinal()]);
+        } catch (ParseException e) {
+            result._birthDate = new Date();
+        }
+        // read the multi-element entries. "~" is used as the element separator
+        result._interests = new TreeSet<>();
+        String[] items = fields[WSYD_Member.Fields.INTERESTS.ordinal()].split("~");
+        result._interests.addAll(Arrays.asList(items));
+        result._friends = new TreeSet<>();
+        items = fields[WSYD_Member.Fields.FRIENDS.ordinal()].split("~");
+        for (String item : items) {
+            result._friends.add(new java.lang.Long(item));
+        }
+        result._friendsRequestsSent = new TreeSet<>();
+        items = fields[WSYD_Member.Fields.FRIENDS_REQUESTS_SENT.ordinal()].split("~");
+        for (String item : items) {
+            result._friendsRequestsSent.add(new Long(item));
+        }
+        result._friendsRequestsReceived = new TreeSet<>();
+        items = fields[WSYD_Member.Fields.FRIENDS_REQUESTS_RECEIVED.ordinal()].split("~");
+        for (String item : items) {
+            result._friendsRequestsReceived.add(new Long(item));
+        }
+
+        return result;
+    }
+
+    /**
+     * implement the java.lang.Comparable interface
+     * 
+     * @param m the member to compare with
+     * @return Returns a negative integer, zero, or a positive integer as this member is less than, equal to, or greater than the provided member
+     */
+    @Override
+    public int compareTo(WSYD_Member m) throws IllegalArgumentException {
+        if (m == null)
+            throw new IllegalArgumentException("Reference cannot be null");
+        return this._userName.compareTo(m._userName);
+    }
+    
+    /**
+     * Convert to a String suitable for display and export to CSV
+     * 
+     * Fields containing collections write collection members separated by "~"
+     * 
+     * @return String
+     */
+    @Override
+    public String toString() {
+        String interests = new String();
+        for (String i: _interests) {
+            interests = interests.concat((interests.length() > 0 ? "~" : "") + i);
+        }
+        String friends = new String();
+        for (Long l: _friends) {
+            friends = friends.concat((friends.length() > 0 ? "~" : "") + l.toString());
+        }
+        String friendsRequestsSent = new String();
+        for (Long l: _friendsRequestsSent) {
+            friendsRequestsSent = friendsRequestsSent.concat((friendsRequestsSent.length() > 0 ? "~" : "") + l.toString());
+        }
+        String friendsRequestsReceived = new String();
+        for (Long l: _friendsRequestsReceived) {
+            friendsRequestsReceived = friendsRequestsReceived.concat((friendsRequestsReceived.length() > 0 ? "~" : "") + l.toString());
+        }
+        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+
+        String record = Long.toString(_userID) + ","
+                    + _userName + ","
+                    + _password + ","
+                    + _currentLocation + ","
+                    + _bio + ","
+                    + df.format(_birthDate) + ","
+                    + interests + ","
+                    + friends + ","
+                    + friendsRequestsSent + ","
+                    + friendsRequestsReceived;
+
+        return record;
+    }
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member_Comparator_UserID.java b/src/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member_Comparator_UserID.java
new file mode 100644 (file)
index 0000000..bdcf4bf
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs;
+
+import java.util.Comparator;
+import java.io.Serializable;
+
+/**
+ * Order by Long values; usable to sort keys in Collections e.g. TreeMap<Long, WSYD_Member>.
+ * 
+ * Implements serializable in order that the Collection using it doesn't cause 
+ * deserialize Exceptions due to missing Comparator
+ *
+ * @author TJ <hacker@iam.tj>
+ */
+public class WSYD_Member_Comparator_UserID implements Comparator<Long>, Serializable {
+    /**
+     * Sorts numerically low to high
+     * 
+     * @param user1
+     * @param user2
+     * @return -1 if user1 < user2, 0 if user1 == user2, 1 if user1 > user2
+     * @throws NullPointerException
+     */
+    @Override
+    public int compare(Long user1, Long user2) throws NullPointerException {
+        if (user1 == null || user2 == null)
+            throw new NullPointerException();
+        return Long.compare(user1, user2);
+    }
+}
\ No newline at end of file
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member_Comparator_UserName.java b/src/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member_Comparator_UserName.java
new file mode 100644 (file)
index 0000000..c4594b1
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * Order WSYD_Members by user name; usable by TreeSet<WSYD_Member> and other Collections.
+ * 
+ * Implements serializable in order that the Collection using it doesn't cause 
+ * deserialize Exceptions due to missing Comparator
+ *
+ * @author TJ <hacker@iam.tj>
+ */
+public class WSYD_Member_Comparator_UserName implements Comparator<WSYD_Member>, Serializable {
+    /**
+     * Sorts alphabetically on username, low to high.
+     * 
+     * @param m1
+     * @param m2
+     * @return -1 if m1 < m2, 0 if m1 == m2, 1 if m1 > m2
+     */
+    @Override
+    public int compare(WSYD_Member m1, WSYD_Member m2) throws NullPointerException {
+        if (m1 == null || m2 == null)
+            throw new NullPointerException();
+        return m1._userName.compareTo(m2._userName);
+    }
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/logging/PacketHandler.java b/src/uk/ac/ntu/n0521366/wsyd/libs/logging/PacketHandler.java
new file mode 100644 (file)
index 0000000..b8a3da8
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.logging;
+
+import java.io.IOException;
+import java.util.logging.LogRecord;
+import java.util.logging.Handler;
+import java.util.logging.ErrorManager;
+import java.net.InetSocketAddress;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import uk.ac.ntu.n0521366.wsyd.libs.message.MessageLogRecord;
+import uk.ac.ntu.n0521366.wsyd.libs.net.NetworkMessage;
+
+/**
+ * Publish log records over UDP encapsulated in a NetworkMessage.
+ *
+ * Sends the records to a remote NetworkServerUDP service.
+ *
+ * @see java.util.logging.Handler
+ * @see uk.ac.ntu.n0521366.wsyd.libs.net.NetworkMessage
+ * @author TJ <hacker@iam.tj>
+ */
+public class PacketHandler extends Handler {
+
+    /**
+     * Where to send the log record
+     */
+    private final InetSocketAddress _socketAddress;
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public void flush() {
+    }
+
+    /**
+     * Send the record to the log service.
+     * 
+     * Wraps the record in a Message, and the Message in a NetworkMessage
+     * 
+     * @param record 
+     */
+    @Override
+    public void publish(LogRecord record) {
+        if (record == null) {
+            return;
+        }
+        if (!this.isLoggable(record)) {
+            return;
+        }
+        MessageLogRecord m = new MessageLogRecord(record);
+        NetworkMessage nm = NetworkMessage.createNetworkMessage("Log", "ServerLog", m);
+        try {
+            byte[] data = NetworkMessage.serialize(nm);
+            DatagramPacket packetMessage = new DatagramPacket(data, data.length);
+
+            // set remote log server host address and port
+            packetMessage.setSocketAddress(this._socketAddress);
+
+            try (
+                DatagramSocket datagramSocket = new DatagramSocket(_socketAddress.getPort(), _socketAddress.getAddress())
+            )
+            {
+                datagramSocket.send(packetMessage);
+            } catch (IOException e ) {
+                reportError("Unable to create UDP socket", e, ErrorManager.WRITE_FAILURE);
+            }
+        } catch (IOException e) {
+            reportError("Unable to serialize NetworkMessage", e, ErrorManager.WRITE_FAILURE);
+        }
+    }
+
+    /**
+     * Create a handler that sends log records to a UDP log service.
+     * 
+     * @param logService address of the target log service
+     */
+    public PacketHandler(InetSocketAddress logService) {
+        this._socketAddress = logService;
+    }
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/logging/TableModelHandler.java b/src/uk/ac/ntu/n0521366/wsyd/libs/logging/TableModelHandler.java
new file mode 100644 (file)
index 0000000..6fea35a
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.logging;
+
+import java.util.logging.LogRecord;
+import java.util.logging.Handler;
+import java.util.logging.Filter;
+
+/**
+ * Publishes log records to a TableModel.
+ * 
+ * JScrollPane contains a JTable which uses a TableModel to hold its data.
+ *
+ * @see java.util.logging.Handler
+ * @see javax.swing.JScrollPane
+ * @see javax.swing.JTable
+ * @see javax.swing.table.DefaultTableModel
+ * @author TJ <hacker@iam.tj>
+ */
+public class TableModelHandler extends Handler {
+    
+    /**
+     * Listener object to publish to (implements java.util.logging.Filter).
+     */
+    private final Filter _publisher;
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public void flush() {
+    }
+
+    @Override
+    public void publish(LogRecord record) {
+        if (_publisher != null)
+            _publisher.isLoggable(record);
+    }
+    
+    /**
+     * Create a handler that sends log records to a TableModel
+     * 
+     * @param publisher the object that receives the records
+     */
+    public TableModelHandler(Filter publisher) {
+         _publisher = publisher;
+    }
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/message/MessageAbstract.java b/src/uk/ac/ntu/n0521366/wsyd/libs/message/MessageAbstract.java
new file mode 100644 (file)
index 0000000..66530d8
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.message;
+
+import java.io.Serializable;
+import java.net.InetAddress;
+
+/**
+ * Base class of all Message types.
+ * 
+ * @author TJ <hacker@iam.tj>
+ */
+public abstract class MessageAbstract implements Serializable {
+    /**
+     * The purpose of this class of message.
+     * 
+     * @return E.g. "LogRecord", "FriendOnline", "ChatRequest"
+     */
+    public abstract String getMessageType();
+    
+    /**
+     * IP address of the sending host.
+     * 
+     * This should be set by the <em>receiving service</em> using the value obtained from
+     * the packet or connection the message arrived in.
+     * 
+     * FIXME: sourceAddress: is this really needed? the message encapsulated in a NetworkMessage already
+     */
+    public InetAddress sourceAddress;
+    
+    public MessageAbstract() {
+        sourceAddress = null;
+    }
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/message/MessageLogRecord.java b/src/uk/ac/ntu/n0521366/wsyd/libs/message/MessageLogRecord.java
new file mode 100644 (file)
index 0000000..485b879
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.message;
+
+import java.util.logging.LogRecord;
+
+/**
+ * A specialised message containing a LogRecord
+ * 
+ * @see java.util.logging.LogRecord
+ * @author TJ <hacker@iam.tj>
+ */
+public class MessageLogRecord extends MessageAbstract {
+    private static final String _type = "LogRecord";
+
+    /**
+     * Return the class type without needing an instance object.
+     * 
+     * Workaround for Java not permitting 'abstract static' qualifiers which would
+     * allow static methods in abstract base classes to be overridden in sub-classes.
+     * 
+     * @return class type
+     */
+    public static String getType() {
+        return _type;
+    }
+    
+    /**
+     * The encapsulated log record
+     * @see java.util.logging.LogRecord
+     */
+    public LogRecord record;
+
+    /**
+     * The message type.
+     *
+     * @return this class's type
+     */
+    @Override
+    public String getMessageType() {
+        return _type;
+    }
+
+    /**
+     * Construct a new message.
+     * 
+     * @param record
+     */
+    public MessageLogRecord(LogRecord record) {
+        super();
+        this.record = record;
+    }
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/message/MessagePresence.java b/src/uk/ac/ntu/n0521366/wsyd/libs/message/MessagePresence.java
new file mode 100644 (file)
index 0000000..948eb34
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.message;
+
+/**
+ *
+ * @author TJ <hacker@iam.tj>
+ */
+public class MessagePresence extends MessageAbstract {
+    private static final String _type = "Presence";
+    
+    /**
+     * Return the class type without needing an instance object.
+     * 
+     * Workaround for Java not permitting 'abstract static' qualifiers which would
+     * allow static methods in abstract base classes to be overridden in sub-classes.
+     * 
+     * @return class type
+     */
+    public static String getType() {
+        return _type;
+    }
+    
+    /**
+     * The title of the service advertising itself.
+     * 
+     * E.g. "ServerSocial", "ServerChat", "ServerLog","UserSocial", 'UserChat",  "Notify"
+     */
+    public String serviceName;
+    
+    /**
+     * The port number this service listens on.
+     * 
+     * The IP address should be set by the receiving service in the sourceAddress property.
+     */
+    int port;
+
+    /**
+     * The message type
+     * @return "Presence"
+     */
+    @Override
+    public String getMessageType() {
+        return _type;
+    }
+
+    public MessagePresence(String service, int port) {
+        super();
+        this.serviceName = service;
+        this.port = port;
+    } 
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/net/Network.java b/src/uk/ac/ntu/n0521366/wsyd/libs/net/Network.java
new file mode 100644 (file)
index 0000000..1b5364c
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.net;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ *
+ * @author TJ <hacker@iam.tj>
+ */
+public class Network {
+    public static final int PORTS_SERVER_SOCIAL = 50000;
+    public static final int PORTS_SERVER_CHAT = 50001;
+    public static final int PORTS_SERVER_LOG = 50002;
+    public static final int PORTS_MULTICAST_DISCOVERY = 50003;
+    public static final int PORTS_CLIENT_CONTROL_FIRST = 500010;
+    public static final int PORTS_CLIENT_CHAT_FIRST = 51000;
+    
+    /**
+     * Special IPv4 'wildcard' address asking java.net.ServerSocket#bind to bind to all interfaces.
+     */    
+    public static final  InetAddress IPv4_WILDCARD;
+    /**
+     * Multicast IPv4 address the applications use to discover each other.
+     */
+    public static final InetAddress MULTICAST_IP;
+    /**
+     * Unicast IPv4 address for testing network connectivity. 
+     */
+    public static final InetAddress PING_IP;
+
+    /**
+     * Initialisation of class-static final constants.
+     * 
+     * Required in order to initialise static final constant values.
+     * Only runs once when the class is created, not when object instances are constructed.
+     */
+    static {
+        InetAddress temp;
+        /* temporary used to avoid the compiler error:
+         *
+         * variable IPv4_WILDCARD might already have been assigned
+         *
+         * that will be caused if the value was directly assigned to the 
+         * 'static final' variable in both the try and catch clauses.
+         */
+        try {
+            temp = InetAddress.getByAddress(new byte[] {0,0,0,0});
+        } catch (UnknownHostException ex) {
+            temp = null; //FIXME: variable IPv4_WILDCARD might already have been assigned
+        }
+        IPv4_WILDCARD = temp;
+        
+        try {
+            temp  = InetAddress.getByAddress(new byte[] {(byte)239, (byte)192, (byte)3, (byte)4});
+        } catch (UnknownHostException ex) {
+            temp = null; // FIXME: variable MULTICAST_IP might already have been assigned
+        }
+        MULTICAST_IP = temp;
+        try {
+            temp       = InetAddress.getByAddress(new byte[] {(byte)176, (byte)58, (byte)127, (byte)210});
+        } catch (UnknownHostException ex) {
+            temp = null; // FIXME: variable PING_IP might already have been assigned
+        }
+        PING_IP = temp;
+    }
+
+    public static final InetAddress getDefaultRoutedInetAddress() {
+        InetAddress result = null;
+        try  {
+            WSYD_SocketAddress sa = new WSYD_SocketAddress(PING_IP);
+            // TODO: getDefaultRoutedInetAddress() - implement route detection logic
+        } catch(Exception e) {
+            // TODO: getDefaultRoutedInetAddress() handle all Exceptions
+        }
+        return result;
+    }
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.java b/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessage.java
new file mode 100644 (file)
index 0000000..a3e9dd3
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.net;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import uk.ac.ntu.n0521366.wsyd.libs.message.MessageAbstract;
+
+/**
+ * Wraps an object and intent label together for passing over a network.
+ * 
+ * @see NetworkMessageEvent
+ * @author TJ <hacker@iam.tj>
+ */
+public class NetworkMessage implements Serializable, Cloneable {
+    /**
+     * number of bytes used by serialized object
+     */
+    int _serializeLength;
+    
+    /**
+     * Key describing purpose of message; receiver uses this to dispatch to the correct
+     * consumer method.
+     */
+    String _intent;
+
+    /**
+     * Filled in by the network server service that sends the message
+     */
+    String _serviceSender;
+
+    /**
+     * Filled in by the message originator with the title of the destination service
+     */
+    String _serviceTarget;
+
+    Class<?> _class;
+    MessageAbstract _message;
+    
+    /**
+     * Default constructor.
+     */
+    NetworkMessage() {
+        _serializeLength = -1;
+        _intent = null;
+        _serviceSender = null;
+        _serviceTarget = null;
+        _class = null;
+        _message = null;
+    }
+    
+    /**
+     * Create a message for passing over the network.
+     * 
+     * @param intent The purpose of the attached object
+     * @param target The title of the target service
+     * @param message The Message being passed. Must implement java.io.Serialize or only
+     * contain native types that are serializable
+     * @throws IllegalArgumentException 
+     */
+    NetworkMessage(String intent, String target, MessageAbstract message) throws IllegalArgumentException {
+        _serializeLength = -1;
+        if ( !(intent != null && intent.length() > 0) ) 
+            throw(new IllegalArgumentException("intent cannot be null or empty"));
+        if (message == null)
+            throw(new IllegalArgumentException("message cannot be null"));
+        _intent = intent;
+        _serviceSender = null;
+        _serviceTarget = target;
+        _message = message;
+        _class = _message.getClass();
+    }
+    
+    /**
+     * Set the number of bytes used by the serialized object.
+     * 
+     * @param length number of bytes
+     */
+    void setSerializeLength(int length) {
+        _serializeLength = length;
+    }
+
+    /**
+     * Set to the title of the sending service to allow replies to be routed correctly.
+     * 
+     * @param sender title of the sending service
+     */
+    public void setSender(String sender) {
+        _serviceSender = sender;
+    }
+    
+    /**
+     * Get the title of the service that sent this message.
+     * 
+     * @return title of the sending service
+     */
+    public String getSender() {
+        return _serviceSender;
+    }
+
+    /**
+     * Set to the title of the target service to allow correct routing.
+     * 
+     * @param target title of the destination service
+     */
+    public void setTarget(String target) {
+        _serviceTarget = target;
+    }
+
+    /**
+     * Get the name of the sending service.
+     * 
+     * @return the sender
+     */
+    public String getTarget() {
+        return _serviceTarget;
+    }
+
+    /**
+     * Get the intended destination topic.
+     * @return the intent
+     */
+    public String getIntent() {
+        return _intent;
+    }
+    /**
+     * Get the content of this NetworkMessage.
+     * 
+     * @return the encapsulated message
+     */
+    public MessageAbstract getMessage() {
+        return _message;
+    }
+
+    /**
+     * Get the size of the serialized object.
+     * 
+     * @return -1 if not yet serialized, otherwise number of bytes required
+     */
+    public int getSerializeLength() {
+        return _serializeLength;
+    }
+    
+    /**
+     * Create a message for passing over the network.
+     * 
+     * @param intent The purpose of the attached object
+     * @param target The title of the target service
+     * @param message The encapsulated message
+     * contain native types that are serializable
+     * @return a freshly constructed NetworkMessage object
+     * @throws IllegalArgumentException 
+     */
+    public static NetworkMessage createNetworkMessage(String intent, String target, MessageAbstract message) throws IllegalArgumentException {
+        return new NetworkMessage(intent, target, message);            
+    }
+
+    /**
+     * Make a deep clone of this message and all its member objects.
+     * 
+     * It is not possible to safely use shallow or deep cloning of all this class's
+ members since its _message member reference could be to an object of any type,
+ and those types and their member attributes may not override the Object.clone()
+ method via the Cloneable interface.
+ Cannot rely on using copy constructors since _message (or its member attributes)
+ may reference an object  that does not have a copy constructor.
+ That leaves an expensive (in time) but reliable trick - serialize and deserialize 
+ the object to/from a byte stream. Since NetworkMessage already has methods supporting
+ serialization this is a simple solution to implement.
+     * 
+     * @see NetworkMessage#serialize
+     * @see NetworkMessage#deserialize
+     * @param message The message to clone
+     * @return Deep clone of the message object
+     * @throws java.lang.CloneNotSupportedException
+     */
+    public static NetworkMessage clone(NetworkMessage message) throws CloneNotSupportedException {
+        NetworkMessage result = null;
+        
+        if (! (message instanceof Serializable))
+            throw new CloneNotSupportedException();
+
+        try {
+            result = deserialize(serialize(message));
+        } catch (IOException | ClassNotFoundException e) {
+            throw new CloneNotSupportedException();
+        }
+
+        return result;
+    }
+    
+    /**
+     * Support the Cloneable interface but use the class's static serialization clone method.
+     * 
+     * @see NetworkMessage#clone(NetworkMessage) 
+     * @return Deep clone of this object
+     * @throws CloneNotSupportedException
+     */
+    @Override
+    @SuppressWarnings("CloneDoesntCallSuperClone")
+    protected Object clone() throws CloneNotSupportedException {
+        return NetworkMessage.clone(this);
+    }
+    
+    /**
+     * Serialize the message.
+     * 
+     * @param message
+     * @return the message as an array of bytes
+     * @throws IOException
+     */
+    public static byte[] serialize(NetworkMessage message) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ObjectOutputStream oos = new ObjectOutputStream(baos);
+        oos.writeObject(message);
+        byte[] bytes = baos.toByteArray();
+        message.setSerializeLength(bytes.length);
+        return bytes;
+    }
+    
+    /**
+     * Deserialize raw data into a NetworkMessage.
+     * 
+     * @param bytes raw bytes
+     * @return the NetworkMessage object
+     * @throws IOException
+     * @throws ClassNotFoundException 
+     */
+    public static NetworkMessage deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
+        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+        ObjectInputStream ois = new ObjectInputStream(bais);
+        NetworkMessage temp = (NetworkMessage) ois.readObject();
+        temp.setSerializeLength(bytes.length);
+        return temp;
+    }
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEvent.java b/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEvent.java
new file mode 100644 (file)
index 0000000..7aae560
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.net;
+
+import java.util.EventObject;
+
+/**
+ * Custom Event for network services to notify listeners that a message has been received
+ * 
+ * @see NetworkMessageEventGenerator
+ * @see NetworkMessageEventListener
+ * @author TJ <hacker@iam.tj>
+ */
+public class NetworkMessageEvent extends EventObject {
+    /**
+     * Immutable (because it is declared final) message
+     */
+    private final NetworkMessage _message;
+    
+    /**
+     * Create a new Event.
+     * 
+     * @param source the object firing the event
+     * @param message the message itself
+     */
+    public NetworkMessageEvent(Object source, NetworkMessage message) {
+        super(source);
+        _message = message;
+    }
+    
+    /**
+     * Retrieve the message.
+     * 
+     * Called by event listeners.
+     *
+     * @return the message
+     */
+    public NetworkMessage getNetworkMessage() {
+        return _message;
+    }
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEventGenerator.java b/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEventGenerator.java
new file mode 100644 (file)
index 0000000..400230e
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.net;
+
+/**
+ * Implemented by classes that generate NetworkMessageEvent events.
+ * 
+ * @see NetworkMessage
+ * @see NetworkMessageEvent
+ * @see NetworkMessageEventListener
+ * @author TJ <hacker@iam.tj>
+ */
+public interface NetworkMessageEventGenerator {
+    /**
+     * Adds a listener to the list.
+     * 
+     * @param listener 
+     */
+    public void addNetworkMessageEventListener(NetworkMessageEventListener listener);
+    /**
+     * Adds a filtered listener to the list.
+     * 
+     * This listener will only receive events for NetworkMessages that have a matching intent.
+     * 
+     * @param listener
+     * @param intent the intent to match
+     */
+    public void addNetworkMessageEventListener(NetworkMessageEventListener listener, String intent);
+
+    /**
+     * Removes a listener from the list.
+     * 
+     * @param listener 
+     */
+    public void removeNetworkMessageEventListener(NetworkMessageEventListener listener);    
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEventListener.java b/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkMessageEventListener.java
new file mode 100644 (file)
index 0000000..72508b2
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.net;
+
+/**
+ * Implemented by classes wanting to receive notifications of NetworkMessage events.
+ *
+ * @see NetworkMessage
+ * @see NetworkMessageEvent
+ * @see NetworkMessageEventGenerator
+ * @author TJ <hacker@iam.tj>
+ */
+public interface NetworkMessageEventListener {
+    public void NetworkMessageReceived(NetworkMessageEvent event);
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerAbstract.java b/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerAbstract.java
new file mode 100644 (file)
index 0000000..d6f9f2b
--- /dev/null
@@ -0,0 +1,486 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.net;
+
+import java.text.MessageFormat;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+import javax.swing.SwingWorker;
+
+/**
+ * Abstract dual-use multithreading network server that can be used stand-alone
+ * or in a Swing GUI application as a background worker thread.
+ * 
+ * Concrete classes are required to implement the Socket-specific functionality.
+ * 
+ * The arguments to the Generics superclass SwingWorker<T, V> are:
+ * 
+ *  < return-TYPE-of doInBackground(), publish(parameter-TYPE) >
+ * 
+ * Here doInBackground() returns an Integer connection counter and publish() takes
+ * a NetworkMessage type.
+ *
+ * Server sockets block in the operating system kernel waiting
+ * for connections or incoming packets.
+ *
+ * SwingWorker objects avoid using the GUI event dispatcher thread. Without that the
+ * user interface could be unresponsive for considerable periods whilst server
+ * sockets wait for incoming connections via the blocking in
+ * ServerSocket.accept() (TCP) or DatagramSocket.receive() (UDP) method.
+ *
+ * This design combines the multithreading support of the java.lang.Runnable
+ * interface with the javax.swing.SwingWorker inheritance so that this single class
+ * can be used in non-GUI daemon services and GUI applications, avoiding the need
+ * to write the same server code in more than one class.
+ * 
+ * The server registers NetworkMessageEventListener objects and notifies them
+ * when a new NetworkMessage has been received.
+ * 
+ * @see javax.swing.SwingWorker
+ * 
+ * @author TJ <hacker@iam.tj>
+ */
+public abstract class NetworkServerAbstract extends SwingWorker<Integer, NetworkMessage> implements NetworkMessageEventGenerator {
+
+    /**
+     * Single Logger for the class used by all object instances.
+     * 
+     * Can be instantiated once by objects of any sub-class.
+     */
+    @SuppressWarnings("NonConstantLogger")
+    protected static Logger LOGGER = null;
+
+    /**
+     * Inject simulated received NetworkMessages.
+     * 
+     * A helpful tool for debugging.
+     */
+    protected boolean _simulate = false;
+
+    /**
+     * Count of packets or connections received.
+     */
+    int _connectionCount;
+
+    /**
+     * Service name for this server instance.
+     * 
+     * E.g. "ServerSocial", "ServerChat", "ServerControl", "ClientControl", "ClientChat", "ServerLog"
+     */
+    String _title;
+
+    /**
+     * Socket parameters for this server.
+     */
+    WSYD_SocketAddress _socketAddress;
+
+    /**
+     * Thread safe First In, First Out Queue of NetworkMessage objects waiting to be sent.
+     * 
+     * Allows the Owner Thread to submit new messages for sending that the Worker Thread
+     * can safely access.
+     */
+    protected ConcurrentLinkedQueue<NetworkMessage> _sendMessageQueue = new ConcurrentLinkedQueue<>();
+
+    protected class LastSeenHost {
+        long timeInMillis;
+        InetSocketAddress address;
+        
+        LastSeenHost(InetSocketAddress address, long timeInMillis) {
+            this.address = address;
+            this.timeInMillis = timeInMillis;
+        }
+        LastSeenHost(InetSocketAddress host) {
+            this(host, System.currentTimeMillis());
+        }
+        
+    };
+    /**
+     * Maps service _title to its parent network host.
+     * <p>
+     * Used by methods on the Owner Thread to determine the list of valid service
+     * names it can submit messages to (by iterating the keys using keySet()).</p>
+     * <p>
+     * New service names can be added in two ways:<br/>
+     * <ol>
+     *  <li>by the Worker Thread from received messages</li>
+     *  <li>by the Owner or (other thread) from a service discovery helper (such as multicast discovery)</li>
+     * </ol>
+     */
+    protected ConcurrentHashMap<String, LastSeenHost> _serviceToHostMap = new ConcurrentHashMap<>();;
+
+    /**
+     * Wrapper for filtering NetworkMessageEvents based on the message intent
+     */
+    public class NetworkMessageEventListenerWithIntent {
+        String _intent;
+        NetworkMessageEventListener _listener;
+        
+        public NetworkMessageEventListenerWithIntent(NetworkMessageEventListener listener, String intent) {
+            _intent = intent;
+            _listener = listener;
+        }
+    }
+    protected ArrayList<NetworkMessageEventListenerWithIntent> _NetworkMessageEventListeners = new ArrayList<>();
+
+    /**
+     * 
+     * @param level message importance
+     * @param title source identifier
+     * @param formatter parameter Formatter for log message
+     * @param parameters variable length list of replaceable parameters for formatter
+     */
+    protected static void log(Level level, String title, String formatter, ArrayList<String> parameters) {
+        if (LOGGER == null)
+            return;
+        // formatter = "{" + Integer.toString(parameters.size()) + "}: " + formatter;
+        // parameters.add(title);
+        LOGGER.logp(level, title, null, MessageFormat.format(formatter, parameters.toArray()));
+    }
+    /**
+     * 
+     * @param level message importance
+     * @param title source identifier
+     * @param message the log entry
+     */
+    protected static void log(Level level, String title, String message) {
+        if (LOGGER == null)
+            return;
+        LOGGER.logp(level, title, null, MessageFormat.format("{1}", message));
+    }
+
+    /**
+     * Set the log level for the server
+     * @param level a new log level
+     * @return the old log level
+     */
+    public Level setLogLevel(Level level) {
+        Level result = Level.OFF;
+        if (LOGGER != null) {
+            Level temp = LOGGER.getLevel();
+            LOGGER.setLevel(level);
+            result = temp;
+        }
+        return result;
+    }
+
+    /**
+     * Default constructor.
+     */
+    NetworkServerAbstract() {
+        this._connectionCount = 0;
+        this._title = null;
+        this._socketAddress = null;
+    }
+    
+    /**
+     * Construct the server with a Logger.
+     * 
+     * No socket is opened.
+     * 
+     * @param socketAddress The socket to listen on
+     * @param title source identifier for use in log messages and sent NetworkMessage objects
+     * @param logger An instance of Logger to be used by all objects of this class
+     */
+    public NetworkServerAbstract(WSYD_SocketAddress socketAddress, String title, Logger logger) {
+        this._connectionCount = 0;
+        this._title = title;
+        this._socketAddress = socketAddress;
+        if (LOGGER == null) // do not replace existing logger reference
+            LOGGER = logger;
+    }
+
+    /**
+     * Construct the server without a Logger.
+     * 
+     * No socket is opened.
+     * 
+     * @param socketAddress The socket to listen on
+     * @param title source identifier for use in log messages and sent NetworkMessage objects
+     */
+    public NetworkServerAbstract(WSYD_SocketAddress socketAddress, String title) {
+        this(socketAddress, title, null);
+    }
+
+    /**
+     * Enable or disable simulated received packet injection.
+     * 
+     * @param simulate true to simulate received messages
+     */
+    public void setSimulate(boolean simulate) {
+        this._simulate = simulate;
+    }
+
+    /**
+     * Get the simulation state.
+     * 
+     * @return true if simulation is enabled.
+     */
+    public boolean getSimulate() {
+        return this._simulate;
+    }
+
+
+    /* XXX: The following Methods execute on the background Worker Thread */
+    
+    /**
+     * The primary SwingWorker method, started on the Worker Thread when the Owner
+     * Thread calls execute().
+     * 
+     * Loops until isCancelled() == true. Within the loop calls serverListen() to
+     * allow reception of one packet or connection and if so counts it.
+     * Then  it checks if there are any messages to be sent out and if so calls
+     * serverSend().
+     * 
+     * @return the number of connections accepted
+     */
+    @Override
+    public Integer doInBackground() {
+        ArrayList<String> logMessages = new ArrayList<>();
+        try {
+            logMessages.add(_socketAddress.toString());
+            log(Level.INFO, _title, "Opening socket {0}", logMessages);
+            this.serverOpen();
+        }
+        catch(SocketException e) {
+            logMessages.clear();
+            logMessages.add(_socketAddress.getAddress().toString());
+            logMessages.add(Integer.toString(_socketAddress.getPort()));
+            logMessages.add(_socketAddress.getProtocol().toString());
+            log(Level.SEVERE, _title, "{0}: Unable to open socket on {1}:{2} {3}", logMessages);
+        }
+        
+        // unless cancelled keep waiting for new packets or connections
+        while (!this.isCancelled()) {
+            if (this.serverListen())
+                this._connectionCount++;
+
+            // send a queued message
+            NetworkMessage temp =  this.dequeueMessage();
+            if (temp != null) {
+                if (!this.serverSend(temp)) {
+                    logMessages.clear();
+                    logMessages.add(temp.getSender());
+                    logMessages.add(temp.getTarget());
+                    log(Level.WARNING, _title, "Unable to send message from {0} to {1}", logMessages);
+                }
+            }
+        }
+     
+        try {
+            logMessages.clear();
+            logMessages.add(_socketAddress.toString());
+            log(Level.INFO, _title, "Closing socket {0}", logMessages);
+            this.serverClose();
+        }
+        catch(SocketException e) {
+            logMessages.clear();
+            logMessages.add(_socketAddress.getAddress().toString());
+            logMessages.add(Integer.toString(_socketAddress.getPort()));
+            logMessages.add(_socketAddress.getProtocol().toString());
+            log(Level.SEVERE, _title, "{0}: Unable to close socket on {1}:{2} {3}", logMessages);
+        }
+        
+        return this._connectionCount;
+    }
+
+
+    /**
+     * Open the socket ready for accepting data or connections.
+     * 
+     * It should also set a reasonable socket timeout with a call to setSoTimeout()
+     * 
+     * @see java.net.ServerSocket#setSoTimeout
+     * @see java.net.DatagramSocket#setSoTimeout
+     * @throws SocketException 
+     */
+    public abstract void serverOpen() throws SocketException;
+    
+    /**
+     * Close the socket.
+     * 
+     * @throws SocketException
+     */
+    public abstract void serverClose() throws SocketException;
+    
+    /**
+     * Send an unsolicited message to a remote service.
+     * 
+     * This method is called by the main worker loop if there is a message to
+     * be sent.
+     * 
+     * @param message must have its _serviceTarget parameter set
+     * @return true if the message was sent
+     */
+    protected abstract boolean serverSend(NetworkMessage message);
+
+    /**
+     * Accept packet or connection from remote hosts.
+     * 
+     * This method must wait for a single incoming connection or packet, process it,
+     * and then publish() it for consumption by process().
+     * 
+     * It must add newly seen remote service names to _serviceToHostMap so that
+     * methods on the Owner Thread can discover the destination service titles
+     * they can use in new NetworkMessage submissions.
+     * 
+     * @return true if the server should continue listening
+     */
+    public abstract boolean serverListen();
+
+    /**
+     * Removes a message from the queue of pending messages.
+     *
+     * This method is called on the Worker Thread by the doInBackground() main loop.
+     *
+     * @return a message to be sent
+     */
+    protected NetworkMessage dequeueMessage() {
+        return this._sendMessageQueue.poll();
+    }
+    
+    /* XXX: Methods below here all execute on the GUI Event Dispatch Thread */
+
+
+    /**
+     * Fetch messages received by the server.
+     * 
+     * For delivery to event listeners; usually Swing GUI components. This method will run on the
+     * Owner Thread so must complete quickly it that is the GUI Event Dispatch Thread.
+     * 
+     * @param list messages received and queued
+     */
+    @Override
+    protected void process(List<NetworkMessage> list) {
+        for (NetworkMessage message: list) {
+            fireNetworkMessageEvent(message);
+        }
+    }
+
+    /**
+     * Clean up after doInBackground() has returned.
+     * 
+     * This method will run on the GUI Event Dispatch Thread so must complete quickly.
+     */
+    @Override
+    protected abstract void done();
+
+
+    /**
+     * Ensure service is in the map of known hosts.
+     * @param service the service name to check
+     * @return true is the target service is known
+     */
+    protected boolean isServiceValid(String service) {
+        return this._serviceToHostMap.containsKey(service);
+    }
+
+    /**
+     * Adds a message to the queue of pending messages.
+     * 
+     * This method will usually be called from the Owner Thread.
+     * 
+     * @param message to be sent
+     * @return true if the message was added to the queue
+     * @throws IllegalArgumentException if the target does not exist in the serviceToHost mapping
+     */
+    public boolean queueMessage(NetworkMessage message) throws IllegalArgumentException {
+        boolean result = false;
+        if (message != null) {
+            // ensure the target is set and is a valid service
+            String target = message.getTarget();
+            if (target == null)
+                throw new IllegalArgumentException("target cannot be null");
+            if(!isServiceValid(target))
+                throw new IllegalArgumentException("target service does not exist: " + target);
+            
+            NetworkMessage temp;
+            try { // make a deep clone of the message
+                temp = NetworkMessage.clone(message);
+                result = this._sendMessageQueue.add(temp);
+            } catch (CloneNotSupportedException e) {
+                // TODO: queueMessage() log CloneNotSupportedException
+                e.printStackTrace();
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Add a NetworkMessageEvent listener.
+     * 
+     * Listens to all intents.
+     * 
+     * @param listener 
+     */
+    @Override
+    public synchronized void addNetworkMessageEventListener(NetworkMessageEventListener listener) {
+        _NetworkMessageEventListeners.add(new NetworkMessageEventListenerWithIntent(listener, null));
+    }
+
+    /**
+     * Add a filtered NetworkMessageEvent listener.
+     * 
+     * Filters on the intent of the NetworkMessage.
+     * @param listener
+     * @param intent null to listen to all intents, otherwise the intent to listen for
+     */
+    @Override
+    public synchronized void addNetworkMessageEventListener(NetworkMessageEventListener listener, String intent) {
+        _NetworkMessageEventListeners.add(new NetworkMessageEventListenerWithIntent(listener, intent));        
+    }
+
+    /**
+     * Remove a NetworkMessageEvent listener.
+     * 
+     * @param listener 
+     */
+    @Override
+    public synchronized void removeNetworkMessageEventListener(NetworkMessageEventListener listener) {
+        for (NetworkMessageEventListenerWithIntent intentListener : _NetworkMessageEventListeners)
+            if (intentListener._listener == listener)
+                _NetworkMessageEventListeners.remove(intentListener);
+    }
+    
+    /**
+     * Send a NetworkMessageEvent to all listeners.
+     * 
+     * Only sends the message to listeners registered for the same intent, or for all messages.
+     * 
+     * @param message the NetworkMessage to send
+     */
+    private synchronized void fireNetworkMessageEvent(NetworkMessage message) {
+        NetworkMessageEvent event = new NetworkMessageEvent(this, message);
+        for (NetworkMessageEventListenerWithIntent intentListener : _NetworkMessageEventListeners) {
+            if (intentListener._intent.equals(message._intent) || intentListener._intent == null)
+                intentListener._listener.NetworkMessageReceived(event);
+        }
+    }
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerTCP.java b/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerTCP.java
new file mode 100644 (file)
index 0000000..6c48624
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.net;
+
+import java.net.ServerSocket;
+import java.net.SocketException;
+
+/**
+ * Dual-use multithreading network TCP server that can be used stand-alone
+ * or in a Swing GUI application as a background worker thread.
+ *
+ * @see NetworkServerAbstract
+ * @author TJ <hacker@iam.tj>
+ */
+public class NetworkServerTCP extends NetworkServerAbstract {
+    ServerSocket _serverSocket;
+
+    /**
+     * Open the socket ready for accepting connections.
+     * 
+     * It should also set a reasonable socket timeout with a call to setSoTimeout()
+     * 
+     * @throws SocketException 
+     */
+    @Override
+    public  void serverOpen() throws SocketException {
+        
+    }
+    
+    /**
+     * Send an unsolicited message to a remote service.
+     * 
+     * This method is called by the main worker loop if there is a message to
+     * be sent.
+     * 
+     * @param message must have its _serviceTarget parameter set
+     * @return true if the message was sent
+     */
+    @Override
+    protected boolean serverSend(NetworkMessage message) {
+        boolean result = false;
+        
+        return result;
+    }
+
+    /**
+     * Close the socket.
+     * 
+     * @throws SocketException
+     */
+    @Override
+    public void serverClose() throws SocketException {
+        
+    }
+    
+    /**
+     * Accept connection from remote hosts.
+     * 
+     * This method should wait for a single incoming connection.
+     * 
+     * @return true if the server should continue listening
+     */
+    @Override
+    public boolean serverListen() {
+        boolean result = false;
+        
+        return false;
+    }
+
+    /* XXX: Methods below here all execute on the GUI Event Dispatch Thread */
+    
+    /**
+     * Clean up after doInBackground() has returned.
+     * 
+     * This method will run on the GUI Event Dispatch Thread so must complete quickly.
+     */
+    @Override
+    protected  void done() {
+        
+    }
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerUDP.java b/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerUDP.java
new file mode 100644 (file)
index 0000000..a40ea55
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.net;
+
+import java.text.MessageFormat;
+import java.io.IOException;
+import java.io.ObjectStreamException;
+import java.net.InetSocketAddress;
+import java.net.DatagramSocket;
+import java.net.DatagramPacket;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.LogRecord;
+import uk.ac.ntu.n0521366.wsyd.libs.message.MessageLogRecord;
+
+
+
+/**
+ * Dual-use multithreading network UDP server that can be used stand-alone
+ * or in a Swing GUI application as a background worker thread.
+ *
+ * @see NetworkServerAbstract
+ * @author TJ <hacker@iam.tj>
+ */
+public class NetworkServerUDP extends NetworkServerAbstract {
+    /**
+     * Server socket.
+     */
+    private DatagramSocket _datagramSocket = null;
+    
+    /**
+     * Maximum size of UDP packet payload
+     */
+    public static final int UDP_PAYLOAD_SIZE_MAX =  65507;
+    
+    /**
+     * Construct the server with a Logger.
+     * 
+     * No socket is opened.
+     * 
+     * @param socketAddress The socket to listen on
+     * @param title source identifier for use in log messages and sent NetworkMessage objects
+     * @param logger An instance of Logger to be used by all objects of this class
+     */
+    public NetworkServerUDP(WSYD_SocketAddress socketAddress, String title, Logger logger) {
+        super(socketAddress, title, logger);
+    }
+
+    /**
+     * Construct the server without a Logger.
+     * 
+     * No socket is opened.
+     * 
+     * @param socketAddress The socket to listen on
+     * @param title source identifier for use in log messages and sent NetworkMessage objects
+     */
+    public NetworkServerUDP(WSYD_SocketAddress socketAddress, String title) {
+        super(socketAddress, title);
+    }
+
+    /**
+     * Get the DatagramSocket for this service.
+     * 
+     * @return the socket
+     */
+    public DatagramSocket getSocket() {
+        return this._datagramSocket;
+    }
+
+    /**
+     * Open the socket ready for accepting packets.
+     * 
+     * It should also set a reasonable socket timeout with a call to setSoTimeout()
+     * to prevent unnecessary blocking.
+     * 
+     * @throws SocketException 
+     */
+    @Override
+    public  void serverOpen() throws SocketException {
+        _datagramSocket = new DatagramSocket(_socketAddress.getPort(), _socketAddress.getAddress());
+        _datagramSocket.setSoTimeout(100); // 1/10th second blocking timeout on receive() 
+    }
+    
+    /**
+     * Close the socket.
+     * 
+     * @throws SocketException
+     */
+    @Override
+    public void serverClose() throws SocketException {
+        // use 'this' to ensure sub-classes refer to their own '_datagramSocket' when inheriting this method
+        if (this._datagramSocket != null)
+            this._datagramSocket.close();
+    }
+
+    /**
+     * Accept packet from remote hosts.
+     * 
+     * 
+     * 
+     * @return true if the server should continue listening
+     */
+    @Override
+    public boolean serverListen() {
+        boolean result = false;
+        byte[] dataReceive = new byte[UDP_PAYLOAD_SIZE_MAX];
+        DatagramPacket packetReceive = new DatagramPacket(dataReceive, dataReceive.length);
+        NetworkMessage messageReceived;
+        try {
+            /* blocks waiting for packet until socket timeout expires, when SocketTimeOut
+             * exception is thrown.
+            */
+            if (this.getSocket() != null)
+                this.getSocket().receive(packetReceive);
+            else
+                throw new SocketTimeoutException("No socket available!");
+            
+            // packet was received
+            messageReceived = NetworkMessage.deserialize(packetReceive.getData());
+            
+            // prevent loopbacks
+            if (!messageReceived.getSender().equals(_title)) {
+            
+                // add or update the last-seen time of the Sender host in the known services map
+                LastSeenHost host = new LastSeenHost((InetSocketAddress)packetReceive.getSocketAddress());
+                this._serviceToHostMap.replace(messageReceived.getSender(), host);
+
+                /* TODO: serverListen() message may be an ACKnowledgement which shouldn't be pubished...
+                 * But it might be useful to keep track of ACKs in a separate queue that the owner thread can query to check
+                 * a message was delivered
+                 */
+                // pass the message to the process() method in the Owner Thread
+                publish(messageReceived);
+
+                // create and send an acknowledgement of receipt
+                NetworkMessage ack = NetworkMessage.createNetworkMessage("ACK", messageReceived.getSender(), null);
+                serverSend(ack);
+
+                // FIXME: serverListen() can this block be deleted now its handled by serverSend() ?
+                /*
+                 try {
+                    byte[] dataAck = NetworkMessage.serialize(ack);
+                    DatagramPacket packetAck = new DatagramPacket(dataAck, dataAck.length);
+
+                    // extract remote host address and port for sending acknowledgement
+                    packetAck.setAddress(packetReceive.getAddress());
+                    packetAck.setPort(packetReceive.getPort());
+
+                    // acknowledge receipt
+                    this.getSocket().send(packetAck);
+
+
+                } catch (IOException e) {
+                    // TODO: serverListen() fill in IOException blank log message
+                    log(Level.WARNING, _title, "", null);
+                }
+                */
+
+                result = true; // successful
+            }
+
+        } catch (SocketTimeoutException e) {
+            result = false; // no packet received
+            if (this._simulate) {
+                LogRecord record = new LogRecord(Level.FINEST, "Simulated received message");
+                record.setSourceClassName("Simulator");
+                record.setMillis(System.currentTimeMillis());
+                MessageLogRecord m = new MessageLogRecord(record);
+                publish(new NetworkMessage("Log","Simulator",m));
+                result = true;
+            }
+            
+        } catch (ObjectStreamException e) {
+            /* order of these Exception catches is important
+             * Deeper sub-classes must be listed before their super classes
+             */
+            // TODO: serverListen() add ObjectStreamException handler
+
+        } catch (IOException e) {
+            // TODO: serverListen() add IOException handler
+
+        } catch (ClassNotFoundException e) {
+            // TODO: serverListen() add ClassNotFoundException handler
+        }
+
+        return result;
+    }
+
+    /**
+     * Send an unsolicited message to a remote service.
+     * 
+     * This method is called by the main worker loop if there is a message to
+     * be sent.
+     * 
+     * @param message must have its _serviceTarget parameter set
+     * @return true if the message was sent
+     */
+    @Override
+    protected boolean serverSend(NetworkMessage message) {
+        boolean result = false;
+
+        if (message != null) {
+            LastSeenHost host = _serviceToHostMap.get(message.getTarget());
+            if (host != null) {
+                InetSocketAddress address = host.address;
+                if (address != null) {
+                    message.setSender(_title);
+                    try {
+                        byte[] dataSend = NetworkMessage.serialize(message);
+                        DatagramPacket packetSend = new DatagramPacket(dataSend, dataSend.length);
+                        // set target's remote host address and port
+                        packetSend.setAddress(address.getAddress());
+                        packetSend.setPort(address.getPort());
+
+                        // acknowledge receipt
+                        this.getSocket().send(packetSend);
+
+                        result = true; // successful
+                    } catch (IOException e) {
+                        // TODO: serverSend() add IOException handler
+                        e.printStackTrace();
+                    }
+                }
+            } else {
+                log(Level.WARNING, _title, MessageFormat.format("Unable to send message for \"{0}\" to unknown target \"{1}\"", message.getIntent(), message.getTarget()));
+            }
+        }
+        return result;
+    }
+
+    /* XXX: Methods below here all execute on the GUI Event Dispatch Thread */
+    
+    /**
+     * Clean up after doInBackground() has returned.
+     * 
+     * This method will run on the Owner Thread so must complete quickly.
+     */
+    @Override
+    protected  void done() {
+        // TODO: done() implement any clean-up after doInBackground() has returned
+    }
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerUDPMulticast.java b/src/uk/ac/ntu/n0521366/wsyd/libs/net/NetworkServerUDPMulticast.java
new file mode 100644 (file)
index 0000000..28c3f4f
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.net;
+
+import java.io.IOException;
+import java.net.MulticastSocket;
+import java.net.SocketException;
+import java.net.NetworkInterface;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+/**
+ *
+ * @author TJ <hacker@iam.tj>
+ */
+public class NetworkServerUDPMulticast extends NetworkServerUDP {
+    
+    /**
+     * the Multicast socket.
+     * 
+     * Deliberately hides the DatagramSocket in NetworkServerUDP so that inherited methods
+     * can access the same name.
+     */
+    private MulticastSocket _multicastSocket = null;
+    /**
+     * Construct the server with a Logger.
+     * 
+     * No socket is opened.
+     * 
+     * @param socketAddress The socket to listen on
+     * @param title source identifier for use in log messages and sent NetworkMessage objects
+     * @param logger An instance of Logger to be used by all objects of this class
+     */
+    public NetworkServerUDPMulticast(WSYD_SocketAddress socketAddress, String title, Logger logger) {
+        super(socketAddress, title, logger);
+        // permit broadcasting to pseudo-host 'all' since this is multicast
+        this._serviceToHostMap.put("all", new LastSeenHost(socketAddress.getSocketAddress()));
+    }
+
+    /**
+     * Construct the server without a Logger.
+     * 
+     * No socket is opened.
+     * 
+     * @param socketAddress The socket to listen on
+     * @param title source identifier for use in log messages and sent NetworkMessage objects
+     */
+    public NetworkServerUDPMulticast(WSYD_SocketAddress socketAddress, String title) {
+        super(socketAddress, title);
+    }
+
+    /**
+     * Get the MulticastSocket object for this service.
+     * 
+     * @return the base-class DatagramSocket type
+     */
+    @Override
+    public java.net.DatagramSocket getSocket() {
+        return this._multicastSocket;
+    }
+    /**
+     * Join the multicast group on all interfaces ready for accepting packets.
+     * 
+     * It should also set a reasonable socket timeout with a call to setSoTimeout()
+     * to prevent unnecessary blocking.
+     * 
+     * @throws SocketException 
+     */
+    @Override
+    public  void serverOpen() throws SocketException {
+        try {
+            ArrayList<String> messages = new ArrayList<>();
+
+            this._multicastSocket = new MulticastSocket(_socketAddress.getPort());
+            this._multicastSocket.setTimeToLive(2); // don't traverse more than 2 routers
+            this._multicastSocket.setSoTimeout(100); // 1/10th second blocking timeout on receive()
+            this._multicastSocket.setLoopbackMode(true); // inverted logic; true == disable. Don't want to receive our own sent packets
+            
+            Enumeration<NetworkInterface> ifs = NetworkInterface.getNetworkInterfaces();
+            while (ifs.hasMoreElements()) {
+                NetworkInterface iface = ifs.nextElement();
+                messages.clear();
+                messages.add(iface.getName());
+                messages.add(Boolean.toString(iface.supportsMulticast()));
+                log(Level.INFO, _title, "Interface {0}: probe multicast support: {1}", messages);
+                if (iface.supportsMulticast()) {
+                    try {
+                        messages.clear();
+                        messages.add(iface.getName());
+                        messages.add(_socketAddress.getSocketAddress().getAddress().toString());
+                        this._multicastSocket.joinGroup(_socketAddress.getSocketAddress(), iface);
+                    log(Level.INFO, _title, "Interface {0}: joined multicast group {1}", messages);
+                    } catch (Exception e) {
+                        messages.clear();
+                        messages.add(iface.getName());
+                        log(Level.SEVERE, _title, "Interface {0}: failed to join multicast group", messages);
+                    }
+                }
+            }
+        } catch (IOException e) {
+            log(Level.SEVERE, _title, "Failed to open multicast socket");
+        }
+    }
+
+    /**
+     * Close the socket.
+     * 
+     * @throws SocketException
+     */
+    @Override
+    public void serverClose() throws SocketException {
+        if (this._multicastSocket != null) {
+            try {
+                log(Level.INFO, _title, "Leaving multicast group");
+                this._multicastSocket.leaveGroup(_socketAddress.getAddress());
+                this._multicastSocket.close();
+            } catch (IOException e) {
+                log(Level.SEVERE, _title, "failed to leave multicast group");
+            }
+            
+        }
+    }
+
+    /**
+     * Remove stale service records.
+     * 
+     * @param ageInMillis milliseconds since last seen to be considered stale
+     * @return quantity of records removed
+     */
+    public ArrayList<String> cleanServiceToHostMap(long ageInMillis) {
+        ArrayList<String> result =  new ArrayList<>();
+        long expireTime = System.currentTimeMillis() - ageInMillis;
+        java.util.Enumeration<String> keys = this._serviceToHostMap.keys();
+        while (keys.hasMoreElements()) {
+            String key = keys.nextElement();
+
+            // XXX: special handling for "all" target - never remove it
+            if (!key.equals("all")) {
+                LastSeenHost host = _serviceToHostMap.get(key);
+                if (host != null) {
+                    if (host.timeInMillis < expireTime) {
+                        if (_serviceToHostMap.remove(key, host)) {
+                            result.add(key);
+                            ArrayList<String> messages = new ArrayList<>();
+                            messages.add(key);
+                            log(Level.INFO, _title, "Removed \"{0}\" from service map", messages);
+                        }
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/net/WSYD_SocketAddress.java b/src/uk/ac/ntu/n0521366/wsyd/libs/net/WSYD_SocketAddress.java
new file mode 100644 (file)
index 0000000..70adf4b
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.net;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.io.IOException;
+import java.text.MessageFormat;
+import uk.ac.ntu.n0521366.wsyd.libs.net.Network;
+
+
+/**
+ * Network socket properties.
+ * 
+ * Encapsulates java.net.InetSocketAddress (which cannot be usefully overridden)
+ * and adds additional attributes for the IP protocol, multicast, and isReachable
+ * properties of a socket.
+ * 
+ * This class doesn't own or control a socket object, only the attributes needed
+ * to create the socket.
+ * 
+ * Serializable to allow passing between separate local and remote processes
+ *
+ * @author TJ <hacker@iam.tj>
+ */
+public class WSYD_SocketAddress implements java.io.Serializable {
+    Boolean _multicast;
+    Boolean _isReachable;
+    Protocol _protocol;
+    InetSocketAddress _socketAddress;
+    
+    public static enum Protocol {TCP, UDP}
+
+    public static byte[] stringIPv4ToBytes4(String ipv4) throws IllegalArgumentException {
+        byte[] result = new byte[4];
+        if (ipv4 == null)
+            throw new IllegalArgumentException("reference cannot be null");
+        
+        String[] elements = ipv4.split("\\.");
+        if (elements.length != 4)
+            throw new IllegalArgumentException(MessageFormat.format("Not a correct dotted decimal notation: {0}", ipv4));
+        
+        int i = 0;
+        for (int index = 0; index <=3; index++) {
+            try {
+                i = Integer.parseInt(elements[index]);
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException(MessageFormat.format("Not a decimal number: {0}", elements[index]));
+            }
+            if (i < 0)
+                throw new IllegalArgumentException(MessageFormat.format("Value must not be negative: {0}", i));
+            if (i > 255)
+                throw new IllegalArgumentException(MessageFormat.format("Value must not be greater than 255: {0}", i));
+            result[index] = (byte) i;
+        }
+        
+        return result;
+    }
+
+    /**
+     * Default constructor.
+     */
+    public WSYD_SocketAddress() {
+        _multicast = false;
+        _isReachable = false;
+        _protocol = Protocol.TCP;
+        _socketAddress = null;
+    }
+
+    /**
+     * Construct a viable Socket.
+     * 
+     * @param address host IP address
+     * @param port number
+     * @param protocol network protocol (TCP or UDP)
+     */
+    public WSYD_SocketAddress(InetAddress address, int port, Protocol protocol) {
+        this(); // call the default constructor
+        _protocol = protocol;
+        setPort(port);
+        setAddress(address); // must be done last since it calls setReachable()
+    }
+
+    /**
+     * Construct a viable TCP Socket.
+     * 
+     * TCP is selected as the default protocol.
+     * 
+     * @param address host IP address
+     * @param port number
+     */
+    public WSYD_SocketAddress(InetAddress address, int port) {
+        this(address, port, Protocol.TCP); // call primary constructor with default parameter
+    }
+    
+    /**
+     * Construct a viable TCP Socket on an ephemeral port.
+     * 
+     * TCP is selected as the default protocol. An ephemeral port is allocated
+     * by the operating system.
+     * 
+     * @param address host IP address
+     */
+    public WSYD_SocketAddress(InetAddress address) {
+        this(address, 0, Protocol.TCP);
+    }
+    
+    /**
+     * Construct a viable Socket on all interfaces.
+     *
+     * The wildcard IP address is used to listen on all available interfaces.
+     *
+     * @see java.net.ServerSocket#bind
+     * @param port number
+     * @param protocol Protocol.TCP or Protocol.UDP
+     * @throws UnknownHostException 
+     */
+    public WSYD_SocketAddress(int port, Protocol protocol) throws UnknownHostException {
+        this(Network.IPv4_WILDCARD, port, protocol);
+    }
+    
+    /**
+     * Construct a viable TCP Socket on all interfaces.
+     * 
+     * TCP is selected as the default protocol. The wildcard IP address is
+     * used.
+     * 
+     * @see java.net.ServerSocket#bind
+     * @param port number
+     * @throws UnknownHostException
+     */
+    public WSYD_SocketAddress(int port) throws UnknownHostException {
+        this(Network.IPv4_WILDCARD, port, Protocol.TCP);
+    }
+    
+    /**
+     * Set IP Address and Port.
+     * 
+     * @param address IP address
+     * @param port number
+     */
+    public void setSocketAddress(InetAddress address, int port) {
+        _socketAddress = new InetSocketAddress(address, port);
+        setMulticast();
+        setReachable();
+    }
+
+    /**
+     * Get the SocketAddress.
+     * 
+     * @return the address and port
+     */
+    public InetSocketAddress getSocketAddress() {
+        return _socketAddress;
+    }
+
+    /**
+     * Set the IP address.
+     * 
+     * Use the current port, or if no address is currently assigned, pick an ephemeral port.
+     * 
+     * @param address IP address
+     */
+    public final void setAddress(InetAddress address) {
+        // keep existing port number
+        int tempPort = (_socketAddress == null) ? 0 : _socketAddress.getPort();
+        setSocketAddress(address, tempPort);
+    }
+    
+    /**
+     * Get the Socket IP address.
+     * 
+     * @return the IP address
+     */
+    public InetAddress getAddress() {
+        return _socketAddress.getAddress();
+    }
+    
+    /**
+     * Set the Socket port.
+     * 
+     * Use the current InetAddress, or if no address is currently assigned, user the wildcard address.
+     * 
+     * @param port  0 requests an ephemeral port
+     * @throws IllegalArgumentException
+     */
+    public final void setPort(int port) throws IllegalArgumentException {
+        // keep existing InetAddress
+        InetAddress tempAddress = (_socketAddress == null) ?  Network.IPv4_WILDCARD : _socketAddress.getAddress();
+        setSocketAddress(tempAddress, port);
+    }
+    
+    /**
+     * Get the Socket port.
+     * 
+     * @return port number
+     */
+    public int getPort() {
+        return _socketAddress.getPort();
+    }
+
+    /**
+     * Set the IP protocol to use.
+     * 
+     * @param protocol Protocol.TCP or Protocol.UDP
+     */
+    public void setProtocol(Protocol protocol) {
+        _protocol = protocol;
+        setMulticast(); // if it's TCP then it can't be multicast, so update multicast flag
+    }
+    
+    /**
+     * Get the current protocol
+     * 
+     * @return Protocol.TCP or Protocol.UDP
+     */
+    public Protocol getProtocol() {
+        return _protocol;
+    }
+    
+    /**
+     * Set Multicast property if the address is <em>Organisation Local</em>.
+     * 
+     * This is valid for both IPv4 and IPv6 addresses.
+     */
+    private void setMulticast() {
+        _multicast = false; // start with the assumption this isn't multicast
+        if (_protocol == Protocol.UDP) {
+            if (_socketAddress != null) {
+                // only use Site Local MultiCast addresses
+                _multicast = _socketAddress.getAddress().isMCOrgLocal();
+            }
+        }
+    }
+    
+    /**
+     * Get the Multicast status of this Socket.
+     * 
+     * @return true if the address is Multicast
+     */
+    public boolean isMulticast() {
+        return _multicast;
+    }
+
+    /**
+     * Test the route to the host (but not the port) if the
+     * address isn't Multicast.
+     * 
+     * @return true if the host is reachable
+     */
+    protected boolean setReachable() {
+        if (!_multicast) {
+            try {
+                _isReachable = _socketAddress.getAddress().isReachable(1000);
+            }
+            catch(IOException e) {
+            }
+        }        
+        return _isReachable;
+    }
+    
+    /**
+     * Get the result of the last isReachable() test but don't do a test now.
+     * 
+     * @return true if the host was reachable last time it was tested
+     */
+    public boolean getReachable() {
+        return _isReachable;
+    }
+    
+    /**
+     * Constructs a string representation of this Socket.
+     * 
+     * @return String representation of the Socket state
+     */
+    @Override
+    public final String toString() {
+        return "Address: " + _socketAddress.getAddress() + ", "
+             + "Port: " + _socketAddress.getPort() + ", "
+             + "Protocol: " + _protocol.toString() + ", "
+             + "MultiCast: " + _multicast.toString();
+    }   
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/libs/net/problem.txt b/src/uk/ac/ntu/n0521366/wsyd/libs/net/problem.txt
new file mode 100644 (file)
index 0000000..9e20b7e
--- /dev/null
@@ -0,0 +1,136 @@
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+queueMessage(): sendMessageQueue.size()=0
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+queueMessage(): NetworkMessage=NetworkMessage@322b6170
+queueMessage(): sendMessageQueue.size()=1
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=1
+deQueueMessage() poll()=NetworkMessage@322b6170
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+queueMessage(): sendMessageQueue.size()=0
+queueMessage(): NetworkMessage=NetworkMessage@2ed278f5
+queueMessage(): sendMessageQueue.size()=1
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+queueMessage(): sendMessageQueue.size()=1
+queueMessage(): NetworkMessage=NetworkMessage@f2f8f5f
+queueMessage(): sendMessageQueue.size()=2
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+queueMessage(): sendMessageQueue.size()=2
+queueMessage(): NetworkMessage=NetworkMessage@23a8013f
+queueMessage(): sendMessageQueue.size()=3
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+queueMessage(): sendMessageQueue.size()=3
+queueMessage(): NetworkMessage=NetworkMessage@39398dae
+queueMessage(): sendMessageQueue.size()=4
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+queueMessage(): sendMessageQueue.size()=4
+queueMessage(): NetworkMessage=NetworkMessage@269b8c6c
+queueMessage(): sendMessageQueue.size()=5
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
+dequeueMessage(): sendMessageQueue.size()=0
+deQueueMessage() poll()=null
\ No newline at end of file
diff --git a/src/uk/ac/ntu/n0521366/wsyd/management/ServerManagement.form b/src/uk/ac/ntu/n0521366/wsyd/management/ServerManagement.form
new file mode 100644 (file)
index 0000000..c1ed78a
--- /dev/null
@@ -0,0 +1,290 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
+  <NonVisualComponents>
+    <Component class="javax.swing.ButtonGroup" name="buttonGroup1">
+    </Component>
+    <Container class="javax.swing.JDialog" name="gDialogAbout">
+      <Properties>
+        <Property name="title" type="java.lang.String" value="About"/>
+        <Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
+          <Color blue="ff" green="ff" red="ff" type="rgb"/>
+        </Property>
+        <Property name="iconImage" type="java.awt.Image" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
+          <Connection code="new ImageIcon( getClass().getResource(resourcePath +&quot;/ScroogledKeepCalmMug.icon.png&quot;)).getImage()" type="code"/>
+        </Property>
+        <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+          <Dimension value="[590, 340]"/>
+        </Property>
+        <Property name="resizable" type="boolean" value="false"/>
+      </Properties>
+
+      <Layout class="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout">
+        <Property name="useNullLayout" type="boolean" value="true"/>
+      </Layout>
+      <SubComponents>
+        <Component class="javax.swing.JTextArea" name="gTextAreaAbout">
+          <Properties>
+            <Property name="editable" type="boolean" value="false"/>
+            <Property name="columns" type="int" value="20"/>
+            <Property name="rows" type="int" value="5"/>
+            <Property name="text" type="java.lang.String" value="Server Management client&#xa;&#xa9; Copyright 2015 TJ &lt;hacker@iam.tj&gt;&#xa;&#xa;&lt;&lt;&lt; Click on the mug to learn more!&#xa;&#xa;I will disappear in 20 seconds.&#xa;&#xa;"/>
+            <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+              <Border info="null"/>
+            </Property>
+          </Properties>
+          <Constraints>
+            <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout" value="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout$AbsoluteConstraintsDescription">
+              <AbsoluteConstraints x="310" y="30" width="270" height="200"/>
+            </Constraint>
+          </Constraints>
+        </Component>
+        <Component class="javax.swing.JButton" name="gBtnAbout">
+          <Properties>
+            <Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
+              <Color blue="ff" green="ff" red="ff" type="rgb"/>
+            </Property>
+            <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
+              <Font name="Dialog" size="18" style="1"/>
+            </Property>
+            <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
+              <Image iconType="3" name="/uk/ac/ntu/n0521366/wsyd/resources/ScroogledKeepCalmMug.png"/>
+            </Property>
+            <Property name="text" type="java.lang.String" value="We Stealz Your Dataz"/>
+            <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+              <Border info="null"/>
+            </Property>
+            <Property name="borderPainted" type="boolean" value="false"/>
+            <Property name="contentAreaFilled" type="boolean" value="false"/>
+            <Property name="defaultCapable" type="boolean" value="false"/>
+            <Property name="focusPainted" type="boolean" value="false"/>
+            <Property name="rolloverEnabled" type="boolean" value="false"/>
+            <Property name="verticalAlignment" type="int" value="1"/>
+            <Property name="verticalTextPosition" type="int" value="1"/>
+          </Properties>
+          <Events>
+            <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="AboutAction"/>
+          </Events>
+          <Constraints>
+            <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout" value="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout$AbsoluteConstraintsDescription">
+              <AbsoluteConstraints x="0" y="0" width="530" height="-1"/>
+            </Constraint>
+          </Constraints>
+        </Component>
+      </SubComponents>
+    </Container>
+    <Component class="javax.swing.JFileChooser" name="gFileChooser">
+      <Properties>
+        <Property name="dialogType" type="int" value="1"/>
+        <Property name="currentDirectory" type="java.io.File" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
+          <Connection code="new File(System.getProperty(&quot;user.dir&quot;))" type="code"/>
+        </Property>
+        <Property name="dialogTitle" type="java.lang.String" value="Save As"/>
+      </Properties>
+    </Component>
+    <Menu class="javax.swing.JMenuBar" name="gMenuBar">
+      <SubComponents>
+        <Menu class="javax.swing.JMenu" name="gMenuFile">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="File"/>
+          </Properties>
+          <SubComponents>
+            <MenuItem class="javax.swing.JMenuItem" name="gMenuFileSave">
+              <Properties>
+                <Property name="text" type="java.lang.String" value="Save..."/>
+                <Property name="actionCommand" type="java.lang.String" value="FileSave"/>
+              </Properties>
+              <Events>
+                <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="gMenuFileSaveActionPerformed"/>
+              </Events>
+            </MenuItem>
+          </SubComponents>
+        </Menu>
+        <Menu class="javax.swing.JMenu" name="gMenuLog">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="Log"/>
+          </Properties>
+          <SubComponents>
+            <MenuItem class="javax.swing.JMenuItem" name="gMenuLogClear">
+              <Properties>
+                <Property name="text" type="java.lang.String" value="Clear"/>
+                <Property name="actionCommand" type="java.lang.String" value="LogTableClear"/>
+              </Properties>
+              <Events>
+                <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="gMenuLogClearActionPerformed"/>
+              </Events>
+            </MenuItem>
+            <MenuItem class="javax.swing.JCheckBoxMenuItem" name="gMenuLogControl">
+              <Properties>
+                <Property name="selected" type="boolean" value="true"/>
+                <Property name="text" type="java.lang.String" value="Running"/>
+                <Property name="actionCommand" type="java.lang.String" value="LogControl"/>
+              </Properties>
+              <Events>
+                <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="gMenuLogControlActionPerformed"/>
+              </Events>
+            </MenuItem>
+            <MenuItem class="javax.swing.JCheckBoxMenuItem" name="gMenuLogAutoScroll">
+              <Properties>
+                <Property name="selected" type="boolean" value="true"/>
+                <Property name="text" type="java.lang.String" value="Auto-scroll"/>
+                <Property name="actionCommand" type="java.lang.String" value="LogAutoScroll"/>
+              </Properties>
+              <Events>
+                <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="gMenuLogAutoScrollActionPerformed"/>
+              </Events>
+            </MenuItem>
+          </SubComponents>
+        </Menu>
+        <Menu class="javax.swing.JMenu" name="gMenuServers">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="Servers"/>
+          </Properties>
+          <SubComponents>
+            <Menu class="javax.swing.JMenu" name="gMenuServerSocial">
+              <Properties>
+                <Property name="text" type="java.lang.String" value="Social"/>
+                <Property name="enabled" type="boolean" value="false"/>
+              </Properties>
+              <SubComponents>
+                <MenuItem class="javax.swing.JMenuItem" name="gMenuServerSocialRestart">
+                  <Properties>
+                    <Property name="text" type="java.lang.String" value="Restart"/>
+                    <Property name="actionCommand" type="java.lang.String" value="SocialRestart"/>
+                  </Properties>
+                  <Events>
+                    <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="gMenuServerSocialRestartActionPerformed"/>
+                  </Events>
+                </MenuItem>
+                <MenuItem class="javax.swing.JMenuItem" name="gMenuServerSocialStop">
+                  <Properties>
+                    <Property name="text" type="java.lang.String" value="Stop"/>
+                    <Property name="actionCommand" type="java.lang.String" value="SocialStop"/>
+                  </Properties>
+                </MenuItem>
+              </SubComponents>
+            </Menu>
+            <Menu class="javax.swing.JMenu" name="gMenuServerChat">
+              <Properties>
+                <Property name="text" type="java.lang.String" value="Chat"/>
+                <Property name="enabled" type="boolean" value="false"/>
+              </Properties>
+              <SubComponents>
+                <MenuItem class="javax.swing.JMenuItem" name="gMenuServerChatRestart">
+                  <Properties>
+                    <Property name="text" type="java.lang.String" value="Restart"/>
+                    <Property name="actionCommand" type="java.lang.String" value="ChatRestart"/>
+                  </Properties>
+                </MenuItem>
+                <MenuItem class="javax.swing.JMenuItem" name="gMenuServerChatStop">
+                  <Properties>
+                    <Property name="text" type="java.lang.String" value="Stop"/>
+                    <Property name="actionCommand" type="java.lang.String" value="ChatStop"/>
+                  </Properties>
+                </MenuItem>
+              </SubComponents>
+            </Menu>
+          </SubComponents>
+        </Menu>
+        <Menu class="javax.swing.JMenu" name="gMenuHelp">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="Help"/>
+          </Properties>
+          <SubComponents>
+            <MenuItem class="javax.swing.JMenuItem" name="gMenuHelpAbout">
+              <Properties>
+                <Property name="text" type="java.lang.String" value="About"/>
+              </Properties>
+              <Events>
+                <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="gMenuHelpAboutActionPerformed"/>
+              </Events>
+            </MenuItem>
+          </SubComponents>
+        </Menu>
+      </SubComponents>
+    </Menu>
+  </NonVisualComponents>
+  <Properties>
+    <Property name="defaultCloseOperation" type="int" value="3"/>
+    <Property name="title" type="java.lang.String" value="We Stealz Your Dataz Servers Management"/>
+    <Property name="iconImage" type="java.awt.Image" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
+      <Connection code="new ImageIcon( getClass().getResource(resourcePath +&quot;/ScroogledKeepCalmMug.icon.png&quot;)).getImage()" type="code"/>
+    </Property>
+  </Properties>
+  <SyntheticProperties>
+    <SyntheticProperty name="menuBar" type="java.lang.String" value="gMenuBar"/>
+    <SyntheticProperty name="formSizePolicy" type="int" value="1"/>
+    <SyntheticProperty name="generateCenter" type="boolean" value="false"/>
+  </SyntheticProperties>
+  <AuxValues>
+    <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+    <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,-24,0,0,3,16"/>
+  </AuxValues>
+
+  <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+  <SubComponents>
+    <Container class="javax.swing.JScrollPane" name="gLogScroller">
+      <Properties>
+        <Property name="autoscrolls" type="boolean" value="true"/>
+      </Properties>
+      <Constraints>
+        <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+          <BorderConstraints direction="Center"/>
+        </Constraint>
+      </Constraints>
+
+      <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+      <SubComponents>
+        <Component class="javax.swing.JTable" name="gLogTable">
+          <Properties>
+            <Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.editors2.TableModelEditor">
+              <Table columnCount="4" rowCount="1">
+                <Column editable="false" title="Time" type="java.lang.String"/>
+                <Column editable="false" title="Level" type="java.lang.String"/>
+                <Column editable="false" title="Facility" type="java.lang.String"/>
+                <Column editable="false" title="Message" type="java.lang.String"/>
+              </Table>
+            </Property>
+            <Property name="autoResizeMode" type="int" value="3"/>
+            <Property name="columnModel" type="javax.swing.table.TableColumnModel" editor="org.netbeans.modules.form.editors2.TableColumnModelEditor">
+              <TableColumnModel selectionModel="0">
+                <Column maxWidth="-1" minWidth="-1" prefWidth="128" resizable="true">
+                  <Title/>
+                  <Editor/>
+                  <Renderer/>
+                </Column>
+                <Column maxWidth="-1" minWidth="-1" prefWidth="128" resizable="true">
+                  <Title/>
+                  <Editor/>
+                  <Renderer/>
+                </Column>
+                <Column maxWidth="-1" minWidth="-1" prefWidth="128" resizable="true">
+                  <Title/>
+                  <Editor/>
+                  <Renderer/>
+                </Column>
+                <Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="true">
+                  <Title/>
+                  <Editor/>
+                  <Renderer/>
+                </Column>
+              </TableColumnModel>
+            </Property>
+            <Property name="fillsViewportHeight" type="boolean" value="true"/>
+            <Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor">
+              <TableHeader reorderingAllowed="false" resizingAllowed="true"/>
+            </Property>
+          </Properties>
+        </Component>
+      </SubComponents>
+    </Container>
+  </SubComponents>
+</Form>
diff --git a/src/uk/ac/ntu/n0521366/wsyd/management/ServerManagement.java b/src/uk/ac/ntu/n0521366/wsyd/management/ServerManagement.java
new file mode 100644 (file)
index 0000000..ada15f6
--- /dev/null
@@ -0,0 +1,633 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.management;
+
+import java.io.FileWriter;
+import java.io.BufferedWriter;
+import java.io.File;
+import javax.swing.ImageIcon;
+import java.awt.Desktop;
+import java.net.URI;
+import java.io.IOException;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.Timer;
+import javax.swing.table.DefaultTableModel;
+import java.net.UnknownHostException;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Filter;
+import java.util.Date;
+import java.text.SimpleDateFormat;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import uk.ac.ntu.n0521366.wsyd.libs.logging.TableModelHandler;
+import uk.ac.ntu.n0521366.wsyd.libs.message.MessageLogRecord;
+import uk.ac.ntu.n0521366.wsyd.libs.message.MessagePresence;
+import uk.ac.ntu.n0521366.wsyd.libs.net.WSYD_SocketAddress.Protocol;
+import uk.ac.ntu.n0521366.wsyd.libs.net.*;
+
+/**
+ *
+ * @author TJ <hacker@iam.tj>
+ */
+public class ServerManagement extends javax.swing.JFrame implements NetworkMessageEventListener, Filter {
+    /**
+     * class-wide logger
+     */
+    static final Logger LOGGER = Logger.getLogger("ServerManagement");
+    
+    /**
+     * Location of resource bundles, images, icons
+     */
+    private static final String resourcePath = "/uk/ac/ntu/n0521366/wsyd/resources";
+
+    /**
+     * The UDP listener address for incoming log messages
+     */
+    WSYD_SocketAddress _logServerSA = null;
+
+    /**
+     * UDP multi-cast presence advertiser
+     */
+    WSYD_SocketAddress _multicastAdvertiserSA = null;
+    
+    /**
+     * Log service running in a SwingWorker thread.
+     */
+    NetworkServerUDP _logServer = null;
+
+    /**
+     * Multi-cast neighbour advertise and discover service in a SwingWorker thread.
+     */
+    NetworkServerUDPMulticast _multicastServer = null;
+    
+
+    /**
+     * Enable or suspend logging
+     */
+    private boolean _doLogging;
+    
+    /**
+     * Enable or suspend table auto-scroll
+     */
+    private boolean _autoScroll;
+    
+    /**
+     * Regular presence announcements
+     */
+    Timer multicastAnnounce;
+    
+    /**
+     * Creates new GUI
+     * Instantiating code <em>must</em> also call initListeners()
+     * 
+     * @see #initListeners()
+     */
+    public ServerManagement() {
+        LOGGER.setLevel(Level.ALL);
+        initComponents();
+        _doLogging = true;
+        _autoScroll = true;
+        setLocationRelativeTo(null); // center on screen
+        // XXX: workaround for bug in NetBeans that doesn't set the displayed background colour, only the component colour, from the Properties editor
+        gDialogAbout.getContentPane().setBackground(gDialogAbout.getBackground());
+    }
+
+    /**
+     * Initialise listeners and other objects that require a reference to 'this'.
+     * 
+     * Passing 'this' from within the constructor is unsafe since the object is not
+     * fully constructed, so do it here. The compiler and virtual machine are free to move 'final'
+     * properties outside the constructor which means they may not be correctly
+     * initialised before the constructor returns. This is especially problematic
+     * in multi-threading applications.
+     * 
+     * @return a reference to 'this' so method calls can be chained (e.g. new ServerManagement().initListeners().setVisible(true) )
+     * @throws UnknownHostException
+     */
+    public ServerManagement initListeners() throws UnknownHostException {        
+        LOGGER.addHandler(new TableModelHandler(this)); // send messages to the GUI log table
+        LOGGER.setUseParentHandlers(false); // don't send messages to the default error stream logger of the parent
+        LOGGER.log(Level.INFO, "Server Management starting");
+        
+        _logServerSA = new WSYD_SocketAddress(Network.PORTS_SERVER_LOG, Protocol.UDP);
+        _logServer = new NetworkServerUDP(_logServerSA, "ServerLog", LOGGER);
+        _logServer.addNetworkMessageEventListener(this, "Log");
+        _logServer.setSimulate(false);
+        _logServer.execute();
+        _multicastAdvertiserSA = new WSYD_SocketAddress(Network.MULTICAST_IP, Network.PORTS_MULTICAST_DISCOVERY, Protocol.UDP);
+        _multicastServer = new NetworkServerUDPMulticast(_multicastAdvertiserSA, "ServerLogMC", LOGGER);
+        _multicastServer.addNetworkMessageEventListener(this, "Neighbour");
+        _multicastServer.execute();
+
+        ActionListener multicastAnnounceActionListener = new ActionListener() {
+            /**
+             * Activated by timer events to send multi-cast neighbour announcements for the Log Service.
+             * @param e 
+             */
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                // Create local log report first
+                LogRecord record = new LogRecord(Level.FINEST, "Multicast: Announcing Presence");
+                record.setSourceClassName("ServerLog");
+                record.setMillis(System.currentTimeMillis());
+                LOGGER.log(record);
+
+                // Announce the Log Server service
+                MessagePresence mp = new MessagePresence("ServerLog", Network.PORTS_SERVER_LOG);
+                NetworkMessage nm = NetworkMessage.createNetworkMessage("Neighbour", "all", mp);
+                nm.setSender("ServerLog");
+                _multicastServer.queueMessage(nm);
+                
+                // clean up the known hosts map and keep Server menu up-to-date
+                ArrayList<String> servicesRemoved = _multicastServer.cleanServiceToHostMap(5000);
+                for (String service: servicesRemoved) {
+                        switch (service) {
+                            case "ServerSocial":
+                                gMenuServerSocial.setEnabled(false);
+                                break;
+                            case "ServerChat":
+                                gMenuServerChat.setEnabled(false);
+                                break;                                
+                        }
+                }
+
+            }
+        };
+        multicastAnnounce = new Timer(1000, multicastAnnounceActionListener);
+        multicastAnnounce.setInitialDelay(100);
+        multicastAnnounce.start();
+        
+        return this;
+    }
+
+    /**
+     * Add a log record to the log table if logging is enabled.
+     * 
+     * Abusing an already-existing logging interface. This method isn't filtering, it is called by
+     * the Logger's TableModelHandler to publish records.
+     * 
+     * @see TableModelHandler
+     * @param record
+     * @return true if logging is enabled
+     */
+    @Override
+    public boolean isLoggable(LogRecord record) {
+        if (_doLogging) {
+            if (record != null) {
+                SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                // add the log record to the log table
+                ((DefaultTableModel)gLogTable.getModel()).addRow(
+                        new Object[]{
+                            df.format(new Date(record.getMillis())),
+                            record.getLevel().toString(),
+                            record.getSourceClassName(),
+                            record.getMessage()
+                        }
+                );
+                if (_autoScroll) { // keep last record added in the view
+                    int row = gLogTable.getModel().getRowCount();
+                    gLogTable.scrollRectToVisible(gLogTable.getCellRect(row, 0, true));
+                }
+            }
+        }
+        return _doLogging;
+    }
+
+    /**
+     * This method is called from within the constructor to initialize the form.
+     * WARNING: Do NOT modify this code. The content of this method is always
+     * regenerated by the Form Editor.
+     */
+    @SuppressWarnings("unchecked")
+    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+    private void initComponents() {
+
+        buttonGroup1 = new javax.swing.ButtonGroup();
+        gDialogAbout = new javax.swing.JDialog();
+        gTextAreaAbout = new javax.swing.JTextArea();
+        gBtnAbout = new javax.swing.JButton();
+        gFileChooser = new javax.swing.JFileChooser();
+        gLogScroller = new javax.swing.JScrollPane();
+        gLogTable = new javax.swing.JTable();
+        gMenuBar = new javax.swing.JMenuBar();
+        gMenuFile = new javax.swing.JMenu();
+        gMenuFileSave = new javax.swing.JMenuItem();
+        gMenuLog = new javax.swing.JMenu();
+        gMenuLogClear = new javax.swing.JMenuItem();
+        gMenuLogControl = new javax.swing.JCheckBoxMenuItem();
+        gMenuLogAutoScroll = new javax.swing.JCheckBoxMenuItem();
+        gMenuServers = new javax.swing.JMenu();
+        gMenuServerSocial = new javax.swing.JMenu();
+        gMenuServerSocialRestart = new javax.swing.JMenuItem();
+        gMenuServerSocialStop = new javax.swing.JMenuItem();
+        gMenuServerChat = new javax.swing.JMenu();
+        gMenuServerChatRestart = new javax.swing.JMenuItem();
+        gMenuServerChatStop = new javax.swing.JMenuItem();
+        gMenuHelp = new javax.swing.JMenu();
+        gMenuHelpAbout = new javax.swing.JMenuItem();
+
+        gDialogAbout.setTitle("About");
+        gDialogAbout.setBackground(new java.awt.Color(255, 255, 255));
+        gDialogAbout.setIconImage(new ImageIcon( getClass().getResource(resourcePath +"/ScroogledKeepCalmMug.icon.png")).getImage());
+        gDialogAbout.setMinimumSize(new java.awt.Dimension(590, 340));
+        gDialogAbout.setResizable(false);
+        gDialogAbout.getContentPane().setLayout(null);
+
+        gTextAreaAbout.setEditable(false);
+        gTextAreaAbout.setColumns(20);
+        gTextAreaAbout.setRows(5);
+        gTextAreaAbout.setText("Server Management client\n© Copyright 2015 TJ <hacker@iam.tj>\n\n<<< Click on the mug to learn more!\n\nI will disappear in 20 seconds.\n\n");
+        gTextAreaAbout.setBorder(null);
+        gDialogAbout.getContentPane().add(gTextAreaAbout);
+        gTextAreaAbout.setBounds(310, 30, 270, 200);
+
+        gBtnAbout.setBackground(new java.awt.Color(255, 255, 255));
+        gBtnAbout.setFont(new java.awt.Font("Dialog", 1, 18)); // NOI18N
+        gBtnAbout.setIcon(new javax.swing.ImageIcon(getClass().getResource("/uk/ac/ntu/n0521366/wsyd/resources/ScroogledKeepCalmMug.png"))); // NOI18N
+        gBtnAbout.setText("We Stealz Your Dataz");
+        gBtnAbout.setBorder(null);
+        gBtnAbout.setBorderPainted(false);
+        gBtnAbout.setContentAreaFilled(false);
+        gBtnAbout.setDefaultCapable(false);
+        gBtnAbout.setFocusPainted(false);
+        gBtnAbout.setRolloverEnabled(false);
+        gBtnAbout.setVerticalAlignment(javax.swing.SwingConstants.TOP);
+        gBtnAbout.setVerticalTextPosition(javax.swing.SwingConstants.TOP);
+        gBtnAbout.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                AboutAction(evt);
+            }
+        });
+        gDialogAbout.getContentPane().add(gBtnAbout);
+        gBtnAbout.setBounds(0, 0, 530, 324);
+
+        gFileChooser.setDialogType(javax.swing.JFileChooser.SAVE_DIALOG);
+        gFileChooser.setCurrentDirectory(new File(System.getProperty("user.dir")));
+        gFileChooser.setDialogTitle("Save As");
+
+        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
+        setTitle("We Stealz Your Dataz Servers Management");
+        setIconImage(new ImageIcon( getClass().getResource(resourcePath +"/ScroogledKeepCalmMug.icon.png")).getImage());
+
+        gLogScroller.setAutoscrolls(true);
+
+        gLogTable.setModel(new javax.swing.table.DefaultTableModel(
+            new Object [][] {
+                {null, null, null, null}
+            },
+            new String [] {
+                "Time", "Level", "Facility", "Message"
+            }
+        ) {
+            Class[] types = new Class [] {
+                java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class
+            };
+            boolean[] canEdit = new boolean [] {
+                false, false, false, false
+            };
+
+            public Class getColumnClass(int columnIndex) {
+                return types [columnIndex];
+            }
+
+            public boolean isCellEditable(int rowIndex, int columnIndex) {
+                return canEdit [columnIndex];
+            }
+        });
+        gLogTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_LAST_COLUMN);
+        gLogTable.setFillsViewportHeight(true);
+        gLogTable.getTableHeader().setReorderingAllowed(false);
+        gLogScroller.setViewportView(gLogTable);
+        if (gLogTable.getColumnModel().getColumnCount() > 0) {
+            gLogTable.getColumnModel().getColumn(0).setPreferredWidth(128);
+            gLogTable.getColumnModel().getColumn(1).setPreferredWidth(128);
+            gLogTable.getColumnModel().getColumn(2).setPreferredWidth(128);
+        }
+
+        getContentPane().add(gLogScroller, java.awt.BorderLayout.CENTER);
+
+        gMenuFile.setText("File");
+
+        gMenuFileSave.setText("Save...");
+        gMenuFileSave.setActionCommand("FileSave");
+        gMenuFileSave.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                gMenuFileSaveActionPerformed(evt);
+            }
+        });
+        gMenuFile.add(gMenuFileSave);
+
+        gMenuBar.add(gMenuFile);
+
+        gMenuLog.setText("Log");
+
+        gMenuLogClear.setText("Clear");
+        gMenuLogClear.setActionCommand("LogTableClear");
+        gMenuLogClear.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                gMenuLogClearActionPerformed(evt);
+            }
+        });
+        gMenuLog.add(gMenuLogClear);
+
+        gMenuLogControl.setSelected(true);
+        gMenuLogControl.setText("Running");
+        gMenuLogControl.setActionCommand("LogControl");
+        gMenuLogControl.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                gMenuLogControlActionPerformed(evt);
+            }
+        });
+        gMenuLog.add(gMenuLogControl);
+
+        gMenuLogAutoScroll.setSelected(true);
+        gMenuLogAutoScroll.setText("Auto-scroll");
+        gMenuLogAutoScroll.setActionCommand("LogAutoScroll");
+        gMenuLogAutoScroll.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                gMenuLogAutoScrollActionPerformed(evt);
+            }
+        });
+        gMenuLog.add(gMenuLogAutoScroll);
+
+        gMenuBar.add(gMenuLog);
+
+        gMenuServers.setText("Servers");
+
+        gMenuServerSocial.setText("Social");
+        gMenuServerSocial.setEnabled(false);
+
+        gMenuServerSocialRestart.setText("Restart");
+        gMenuServerSocialRestart.setActionCommand("SocialRestart");
+        gMenuServerSocialRestart.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                gMenuServerSocialRestartActionPerformed(evt);
+            }
+        });
+        gMenuServerSocial.add(gMenuServerSocialRestart);
+
+        gMenuServerSocialStop.setText("Stop");
+        gMenuServerSocialStop.setActionCommand("SocialStop");
+        gMenuServerSocial.add(gMenuServerSocialStop);
+
+        gMenuServers.add(gMenuServerSocial);
+
+        gMenuServerChat.setText("Chat");
+        gMenuServerChat.setEnabled(false);
+
+        gMenuServerChatRestart.setText("Restart");
+        gMenuServerChatRestart.setActionCommand("ChatRestart");
+        gMenuServerChat.add(gMenuServerChatRestart);
+
+        gMenuServerChatStop.setText("Stop");
+        gMenuServerChatStop.setActionCommand("ChatStop");
+        gMenuServerChat.add(gMenuServerChatStop);
+
+        gMenuServers.add(gMenuServerChat);
+
+        gMenuBar.add(gMenuServers);
+
+        gMenuHelp.setText("Help");
+
+        gMenuHelpAbout.setText("About");
+        gMenuHelpAbout.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                gMenuHelpAboutActionPerformed(evt);
+            }
+        });
+        gMenuHelp.add(gMenuHelpAbout);
+
+        gMenuBar.add(gMenuHelp);
+
+        setJMenuBar(gMenuBar);
+
+        pack();
+    }// </editor-fold>//GEN-END:initComponents
+
+    private void gMenuServerSocialRestartActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuServerSocialRestartActionPerformed
+        // TODO add your handling code here:
+
+    }//GEN-LAST:event_gMenuServerSocialRestartActionPerformed
+
+    /**
+     * When the (disguised) mug-icon button is pressed load a web page in the system default browser.
+     * 
+     * Displays a news story about Microsoft Store's 'Scroogle' mug which coincidentally has a tag line
+     * that is almost identical to the chosen name of this application.
+     * 
+     * @param evt 
+     */
+    private void AboutAction(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_AboutAction
+        try {
+            Desktop.getDesktop().browse(URI.create("http://www.slate.com/blogs/future_tense/2013/11/20/microsoft_scroogled_gear_anti_google_t_shirts_mugs_say_keep_calm_while_we.html"));
+        } catch(IOException e) {
+        }
+    }//GEN-LAST:event_AboutAction
+
+    /**
+     * Show the Help>About dialog and auto-close it after 20 seconds.
+     * 
+     * @param evt
+     */
+    private void gMenuHelpAboutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuHelpAboutActionPerformed
+        ActionListener autoCloseAboutDlg = new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                gDialogAbout.setVisible(false);
+            }
+        };
+
+        gDialogAbout.setLocationRelativeTo(this); // center in application window
+        gDialogAbout.setVisible(true);
+        Timer autoClose = new Timer(20000, autoCloseAboutDlg);
+        autoClose.start();
+    }//GEN-LAST:event_gMenuHelpAboutActionPerformed
+
+    /**
+     * Save log entries to a file.
+     * 
+     * @param evt 
+     */
+    private void gMenuFileSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuFileSaveActionPerformed
+        if (gFileChooser.showSaveDialog(this) == javax.swing.JFileChooser.APPROVE_OPTION) {
+            File saveAs = gFileChooser.getSelectedFile();
+            try (
+                BufferedWriter writer = new BufferedWriter(new FileWriter(saveAs));
+            )
+            {
+                for (int row = 0; row < gLogTable.getModel().getRowCount(); row++) {
+                    StringBuilder record = new StringBuilder();
+                    int colMax = gLogTable.getModel().getColumnCount();
+                    for (int col = 0; col < colMax; col++) {
+                        record.append(gLogTable.getModel().getValueAt(row, col));
+                        if (col < colMax - 1) {
+                            record.append(",");
+                        }
+                    }
+                    writer.write(record.toString());
+                    writer.newLine();
+                }
+                writer.close();
+            } catch (IOException e) {
+                System.err.println("Unable to write to file");
+            }
+        }
+    }//GEN-LAST:event_gMenuFileSaveActionPerformed
+
+    /**
+     * Clear the log table.
+     * @param evt 
+     */
+    private void gMenuLogClearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuLogClearActionPerformed
+        ((DefaultTableModel)gLogTable.getModel()).setRowCount(0);
+    }//GEN-LAST:event_gMenuLogClearActionPerformed
+
+    /**
+     * Enable or disable logging.
+     * @param evt 
+     */
+    private void gMenuLogControlActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuLogControlActionPerformed
+        _doLogging = !_doLogging;
+        gMenuLogControl.setSelected(_doLogging);
+    }//GEN-LAST:event_gMenuLogControlActionPerformed
+
+    /**
+     * Enable or disable automatic scrolling of the log table.
+     * @param evt 
+     */
+    private void gMenuLogAutoScrollActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuLogAutoScrollActionPerformed
+        _autoScroll = !_autoScroll;
+        gMenuLogAutoScroll.setSelected(_autoScroll);
+    }//GEN-LAST:event_gMenuLogAutoScrollActionPerformed
+
+    /**
+     * Receive a NetworkMessageEvent and dispose of it
+     * @param event
+     */
+    @Override
+    public void NetworkMessageReceived(NetworkMessageEvent event) {
+        NetworkMessage nm = event.getNetworkMessage();
+        if (nm == null || !_doLogging)
+            return;
+        // is it a LogRecord?
+        switch (nm.getIntent()) {
+            case "Log":
+                MessageLogRecord m = (MessageLogRecord) nm.getMessage();
+                if (m == null)
+                    return;
+                this.isLoggable(m.record);
+                break;
+            case "Neighbour":
+                // TODO: NetworkMessageReceived(): Test Multicast message received handler to enable menu items for recognised servers
+                String type = nm.getMessage().getMessageType();
+                    if (type.equals(MessagePresence.getType())) { // Presence
+                        MessagePresence mp = (MessagePresence)nm.getMessage();
+                        switch (mp.serviceName) {
+                            case "ServerSocial":
+                                gMenuServerSocial.setEnabled(true);
+                                break;
+                            case "ServerChat":
+                                gMenuServerChat.setEnabled(true);
+                                break;                                
+                        }
+                    }
+
+
+            default: // log all unhandled messages
+                LogRecord record = new LogRecord(Level.WARNING,
+                    MessageFormat.format("Unhandled NetworkMessage received with intent: \"{0}\" sender: \"{1}\" target: \"{2}\"",
+                        nm.getIntent(), nm.getSender(), nm.getTarget() )
+                );
+                record.setMillis(System.currentTimeMillis());
+                LOGGER.log(record);
+        }
+    }
+
+    /**
+     * @param args the command line arguments
+     */
+    public static void main(String args[]) {
+        /* Set the Nimbus look and feel */
+        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
+        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
+         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
+         */
+        try {
+            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
+                if ("Nimbus".equals(info.getName())) {
+                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
+                    break;
+                }
+            }
+        } catch (ClassNotFoundException|InstantiationException|IllegalAccessException|javax.swing.UnsupportedLookAndFeelException ex) {
+            java.util.logging.Logger.getLogger(ServerManagement.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
+        }
+        //</editor-fold>
+        //</editor-fold>
+        //</editor-fold>
+        //</editor-fold>
+
+        /* Create and display the form */
+        java.awt.EventQueue.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                new ServerManagement().initListeners().setVisible(true);
+                }
+                catch(UnknownHostException e) {
+                    System.err.println("Error: cannot create log server listener socket");
+                }
+            }
+        });
+    }
+
+    // Variables declaration - do not modify//GEN-BEGIN:variables
+    private javax.swing.ButtonGroup buttonGroup1;
+    private javax.swing.JButton gBtnAbout;
+    private javax.swing.JDialog gDialogAbout;
+    private javax.swing.JFileChooser gFileChooser;
+    private javax.swing.JScrollPane gLogScroller;
+    private javax.swing.JTable gLogTable;
+    private javax.swing.JMenuBar gMenuBar;
+    private javax.swing.JMenu gMenuFile;
+    private javax.swing.JMenuItem gMenuFileSave;
+    private javax.swing.JMenu gMenuHelp;
+    private javax.swing.JMenuItem gMenuHelpAbout;
+    private javax.swing.JMenu gMenuLog;
+    private javax.swing.JCheckBoxMenuItem gMenuLogAutoScroll;
+    private javax.swing.JMenuItem gMenuLogClear;
+    private javax.swing.JCheckBoxMenuItem gMenuLogControl;
+    private javax.swing.JMenu gMenuServerChat;
+    private javax.swing.JMenuItem gMenuServerChatRestart;
+    private javax.swing.JMenuItem gMenuServerChatStop;
+    private javax.swing.JMenu gMenuServerSocial;
+    private javax.swing.JMenuItem gMenuServerSocialRestart;
+    private javax.swing.JMenuItem gMenuServerSocialStop;
+    private javax.swing.JMenu gMenuServers;
+    private javax.swing.JTextArea gTextAreaAbout;
+    // End of variables declaration//GEN-END:variables
+}
diff --git a/src/uk/ac/ntu/n0521366/wsyd/resources/ScroogledKeepCalmMug.icon.png b/src/uk/ac/ntu/n0521366/wsyd/resources/ScroogledKeepCalmMug.icon.png
new file mode 100644 (file)
index 0000000..bb36c92
Binary files /dev/null and b/src/uk/ac/ntu/n0521366/wsyd/resources/ScroogledKeepCalmMug.icon.png differ
diff --git a/src/uk/ac/ntu/n0521366/wsyd/resources/ScroogledKeepCalmMug.png b/src/uk/ac/ntu/n0521366/wsyd/resources/ScroogledKeepCalmMug.png
new file mode 100644 (file)
index 0000000..512a328
Binary files /dev/null and b/src/uk/ac/ntu/n0521366/wsyd/resources/ScroogledKeepCalmMug.png differ
diff --git a/src/uk/ac/ntu/n0521366/wsyd/server/ServerSocial.java b/src/uk/ac/ntu/n0521366/wsyd/server/ServerSocial.java
new file mode 100644 (file)
index 0000000..836c6d3
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iam.tj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.server;
+
+import java.util.Date;
+import java.util.ArrayList;
+import java.text.SimpleDateFormat;
+import java.text.ParseException;
+import java.util.TreeSet;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Map;
+import java.util.Collections;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import uk.ac.ntu.n0521366.wsyd.libs.WSYD_Member;
+import uk.ac.ntu.n0521366.wsyd.libs.WSYD_Member_Comparator_UserID;
+
+/**
+ * The main Social Network server.
+ *
+ * Can be restarted or stopped using the class static attributes
+ * exitRequested and restartRequested. This can be done by an optional
+ * Management GUI application.
+ *
+ * @author TJ <hacker@iam.tj>
+ */
+public final class ServerSocial {
+    /**
+     * Persistent storage in file-system when server exits.
+     */
+    static final String _membersFile = "WSYD_Members.serialized";
+
+    /**
+     * CSV test data file.
+     * 
+     * If it exists in the file system, only used if there is no _membersFile
+     */
+    static final String _testData = "WSYD_TestData.csv";
+    
+    /**
+     * Indicates to start() loop and main() methods to exit completely.
+     */
+    public static boolean exitRequested = false;
+
+    /**
+     * Indicates to start() loop to exit, and to main() to restart the server.
+     */
+    public static boolean restartRequested = true;
+
+    /**
+     * Handles display and sending of log messages.
+     */
+    @SuppressWarnings("NonConstantLogger")
+    private static Logger LOGGER;
+
+    /**
+     * SortedMap wraps a TreeMap that has been made thread-safe by
+     * Collections.synchronizedSortedMap() in readMembers().
+     *
+     * Long key, the userID
+     * WSYD_Member member record
+     */
+    SortedMap<Long, WSYD_Member> _members;
+
+    /**
+     * userIDs of members currently logged in
+     */
+    ArrayList<Long> _membersOnline;
+    
+
+    /**
+     * Default constructor.
+     */
+    public ServerSocial() {
+        String[] className = this.getClass().getName().split("\\.");
+        LOGGER = Logger.getLogger(className[className.length - 1]);
+        LOGGER.setLevel(Level.ALL);
+        readMembers(_membersFile);
+        _membersOnline = new ArrayList<>();
+    }
+
+    /**
+     * Main execution loop of the server
+     *
+     * @return true if no errors encountered
+     * @throws java.lang.InterruptedException
+     */
+    @SuppressWarnings("SleepWhileInLoop")
+    public boolean start() throws InterruptedException {
+        boolean result;
+
+        // TODO: start() create TCP listener
+        // TODO: start() create UDP Multicast group listener and broadcast adverts
+        // wait for connections
+        int loopCount = 10;
+        while (!ServerSocial.exitRequested && ! ServerSocial.restartRequested) {
+            Thread.sleep(1000); // wait a second
+            System.out.println("start() loop " + loopCount);
+            if (loopCount-- == 0)
+                ServerSocial.exitRequested = true;
+        }
+        result = writeMembers(_membersFile);
+
+        return result;
+    }
+
+    /**
+     * Deserialize the collection of WSYD_Members from file
+     *
+     * @param fileName serialized members data file
+     * @return true if successfully deserialized
+     */
+    @SuppressWarnings("CallToPrintStackTrace")
+    public boolean readMembers(String fileName) {
+        boolean result = false;
+
+        try (
+            FileInputStream f = new FileInputStream(fileName);
+            ObjectInputStream in  = new ObjectInputStream(f);
+            )
+        {
+            if (_members == null)
+                /* XXX: do not pass a Comparator to the constructor if collection is being deserialized as one was already saved during serialization.
+                 *      If the Comparator is passed to the constructor the serialized object will 'grow' by ~17 bytes each time as multi Comparator
+                 *      objects are written each time the collection is serialized.
+                */
+                _members = Collections.synchronizedSortedMap( new TreeMap<Long, WSYD_Member>() );
+            if (!_members.isEmpty())
+                _members.clear();
+            /* Need explicit cast to SortedMap for Object type returned by readObject()
+             * but this can cause an "unchecked cast" compiler warning since the compiler
+             * cannot be sure the Object returned from readObject() is really a
+             * SortedMap<Long, WSYD_Member> so need to tell the compiler that in this case
+             * we are sure it is. The following for() iteration will cause a
+             * ClassCastException if the type is not as expected.
+             */
+            @SuppressWarnings("unchecked")
+            SortedMap<Long, WSYD_Member> temp = (SortedMap<Long, WSYD_Member>) in.readObject();
+            _members = Collections.synchronizedSortedMap( temp );
+            for (Map.Entry<Long, WSYD_Member> e : _members.entrySet()) {
+                System.out.println(e.getKey() + ": " + e.getValue().toString());
+            }
+            LOGGER.log(Level.INFO, "Members database read from {0}", fileName);
+            result = true;
+        }
+        catch(FileNotFoundException e) {
+            _members = Collections.synchronizedSortedMap( new TreeMap<Long, WSYD_Member>( new WSYD_Member_Comparator_UserID() ) );
+            LOGGER.log(Level.INFO, "Starting new members database: no database file found ({0})", fileName);
+            result = true;
+
+            // if test data CSV exists import it
+            File csv = new File(_testData);
+            if (csv.exists() && csv.isFile()) {
+                LOGGER.log(Level.INFO, "Importing test data from {0}", _testData);
+                importCSV(_testData);
+            }
+
+        }
+        catch(IOException e) {
+            LOGGER.log(Level.SEVERE, "Unable to read database file {0}", fileName);
+            e.printStackTrace();
+        }
+        catch(ClassNotFoundException e) {
+            LOGGER.log(Level.SEVERE, "Unable to deserialize database file {0}", fileName);
+            e.printStackTrace();
+        }
+
+        return result;
+    }
+
+    /**
+     * Serialize the WSYD_Members collection to a file
+     *
+     * @param fileName database file
+     * @return true if collection was successfully serialized
+     */
+    @SuppressWarnings("CallToPrintStackTrace")
+    public boolean writeMembers(String fileName) {
+        boolean result = false;
+
+        if (!_members.isEmpty()) { // don't write an empty database
+            try (
+                FileOutputStream f = new FileOutputStream(fileName);
+                ObjectOutputStream out = new ObjectOutputStream(f);
+            )
+            {
+                out.writeObject(_members);
+
+                LOGGER.log(Level.INFO, "Members database written to {0}", fileName);
+                result = true;
+            }
+            catch(IOException e) {
+                LOGGER.log(Level.SEVERE, "Unable to write database file {0}", fileName);
+                e.printStackTrace();
+            }
+        }
+        else
+            result = true;
+
+        return result;
+    }
+
+    /**
+     * Read a CSV file containing WSYD_Member records and add it to the in-memory
+     * collection.
+     * 
+     * @param fileName name of CSV file
+     * @return true if successfully imported
+     */
+    @SuppressWarnings("CallToPrintStackTrace")
+    public boolean importCSV(String fileName) {
+        boolean result = false;
+
+        try (
+            FileInputStream fis = new FileInputStream(fileName);
+            InputStreamReader isr = new InputStreamReader(fis);
+            BufferedReader br = new BufferedReader(isr);
+        )
+        {
+            String line;
+            while ((line = br.readLine()) != null) {
+                LOGGER.log(Level.FINEST, line);
+                try {
+                    WSYD_Member temp = WSYD_Member.createWSYD_Member(line);
+                    if (temp != null)
+                        _members.put(temp._userID, temp); // add new member to collection
+                } catch (IllegalArgumentException e) {     
+                    LOGGER.log(Level.WARNING, "Ignoring bad CSV import line");
+                }
+            }
+        }
+        catch(IOException e) {
+                LOGGER.log(Level.SEVERE, "Unable to import CSV file {0}", fileName);
+                e.printStackTrace();
+        }
+
+        return result;
+    }
+
+    /**
+     * Export WSYD_Members collection to a CSV file.
+     * 
+     * @param fileName name of the CSV file to write
+     * @return true if successful
+     */
+    @SuppressWarnings("CallToPrintStackTrace")
+    public boolean exportCSV(String fileName) {
+        boolean result = false;
+
+        try (
+            FileOutputStream fos = new FileOutputStream(fileName);
+            OutputStreamWriter osw = new OutputStreamWriter(fos, "utf-8");
+            BufferedWriter bw = new BufferedWriter(osw);
+        )
+        {
+            bw.write("# 0     , 1       , 2       , 3              , 4  , 5        , 6        , 7      , 8                  , 9");
+            bw.write("# userID, userName, password, currentLocation, bio, birthDate, interests, friends, friendsRequestsSent, friendsRequestsReceived");
+            for (Map.Entry<Long, WSYD_Member> e: _members.entrySet()) {
+                bw.write(e.getKey() + ": " + e.getValue().toString());
+            }
+        }
+        catch(IOException e) {
+            LOGGER.log(Level.SEVERE, "Unable to export to CSV file {0}", fileName);
+            e.printStackTrace();
+        }
+
+        return result;
+    }
+
+    /**
+     * Entry point which starts, restarts, and exits the application.
+     * 
+     * @param args the command line arguments
+     * @throws java.lang.InterruptedException
+     */
+    public static void main(String[] args) throws InterruptedException {
+        while (!ServerSocial.exitRequested && ServerSocial.restartRequested) {
+            ServerSocial app = new ServerSocial();
+            ServerSocial.restartRequested = false;
+            if (!app.start()) {
+                System.err.println("Encountered error running Social Server");
+                break; // leave the while loop
+            }
+        }
+    }
+}
+    
+
diff --git a/test/uk/ac/ntu/n0521366/wsyd/libs/WSYD_MemberTest.java b/test/uk/ac/ntu/n0521366/wsyd/libs/WSYD_MemberTest.java
new file mode 100644 (file)
index 0000000..364ec84
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 Eddie Berrisford-Lynch <n0521366@ntu.ac.uk>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TreeSet;
+
+/**
+ *
+ * @author TJ <hacker@iam.tj>
+ */
+public class WSYD_MemberTest {
+    public WSYD_Member instance;
+    public String importCSV;
+    
+    public WSYD_MemberTest() {
+    }
+    
+    @BeforeClass
+    public static void setUpClass() {
+    }
+    
+    @AfterClass
+    public static void tearDownClass() {
+    }
+    
+    @Before
+    public void setUp() throws java.text.ParseException {
+        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+        Date birthDate = df.parse("1993-04-05");
+        TreeSet<String> interests = new TreeSet<>();
+        TreeSet<Long> friends = new TreeSet<>();
+        TreeSet<Long> friendsRequestsSent = new TreeSet<>();
+        TreeSet<Long> friendsRequestsReceived = new TreeSet<>();
+        interests.add("Computers");
+        interests.add("Games");
+        interests.add("Gym");
+        interests.add("Music");
+        friends.add(new Long(1));
+        friends.add(new Long(5));
+        friendsRequestsSent.add(new Long(0));
+        friendsRequestsReceived.add(new Long(3));
+        
+        instance = new WSYD_Member(2, "Eddie", "letmein", "Nottingham", "Be glad when exams are over", birthDate, interests, friends, friendsRequestsSent, friendsRequestsReceived);
+        importCSV = "2,Eddie,letmein,Nottingham,Be glad when exams are over,1993-04-05,Computers~Games~Gym~Music,1~5,0,3";
+    }
+    
+    @After
+    public void tearDown() {
+    }
+
+    @Test
+    public void testCompareTo() {
+        System.out.println(java.text.MessageFormat.format("{0}.compareTo(value)", instance.getClass().getName()));
+        WSYD_Member b = new WSYD_Member();
+        WSYD_Member e = new WSYD_Member();
+        WSYD_Member h = new WSYD_Member();
+        WSYD_Member f = new WSYD_Member();
+        b._userName = "Below";
+        e._userName = "Eddie";
+        h._userName = "Higher";
+        f._userName = "Fit";
+        assertEquals(3, instance.compareTo(b)); // String.compareTo() returns the 'distance' between the letters
+        assertEquals(0, instance.compareTo(e));
+        assertEquals(-3, instance.compareTo(h));
+        assertEquals(-1, instance.compareTo(f));
+        
+    }
+    
+    @Test (expected=IllegalArgumentException.class)
+    public void testCompareToIAE() throws IllegalArgumentException {
+        System.out.println(java.text.MessageFormat.format("{0}.compareTo(null)", instance.getClass().getName()));
+        instance.compareTo(null);
+    }
+
+    @Test
+    public void testToString() {
+        System.out.println(java.text.MessageFormat.format("{0}.toString()", instance.getClass().getName()));
+        assertEquals("2,Eddie,letmein,Nottingham,Be glad when exams are over,1993-04-05,Computers~Games~Gym~Music,1~5,0,3", instance.toString());
+    }
+
+    @Test (expected=IllegalArgumentException.class)
+    public void testCreateWSYD_MemberIAE() throws IllegalArgumentException {
+        System.out.println(java.text.MessageFormat.format("{0}.createWSYD_Member(IllegalArgumentException)", instance.getClass().getName()));
+        WSYD_Member.createWSYD_Member("too many arguments," + importCSV);
+    }
+    
+    @Test
+    public void testCreateWSYD_Member() {
+        System.out.println(java.text.MessageFormat.format("{0}.createWSYD_Member(String)", instance.getClass().getName()));
+        assertEquals(instance.toString(), WSYD_Member.createWSYD_Member(importCSV).toString());
+    }
+}
diff --git a/test/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member_Comparator_UserIDTest.java b/test/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member_Comparator_UserIDTest.java
new file mode 100644 (file)
index 0000000..a1d6f55
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 Eddie Berrisford-Lynch <n0521366@ntu.ac.uk>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs;
+
+import java.text.MessageFormat;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author TJ <hacker@iam.tj>
+ */
+public class WSYD_Member_Comparator_UserIDTest {
+    WSYD_Member_Comparator_UserID instance;
+    Long user1;
+
+    public WSYD_Member_Comparator_UserIDTest() {
+    }
+    
+    @BeforeClass
+    public static void setUpClass() {
+    }
+    
+    @AfterClass
+    public static void tearDownClass() {
+    }
+    
+    @Before
+    public void setUp() {
+        instance = new WSYD_Member_Comparator_UserID();
+        user1 = (long) 42;
+    }
+    
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of compare method, of class WSYD_Member_Comparator_UserID.
+     */
+    @Test
+    public void testCompare() {
+        System.out.println(MessageFormat.format("{0}.compare(value, value)", instance.getClass().getName()));
+        assertEquals(0, instance.compare(user1, new Long(42)));
+        assertEquals(-1, instance.compare(user1, new Long(43)));
+        assertEquals(1, instance.compare(user1, new Long(41)));
+    }
+    
+    
+    @Test (expected=NullPointerException.class)    
+    public void testNPEuser1() {
+        System.out.println(MessageFormat.format("{0}.compare(null, value)", instance.getClass().getName()));
+        instance.compare(user1, null);
+    }
+    
+    @Test (expected=NullPointerException.class)    
+    public void testNPEuser2() {
+        System.out.println(MessageFormat.format("{0}.compare(value, null)", instance.getClass().getName()));
+        instance.compare(null, user1);
+    }
+    
+    @Test (expected=NullPointerException.class)
+    public void testNPEboth() {
+        System.out.println(MessageFormat.format("{0}.compare(null,null)", instance.getClass().getName()));
+        instance.compare(null, null);
+    }
+}
diff --git a/test/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member_Comparator_UserNameTest.java b/test/uk/ac/ntu/n0521366/wsyd/libs/WSYD_Member_Comparator_UserNameTest.java
new file mode 100644 (file)
index 0000000..ecbfbae
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 Eddie Berrisford-Lynch <n0521366@ntu.ac.uk>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author TJ <hacker@iam.tj>
+ */
+public class WSYD_Member_Comparator_UserNameTest {
+    WSYD_Member_Comparator_UserName instance;
+    WSYD_Member member;
+    
+    public WSYD_Member_Comparator_UserNameTest() {
+    }
+    
+    @BeforeClass
+    public static void setUpClass() {
+    }
+    
+    @AfterClass
+    public static void tearDownClass() {
+    }
+    
+    @Before
+    public void setUp() {
+        instance = new WSYD_Member_Comparator_UserName();
+        member = new WSYD_Member();
+        member._userName = "Eddie";
+    }
+    
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of compare method, of class WSYD_Member_Comparator_UserName.
+     */
+    @Test
+    public void testCompare() {
+        System.out.println(java.text.MessageFormat.format("{0}.compare(value, value)", instance.getClass().getName()));
+        WSYD_Member l = new WSYD_Member();
+        WSYD_Member m = new WSYD_Member();
+        WSYD_Member n = new WSYD_Member();
+        WSYD_Member o = new WSYD_Member();
+        l._userName = "Below";
+        m._userName = "Eddie";
+        n._userName = "Higher";
+        o._userName = "Fit";
+        assertEquals( 3, instance.compare(member, l));
+        assertEquals( 0, instance.compare(member, m));
+        assertEquals(-3, instance.compare(member, n));
+        assertEquals(-1, instance.compare(member, o));
+    }
+
+    @Test (expected=NullPointerException.class)    
+    public void testNPEuser1() {
+        System.out.println(java.text.MessageFormat.format("{0}.compare(null, value)", instance.getClass().getName()));
+        instance.compare(member, null);
+    }
+    
+    @Test (expected=NullPointerException.class)    
+    public void testNPEuser2() {
+        System.out.println(java.text.MessageFormat.format("{0}.compare(value, null)", instance.getClass().getName()));
+        instance.compare(null, member);
+    }
+    
+    @Test (expected=NullPointerException.class)
+    public void testNPEboth() {
+        System.out.println(java.text.MessageFormat.format("{0}.compare(null,null)", instance.getClass().getName()));
+        instance.compare(null, null);
+    }
+
+}
diff --git a/test/uk/ac/ntu/n0521366/wsyd/libs/net/WSYD_SocketAddressTest.java b/test/uk/ac/ntu/n0521366/wsyd/libs/net/WSYD_SocketAddressTest.java
new file mode 100644 (file)
index 0000000..4e5f57e
--- /dev/null
@@ -0,0 +1,327 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2015 Eddie Berrisford-Lynch <n0521366@ntu.ac.uk>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.net;
+
+import java.net.InetAddress;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+import org.junit.Ignore;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.ListIterator;
+import uk.ac.ntu.n0521366.wsyd.libs.net.WSYD_SocketAddress.Protocol;
+
+/**
+ *
+ * @author TJ <hacker@iam.tj>
+ */
+public class WSYD_SocketAddressTest {
+
+    public WSYD_SocketAddressTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() {
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    @Test
+    public void testStringIPv4ToBytes4() {
+        System.out.println(MessageFormat.format("{0}.stringIPv4ToBytes4()",WSYD_SocketAddress.class.getCanonicalName()));
+        String[] ipv4_strings = {"1.2.3.4", "255.255.255.255", "0.0.0.0", "a12.2.3.4", "256.2.3.4", "-1.2.3.4", "1.2.3.4.5" };
+        long[] ipv4_values = {16909060, 4294967295L, 0, -1, -1, -1, -1};
+        String[] exceptionExpected = {null, null, null, "Not a decimal number", "Value must not be greater", "Value must not be negative", "Not a correct dotted" };
+        
+        long value;
+        byte[] bytes;
+        for (int index = 0; index < ipv4_strings.length; index++) {
+            System.out.print(MessageFormat.format("Testing {0} expecting \"{1}\"", (Object[])new String[] {ipv4_strings[index], (ipv4_values[index] != -1 ? Long.toString(ipv4_values[index]) : exceptionExpected[index])} ));
+            try { // convoluted way to convert a byte[] to an *unsigned* 32-bit integer (have to store value in a long since all Java numeric types are signed)
+                ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE/8); // SIZE is number of bits
+                bytes = WSYD_SocketAddress.stringIPv4ToBytes4(ipv4_strings[index]);
+                bb.put(bytes).rewind();
+                IntBuffer ib = bb.asIntBuffer(); // convert the 4 bytes to a 32-bit signed integer
+                value = ((long)ib.get()) & (-1L >>> 32); // extract the unsigned 32-bit integer value
+                System.out.println(MessageFormat.format(" got correct value {0} ( {1} {2} {3} {4})", value, bytes[0], bytes[1], bytes[2], bytes[3]));
+                assertEquals(ipv4_values[index], value);
+            } catch (IllegalArgumentException e) {
+                if (ipv4_values[index] == -1 && e.getMessage().startsWith(exceptionExpected[index])) {
+                    System.out.println(MessageFormat.format(" got correct exception {0} {1}", e.getClass().getName(), e.getMessage()));
+                    assertEquals(exceptionExpected[index], e.getMessage().substring(0, exceptionExpected[index].length()));
+                } else {
+                    fail(MessageFormat.format("Unexpected exception {2} {1} converting: {0}", ipv4_strings[index], e.getMessage(), e.getClass().getName()));
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testConstructorDefault() {
+        WSYD_SocketAddress instance = new WSYD_SocketAddress();
+        System.out.println(java.text.MessageFormat.format("{0}.WSYD_SocketAddress(default)", instance.getClass().getName()));
+        assertEquals(instance._isReachable, false);
+        assertEquals(instance._multicast, false);
+        assertEquals(instance._protocol, Protocol.TCP);
+        assertEquals(instance._socketAddress, null);
+    }
+
+    /**
+     * Tests the Constructor(s) that accept parameters
+     */
+    @Test
+    public void testConstructorsByValue() {
+        System.out.println(MessageFormat.format("{0}.WSYD_SocketAddress(values ...)",WSYD_SocketAddress.class.getCanonicalName()));
+
+        ArrayList<String> aS = new ArrayList(Arrays.asList(new String[] {"1.2.3.4", "0.0.0.0", "255.255.255.255", "239.192.3.4"}));
+        ArrayList<Boolean> aMulti = new ArrayList<>(Arrays.asList(new Boolean[] {false, false, false, true}));
+
+        ArrayList<Integer> aP = new ArrayList<>(Arrays.asList(new Integer[] {65535, 0, -1, 65536}));
+        ArrayList<Protocol> aPr = new ArrayList<>(Arrays.asList(new Protocol[] {Protocol.TCP, Protocol.UDP}));
+        
+        String sEx;
+        byte[] bEx;
+        InetAddress iaEx = null;
+        boolean mEx;
+        WSYD_SocketAddress wia;
+        ListIterator<String> iter = aS.listIterator();
+        while(iter.hasNext()) {
+            int index = iter.nextIndex();
+            sEx = iter.next();
+            bEx = WSYD_SocketAddress.stringIPv4ToBytes4(sEx); // byte[]
+            try {
+                iaEx = InetAddress.getByAddress(bEx); // InetAddress
+            } catch (java.net.UnknownHostException e) {
+                fail(MessageFormat.format("Got {0} whilst creating InetAddress({1})", e.getClass().getName(), sEx));
+            }
+            for (Integer p: aP) {
+                for (Protocol pr: aPr) {
+                    mEx = aMulti.get(index);
+                    if (mEx == true && pr == Protocol.TCP)
+                        mEx = false;
+                    String expecting = MessageFormat.format("Address: /{0}, Port: {1}, Protocol: {2}, MultiCast: {3}", sEx, p.toString(), pr, mEx);
+                    System.out.println(MessageFormat.format("Expecting: {0}", expecting));
+                    // Test WSYD_SocketAddress(InetAddress address, int port, Protocol protocol)
+                    try {
+                        wia = new WSYD_SocketAddress(iaEx, p, pr);
+                        System.out.println(MessageFormat.format("Got      : {0}", wia.toString()));
+                        assertEquals(expecting, wia.toString());
+                    } catch (IllegalArgumentException e) {
+                        if (!e.getMessage().startsWith("port out of range")) {
+                            fail(MessageFormat.format("Got unexcepted Exception {0}: {1}", e.getClass().getName(), e.getMessage()));
+                        }
+                        else
+                            System.out.println(MessageFormat.format("Got expected exception {0}", e.getClass().getName()));
+                    }
+                    // TODO: testConstructorsByValue() add test of WSYD_SocketAddress(InetAddress address, int port)
+                    // TODO: testConstructorsByValue() add test of WSYD_SocketAddress(InetAddress address)
+                    // TODO: testConstructorsByValue() add test of WSYD_SocketAddress(int port, Protocol protocol)
+                    // TODO: testConstructorsByValue() add test of WSYD_SocketAddress(int port)
+                }
+            }
+        }
+    }
+
+    /**
+     * Test of setAddress method, of class WSYD_SocketAddress.
+     */
+    @Ignore
+    @Test
+    public void testSetAddress() {
+        System.out.println("setAddress");
+        InetAddress address = null;
+        WSYD_SocketAddress instance = new WSYD_SocketAddress();
+        instance.setAddress(address);
+        // TODO review the generated test code and remove the default call to fail.
+        fail("The test case is a prototype.");
+    }
+
+    /**
+     * Test of getAddress method, of class WSYD_SocketAddress.
+     */
+    @Ignore
+    @Test
+    public void testGetAddress() {
+        System.out.println("getAddress");
+        WSYD_SocketAddress instance = new WSYD_SocketAddress();
+        InetAddress expResult = null;
+        InetAddress result = instance.getAddress();
+        assertEquals(expResult, result);
+        // TODO review the generated test code and remove the default call to fail.
+        fail("The test case is a prototype.");
+    }
+
+    /**
+     * Test of setPort method, of class WSYD_SocketAddress.
+     */
+    @Ignore
+    @Test
+    public void testSetPort() {
+        System.out.println("setPort");
+        int port = 0;
+        WSYD_SocketAddress instance = new WSYD_SocketAddress();
+        instance.setPort(port);
+        // TODO review the generated test code and remove the default call to fail.
+        fail("The test case is a prototype.");
+    }
+
+    /**
+     * Test of getPort method, of class WSYD_SocketAddress.
+     */
+    @Ignore
+    @Test
+    public void testGetPort() {
+        System.out.println("getPort");
+        WSYD_SocketAddress instance = new WSYD_SocketAddress();
+        int expResult = 0;
+        int result = instance.getPort();
+        assertEquals(expResult, result);
+        // TODO review the generated test code and remove the default call to fail.
+        fail("The test case is a prototype.");
+    }
+
+    /**
+     * Test of setSocketAddress method, of class WSYD_SocketAddress.
+     */
+    @Ignore
+    @Test
+    public void testSetSocketAddress() {
+        System.out.println("setSocketAddress");
+        InetAddress address = null;
+        int port = 0;
+        WSYD_SocketAddress instance = new WSYD_SocketAddress();
+        instance.setSocketAddress(address, port);
+        // TODO review the generated test code and remove the default call to fail.
+        fail("The test case is a prototype.");
+    }
+
+    /**
+     * Test of setProtocol method, of class WSYD_SocketAddress.
+     */
+    @Ignore
+    @Test
+    public void testSetProtocol() {
+        System.out.println("setProtocol");
+        WSYD_SocketAddress.Protocol protocol = null;
+        WSYD_SocketAddress instance = new WSYD_SocketAddress();
+        instance.setProtocol(protocol);
+        // TODO review the generated test code and remove the default call to fail.
+        fail("The test case is a prototype.");
+    }
+
+    /**
+     * Test of getProtocol method, of class WSYD_SocketAddress.
+     */
+    @Ignore
+    @Test
+    public void testGetProtocol() {
+        System.out.println("getProtocol");
+        WSYD_SocketAddress instance = new WSYD_SocketAddress();
+        WSYD_SocketAddress.Protocol expResult = null;
+        WSYD_SocketAddress.Protocol result = instance.getProtocol();
+        assertEquals(expResult, result);
+        // TODO review the generated test code and remove the default call to fail.
+        fail("The test case is a prototype.");
+    }
+
+    /**
+     * Test of isMulticast method, of class WSYD_SocketAddress.
+     */
+    @Ignore
+    @Test
+    public void testIsMulticast() {
+        System.out.println("isMulticast");
+        WSYD_SocketAddress instance = new WSYD_SocketAddress();
+        boolean expResult = false;
+        boolean result = instance.isMulticast();
+        assertEquals(expResult, result);
+        // TODO review the generated test code and remove the default call to fail.
+        fail("The test case is a prototype.");
+    }
+
+    /**
+     * Test of setReachable method, of class WSYD_SocketAddress.
+     */
+    @Ignore
+    @Test
+    public void testSetReachable() {
+        System.out.println("setReachable");
+        WSYD_SocketAddress instance = new WSYD_SocketAddress();
+        boolean expResult = false;
+        boolean result = instance.setReachable();
+        assertEquals(expResult, result);
+        // TODO review the generated test code and remove the default call to fail.
+        fail("The test case is a prototype.");
+    }
+
+    /**
+     * Test of getReachable method, of class WSYD_SocketAddress.
+     */
+    @Ignore
+    @Test
+    public void testGetReachable() {
+        System.out.println("getReachable");
+        WSYD_SocketAddress instance = new WSYD_SocketAddress();
+        boolean expResult = false;
+        boolean result = instance.getReachable();
+        assertEquals(expResult, result);
+        // TODO review the generated test code and remove the default call to fail.
+        fail("The test case is a prototype.");
+    }
+
+    /**
+     * Test of toString method, of class WSYD_SocketAddress.
+     */
+    @Ignore
+    @Test
+    public void testToString() {
+        System.out.println("toString");
+        WSYD_SocketAddress instance = new WSYD_SocketAddress();
+        String expResult = "";
+        String result = instance.toString();
+        assertEquals(expResult, result);
+        // TODO review the generated test code and remove the default call to fail.
+        fail("The test case is a prototype.");
+    }
+
+}