public class UNISoNController {

	/** The instance. */
	private static UNISoNController instance;

	private UNISoNGUI gui;

	/** The message queue. */
	private final LinkedBlockingQueue<NewsArticle> messageQueue;

	/** The nntp reader. */
	private NewsGroupReader nntpReader;

	/** The header downloader. */
	private HeaderDownloadWorker headerDownloader;

	/** The helper. */
	private final HibernateHelper helper;

	/** The nntp host. */
	private String nntpHost;

	/** The session. */
	private Session session;

	/** The download panel. */
	private UNISoNLogger downloadPanel;

	private final UNISoNAnalysis analysis;

	private final UNISoNDatabase database;

	private final NewsGroupFilter filter;

	private final DataHibernatorPool pool;

	private NewsClient client;

	public static UNISoNController create(final JFrame frame, final DataHibernatorPool pool)
	        throws UNISoNException {
		UNISoNController.instance = new UNISoNController(pool);
		UNISoNController.instance.setGui(new UNISoNGUI(frame));
		return UNISoNController.instance;
	}
	// private static UNISoNController instance;

	/**
	 * Gets the single instance of UNISoNController.
	 *
	 * @return single instance of UNISoNController
	 */
	public static UNISoNController getInstance() {
		return UNISoNController.instance;
	}

	/**
	 * Instantiates a new UNI so n controller.
	 *
	 * @throws UNISoNException
	 */
	private UNISoNController(final DataHibernatorPool hibernatorPool) throws UNISoNException {
		this.pool = hibernatorPool;

		this.messageQueue = new LinkedBlockingQueue<>();
		this.helper = new HibernateHelper(this.gui);
		try {
			final Session hibernateSession = this.getHelper().getHibernateSession();
			this.setSession(hibernateSession);
			this.filter = new NewsGroupFilter(hibernateSession, this.helper);
			this.analysis = new UNISoNAnalysis(this.filter, hibernateSession, this.helper);
			this.database = new UNISoNDatabase(this.filter, hibernateSession, this.helper,
			        new DataQuery(this.helper));
		}
		catch (final UNISoNException e) {
			this.getGui().showAlert("Error:" + e.getMessage());
			throw e;
		}

		this.nntpReader = new NewsGroupReader(this);
	}

	public void cancel() {
		this.getHeaderDownloader().fullstop();
		this.stopDownload();
	}

	public void download(final StatusMonitor monitor, final NewsGroup[] items,
	        final String fromDateString, final String toDateString, final UNISoNLogger logger,
	        final boolean locationSelected, final boolean getTextSelected) {
		monitor.downloadEnabled(false);

		final Set<NewsGroup> groups = new HashSet<>();
		for (final Object item : items) {
			groups.add((NewsGroup) item);
		}
		if (groups.size() > 0) {
			try {
				logger.log("Download : " + groups);
				final Date fromDate = StringUtils.stringToDate(fromDateString);
				final Date toDate = StringUtils.stringToDate(toDateString);

				DownloadMode mode;
				if (getTextSelected) {
					mode = DownloadMode.ALL;
				}
				else {
					if (locationSelected) {
						mode = DownloadMode.HEADERS;
					}
					else {
						mode = DownloadMode.BASIC;
					}
				}
				this.quickDownload(groups, fromDate, toDate, logger, mode);

				logger.log("Done.");
			}
			catch (final UNISoNException e) {
				logger.alert("Failed to download. Check your internet connection" + e.getMessage());
				monitor.downloadEnabled(true);
			}
			catch (final DateTimeParseException e) {
				logger.alert("Failed to parse date : " + e.getMessage());
				monitor.downloadEnabled(true);
			}
		}
	}

	public UNISoNAnalysis getAnalysis() {
		return this.analysis;
	}

	/**
	 * Gets the available groups model.
	 *
	 * @return the available groups model
	 */
	public ListModel<NewsGroup> getAvailableGroupsModel(final Set<NewsGroup> availableGroups2) {
		final DefaultListModel<NewsGroup> model = new DefaultListModel<>();

		if (null != availableGroups2) {
			for (final NewsGroup next : availableGroups2) {
				model.addElement(next);
			}
		}
		return model;
	}

	public UNISoNDatabase getDatabase() {
		return this.database;
	}

	/**
	 * Gets the download panel.
	 *
	 * @return the download panel
	 */
	public UNISoNLogger getDownloadPanel() {
		return this.downloadPanel;
	}

	public NewsGroupFilter getFilter() {
		return this.filter;
	}

	public UNISoNGUI getGui() {
		return this.gui;
	}

	/**
	 * Gets the header downloader.
	 *
	 * @return the header downloader
	 */
	public HeaderDownloadWorker getHeaderDownloader() {
		return this.headerDownloader;
	}

	public HibernateHelper getHelper() {
		return this.helper;
	}

	/**
	 * Gets the nntp host.
	 *
	 * @return the nntp host
	 */
	public String getNntpHost() {
		return this.nntpHost;
	}

	/**
	 * Gets the nntp reader.
	 *
	 * @return the nntp reader
	 */
	public NewsGroupReader getNntpReader() {
		return this.nntpReader;
	}

	/**
	 * Gets the queue.
	 *
	 * @return the queue
	 */
	public LinkedBlockingQueue<NewsArticle> getQueue() {
		DataHibernatorWorker.startHibernators(this.getNntpReader(), this.helper, this.messageQueue,
		        this.session);
		return this.messageQueue;
	}

	public Session getSession() {
		return this.session;
	}

	/**
	 * Helper.
	 *
	 * @return the hibernate helper
	 */
	public HibernateHelper helper() {
		return this.getHelper();
	}

	/**
	 * List newsgroups.
	 *
	 * @param searchString
	 *            the search string
	 * @param host
	 *            the host
	 * @return the sets the
	 * @throws UNISoNException
	 *             the UNI so n exception
	 */
	public Set<NewsGroup> listNewsgroups(final String searchString, final String host,
	        final NewsClient client2) throws UNISoNException {
		this.setNntpHost(host);
		return client2.listNewsGroups(searchString, host);
	}

	/**
	 * Quick download.
	 *
	 * @param groups
	 *            the groups
	 * @param fromDate1
	 *            the from date
	 * @param toDate1
	 *            the to date
	 * @param log
	 *            the log
	 * @param mode
	 *            the mode
	 * @throws UNISoNException
	 *             the UNI so n exception
	 */
	public void quickDownload(final Set<NewsGroup> groups, final Date fromDate1, final Date toDate1,
	        final UNISoNLogger log, final DownloadMode mode) throws UNISoNException {
		final NewsGroupReader reader = this.getNntpReader();
		this.client = reader.getClient();
		final HeaderDownloadWorker headerDownloader2 = this.getHeaderDownloader();
		final String nntpHost2 = this.getNntpHost();

		for (final NewsGroup group : groups) {
			try {
				this.client.reconnect();
				this.client.selectNewsgroup(group.getName());
				reader.setMessageCount(group.getArticleCount());
				headerDownloader2.initialise(reader, group.getFirstMessage(),
				        group.getLastMessage(), nntpHost2, group.getName(), log, mode, fromDate1,
				        toDate1);
			}
			catch (final IOException e) {
				e.printStackTrace();
				throw new UNISoNException(
				        "Error downloading messages. Check your internet connection: ", e);
			}
		}
	}

	public void requestDownload(final String group, final String host, final StatusMonitor monitor,
	        final UNISoNLogger logger, final NewsClient client2) {
		this.setNntpHost(host);
		logger.log("Find groups matching : " + group + " on " + host);
		monitor.downloadEnabled(false);

		if (null != group) {
			try {
				final Set<NewsGroup> availableGroups2 = this.listNewsgroups(group, host, client2);
				if ((null == availableGroups2) || (availableGroups2.size() == 0)) {
					logger.alert("No groups found for string : " + group + " on " + host
					        + ".\nPerhaps another host?");

				}
				else {
					monitor.downloadEnabled(true);
				}
				monitor.updateAvailableGroups(availableGroups2);
			}
			catch (final UNISoNException e) {
				logger.alert("Problem downloading: " + e.getMessage());
			}
		}
	}

	/**
	 * Sets the button state.
	 *
	 * @param connectButtonState
	 *            the connect button state
	 * @param downloadButtonState
	 *            the download button state
	 * @param pauseButtonState
	 *            the pause button state
	 * @param cancelButtonState
	 *            the cancel button state
	 */
	private void setButtonState(final boolean connectButtonState, final boolean downloadButtonState,
	        final boolean pauseButtonState, final boolean cancelButtonState) {
	}

	/**
	 * Sets the downloading state.
	 *
	 * @param progress
	 *            the new downloading state
	 */
	public void setDownloadingState(final int progress) {
		this.setButtonState(false, false, true, true);
	}

	/**
	 * Sets the download panel.
	 *
	 * @param downloadPanel
	 *            the new download panel
	 */
	public void setDownloadPanel(final UNISoNLogger downloadPanel) {
		this.downloadPanel = downloadPanel;
	}

	private void setGui(final UNISoNGUI gui) {
		this.gui = gui;
	}

	public void setHeaderDownloader(final HeaderDownloadWorker downloadWorker) {
		this.headerDownloader = downloadWorker;
	}

	/**
	 * Sets the idle state.
	 */
	private void setIdleState() {
		this.setButtonState(true, false, false, false);
	}

	/**
	 * Sets the nntp host.
	 *
	 * @param nntpHost
	 *            the new nntp host
	 */
	public void setNntpHost(final String nntpHost) {
		this.nntpHost = nntpHost;
		this.headerDownloader = new HeaderDownloadWorker(this.messageQueue,
		        new DownloaderImpl(this.nntpHost, this.messageQueue, this.client, this.nntpReader,
		                this.helper, this.session));
		this.headerDownloader.initialise();

	}

	public void setNntpReader(final NewsGroupReader reader) {
		this.nntpReader = reader;
	}

	public void setSession(final Session session) {
		this.session = session;
	}

	/**
	 * Stop download.
	 */
	public void stopDownload() {
		this.pool.stopAllDownloads();
		this.setIdleState();
	}

	/**
	 * Switch filtered.
	 *
	 * @param on
	 *            the on
	 */
	public void switchFiltered(final boolean on) {
		this.getFilter().setFiltered(on);
		this.getDatabase().refreshDataFromDatabase();
	}
} public interface UNISoNLogger {

	/**
	 * Alert.
	 *
	 * @param message
	 *            the message
	 */
	public void alert(String message);

	/**
	 * Log.
	 *
	 * @param message
	 *            the message
	 */
	public void log(String message);
} public enum DownloadMode {

	/** The basic. */
	BASIC,
	/** The headers. */
	HEADERS,
	/** The all. */
	ALL
} public class Location implements java.io.Serializable {

	/**
	 *
	 */
	private static final long serialVersionUID = -4937544540164453405L;

	/** The City. */

	private String City;

	/** The Country. */
	private String Country;

	/** The Country code. */
	private String CountryCode;

	/** The Guessed. */
	private boolean Guessed;

	/** The id. */
	private int id;

	/** The Ip addresses. */
	private List<IpAddress> IpAddresses = new ArrayList<>(0);

	/** The posters. */
	private List<UsenetUser> posters = new ArrayList<>(0);

	/**
	 * Instantiates a new location.
	 */
	public Location() {
	}

	/**
	 * Instantiates a new location.
	 *
	 * @param city
	 *            the city
	 * @param country
	 *            the country
	 * @param countryCode
	 *            the country code
	 * @param guessed
	 *            the guessed
	 * @param posters
	 *            the posters
	 * @param ipAddresses
	 *            the ip addresses
	 */
	public Location(final String city, final String country, final String countryCode,
	        final boolean guessed, final List<UsenetUser> posters,
	        final List<IpAddress> ipAddresses) {
		this.City = city;
		this.Country = country;
		this.CountryCode = countryCode;
		this.Guessed = guessed;
		this.posters = posters;
		this.IpAddresses = ipAddresses;
	}

	@Override
	public boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (this.getClass() != obj.getClass()) {
			return false;
		}
		final Location other = (Location) obj;
		if (this.City == null) {
			if (other.City != null) {
				return false;
			}
		}
		else if (!this.City.equals(other.City)) {
			return false;
		}
		if (this.Country == null) {
			if (other.Country != null) {
				return false;
			}
		}
		else if (!this.Country.equals(other.Country)) {
			return false;
		}
		if (this.CountryCode == null) {
			if (other.CountryCode != null) {
				return false;
			}
		}
		else if (!this.CountryCode.equals(other.CountryCode)) {
			return false;
		}
		if (this.Guessed != other.Guessed) {
			return false;
		}
		if (this.IpAddresses == null) {
			if (other.IpAddresses != null) {
				return false;
			}
		}
		else if (!this.IpAddresses.equals(other.IpAddresses)) {
			return false;
		}
		if (this.id != other.id) {
			return false;
		}
		if (this.posters == null) {
			if (other.posters != null) {
				return false;
			}
		}
		else if (!this.posters.equals(other.posters)) {
			return false;
		}
		return true;
	}

	/**
	 * Full string.
	 *
	 * @return the string
	 */
	public String fullString() {
		return this.getCity() + ", " + this.getCountry() + " (" + this.getCountryCode() + ")";
	}

	/**
	 * Gets the city.
	 *
	 * @return the city
	 */
	public String getCity() {
		return this.City;
	}

	/**
	 * Gets the country.
	 *
	 * @return the country
	 */
	public String getCountry() {
		return this.Country;
	}

	/**
	 * Gets the country code.
	 *
	 * @return the country code
	 */
	public String getCountryCode() {
		return this.CountryCode;
	}

	/**
	 * Gets the id.
	 *
	 * @return the id
	 */
	public int getId() {
		return this.id;
	}

	/**
	 * Gets the ip addresses.
	 *
	 * @return the ip addresses
	 */
	public List<IpAddress> getIpAddresses() {
		return this.IpAddresses;
	}

	/**
	 * Gets the posters.
	 *
	 * @return the posters
	 */
	public List<UsenetUser> getPosters() {
		return this.posters;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = (prime * result) + ((this.City == null) ? 0 : this.City.hashCode());
		result = (prime * result) + ((this.Country == null) ? 0 : this.Country.hashCode());
		result = (prime * result) + ((this.CountryCode == null) ? 0 : this.CountryCode.hashCode());
		result = (prime * result) + (this.Guessed ? 1231 : 1237);
		result = (prime * result) + ((this.IpAddresses == null) ? 0 : this.IpAddresses.hashCode());
		result = (prime * result) + this.id;
		result = (prime * result) + ((this.posters == null) ? 0 : this.posters.hashCode());
		return result;
	}

	/**
	 * Checks if is guessed.
	 *
	 * @return true, if is guessed
	 */
	public boolean isGuessed() {
		return this.Guessed;
	}

	/**
	 * Sets the city.
	 *
	 * @param City
	 *            the new city
	 */
	public void setCity(final String City) {
		this.City = City;
	}

	/**
	 * Sets the country.
	 *
	 * @param Country
	 *            the new country
	 */
	public void setCountry(final String Country) {
		this.Country = Country;
	}

	/**
	 * Sets the country code.
	 *
	 * @param CountryCode
	 *            the new country code
	 */
	public void setCountryCode(final String CountryCode) {
		this.CountryCode = CountryCode;
	}

	/**
	 * Sets the guessed.
	 *
	 * @param Guessed
	 *            the new guessed
	 */
	public void setGuessed(final boolean Guessed) {
		this.Guessed = Guessed;
	}

	/**
	 * Sets the id.
	 *
	 * @param id
	 *            the new id
	 */
	protected void setId(final int id) {
		this.id = id;
	}

	/**
	 * Sets the ip addresses.
	 *
	 * @param IpAddresses
	 *            the new ip addresses
	 */
	public void setIpAddresses(final List<IpAddress> IpAddresses) {
		this.IpAddresses = IpAddresses;
	}

	/**
	 * Sets the posters.
	 *
	 * @param posters
	 *            the new posters
	 */
	public void setPosters(final List<UsenetUser> posters) {
		this.posters = posters;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return this.getCity() + " - " + this.getCountryCode();
	}

} public class Message implements java.io.Serializable {

	/**
	 *
	 */
	private static final long serialVersionUID = -4828381724935020136L;

	/** The Date created. */
	private Date dateCreated;

	/** The id. */
	private int id;

	/** The Message body. */
	private byte[] messageBody;

	/** The newsgroups. */
	private Set<NewsGroup> newsgroups = new HashSet<>(0);

	/** The poster. */
	private UsenetUser poster;

	/** The referenced messages. */
	private String referencedMessages;

	/** The Subject. */
	private String subject;

	/** The topic. */
	private Topic topic;

	/** The Usenet message id. */
	private String usenetMessageID;

	Log logger = LogFactory.getLog(Message.class);

	/**
	 * Instantiates a new message.
	 */
	public Message() {
	}

	/**
	 * Instantiates a new message.
	 *
	 * @param dateCreated
	 *            the date created
	 * @param usenetMessageID
	 *            the usenet message id
	 * @param subject
	 *            the subject
	 * @param poster
	 *            the poster
	 * @param topic
	 *            the topic
	 * @param newsgroups
	 *            the newsgroups
	 * @param referencedMessages
	 *            the referenced messages
	 * @param messageBody
	 *            the message body
	 */
	public Message(final Date dateCreated, final String usenetMessageID, final String subject,
	        final UsenetUser poster, final Topic topic, final Set<NewsGroup> newsgroups,
	        final String referencedMessages, final byte[] messageBody) {
		this.dateCreated = dateCreated;
		this.usenetMessageID = usenetMessageID;
		this.subject = subject;
		this.poster = poster;
		this.topic = topic;
		this.newsgroups = newsgroups;
		this.referencedMessages = referencedMessages;
		this.messageBody = messageBody;
	}

	@Override
	public boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			this.logger.debug("thatOne is null");
			return false;
		}
		if (this.getClass() != obj.getClass()) {
			this.logger.debug("thatOne is a " + obj.getClass().getName());
			return false;
		}
		final Message other = (Message) obj;
		if (this.dateCreated == null) {
			if (other.dateCreated != null) {
				this.logger.debug("thatOne doesn't have null dateCreated");
				return false;
			}
		}
		else if (!this.dateCreated.equals(other.dateCreated)) {
			this.logger.debug("thatOne has a different dateCreated");
			return false;
		}
		if (this.id != other.id) {
			this.logger.debug("thatOne has different Id");

			return false;
		}
		if (!Arrays.equals(this.messageBody, other.messageBody)) {
			this.logger.debug("thatOne has a different body");
			return false;
		}
		if (this.newsgroups == null) {
			if (other.newsgroups != null) {
				return false;
			}
		}
		else if (!this.newsgroups.equals(other.newsgroups)) {
			return false;
		}
		if (this.poster == null) {
			if (other.poster != null) {
				return false;
			}
		}
		else if (!this.poster.equals(other.poster)) {
			this.logger.debug("thatOne has different poster" + this.poster + ":" + other.poster);
			return false;
		}
		if (this.referencedMessages == null) {
			if (other.referencedMessages != null) {
				return false;
			}
		}
		else if (!this.referencedMessages.equals(other.referencedMessages)) {
			return false;
		}
		if (this.subject == null) {
			if (other.subject != null) {
				return false;
			}
		}
		else if (!this.subject.equals(other.subject)) {
			return false;
		}
		if (this.topic == null) {
			if (other.topic != null) {
				return false;
			}
		}
		else if (!this.topic.equals(other.topic)) {
			return false;
		}
		if (this.usenetMessageID == null) {
			if (other.usenetMessageID != null) {
				return false;
			}
		}
		else if (!this.usenetMessageID.equals(other.usenetMessageID)) {
			return false;
		}
		return true;
	}

	/**
	 * Gets the date created.
	 *
	 * @return the date created
	 */
	public Date getDateCreated() {
		return this.dateCreated;
	}

	/**
	 * Gets the id.
	 *
	 * @return the id
	 */
	public int getId() {
		return this.id;
	}

	/**
	 * Gets the message body.
	 *
	 * @return the message body
	 */
	public byte[] getMessageBody() {
		return this.messageBody;
	}

	/**
	 * Gets the newsgroups.
	 *
	 * @return the newsgroups
	 */
	public Set<NewsGroup> getNewsgroups() {
		return this.newsgroups;
	}

	/**
	 * Gets the poster.
	 *
	 * @return the poster
	 */
	public UsenetUser getPoster() {
		return this.poster;
	}

	/**
	 * Gets the referenced messages.
	 *
	 * @return the referenced messages
	 */
	public String getReferencedMessages() {
		return this.referencedMessages;
	}

	/**
	 * Gets the subject.
	 *
	 * @return the subject
	 */
	public String getSubject() {
		return this.subject;
	}

	/**
	 * Gets the topic.
	 *
	 * @return the topic
	 */
	public Topic getTopic() {
		return this.topic;
	}

	/**
	 * Gets the usenet message id.
	 *
	 * @return the usenet message id
	 */
	public String getUsenetMessageID() {
		return this.usenetMessageID;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = (prime * result) + ((this.dateCreated == null) ? 0 : this.dateCreated.hashCode());
		result = (prime * result) + this.id;
		result = (prime * result) + Arrays.hashCode(this.messageBody);
		result = (prime * result) + ((this.newsgroups == null) ? 0 : this.newsgroups.hashCode());
		result = (prime * result) + ((this.poster == null) ? 0 : this.poster.hashCode());
		result = (prime * result)
		        + ((this.referencedMessages == null) ? 0 : this.referencedMessages.hashCode());
		result = (prime * result) + ((this.subject == null) ? 0 : this.subject.hashCode());
		result = (prime * result) + ((this.topic == null) ? 0 : this.topic.hashCode());
		result = (prime * result)
		        + ((this.usenetMessageID == null) ? 0 : this.usenetMessageID.hashCode());
		return result;
	}

	/**
	 * Sets the date created.
	 *
	 * @param DateCreated
	 *            the new date created
	 */
	public void setDateCreated(final Date DateCreated) {
		this.dateCreated = DateCreated;
	}

	/**
	 * Sets the id.
	 *
	 * @param id
	 *            the new id
	 */
	protected void setId(final int id) {
		this.id = id;
	}

	/**
	 * Sets the message body.
	 *
	 * @param MessageBody
	 *            the new message body
	 */
	public void setMessageBody(final byte[] MessageBody) {
		this.messageBody = MessageBody;
	}

	/**
	 * Sets the newsgroups.
	 *
	 * @param newsgroups
	 *            the new newsgroups
	 */
	public void setNewsgroups(final Set<NewsGroup> newsgroups) {
		this.newsgroups = newsgroups;
	}

	/**
	 * Sets the poster.
	 *
	 * @param poster
	 *            the new poster
	 */
	public void setPoster(final UsenetUser poster) {
		this.poster = poster;
	}

	/**
	 * Sets the referenced messages.
	 *
	 * @param referencedMessages
	 *            the new referenced messages
	 */
	public void setReferencedMessages(final String referencedMessages) {
		this.referencedMessages = referencedMessages;
	}

	/**
	 * Sets the subject.
	 *
	 * @param Subject
	 *            the new subject
	 */
	public void setSubject(final String Subject) {
		this.subject = Subject;
	}

	/**
	 * Sets the topic.
	 *
	 * @param topic
	 *            the new topic
	 */
	public void setTopic(final Topic topic) {
		this.topic = topic;
	}

	/**
	 * Sets the usenet message id.
	 *
	 * @param UsenetMessageID
	 *            the new usenet message id
	 */
	public void setUsenetMessageID(final String UsenetMessageID) {
		this.usenetMessageID = UsenetMessageID;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return this.getSubject() + ":" + this.poster;
	}

} public class NewsGroup implements java.io.Serializable, Comparable<NewsGroup> {
	/**
	 *
	 */
	private static final long	serialVersionUID	= -8526704874026184061L;
	/** The first message. */

	private int					firstMessage;

	/** The full name. */
	private String fullName;

	/** The id. */
	private int id;

	/** The last index downloaded. */
	private int lastIndexDownloaded;

	/** The last message. */
	private int lastMessage;

	/** The last message total. */
	private int lastMessageTotal;

	/** The last node. */
	private boolean lastNode;

	/** Messages in this NewsGroup. */
	private Set<Message> messages = new HashSet<>(0);

	/** The name. */
	private String name;

	/** The parent news group. */
	private NewsGroup parentNewsGroup;

	/** Message Threads in this NewsGroup. */
	private Set<Topic> topics = new HashSet<>(0);

	private int estimatedArticleCount;

	/**
	 * Instantiates a new news group.
	 */
	public NewsGroup() {
	}

	@SuppressWarnings("deprecation")
	public NewsGroup(final NewsgroupInfo original) {
		this.estimatedArticleCount = original.getArticleCount();
		this.firstMessage = original.getFirstArticle();
		this.lastMessage = original.getLastArticle();
		this.name = original.getNewsgroup();
		this.fullName = this.name;
	}

	/**
	 * Instantiates a new news group.
	 *
	 * @param name
	 *            the name
	 * @param parentNewsGroup
	 *            the parent news group
	 * @param topics
	 *            the topics
	 * @param messages
	 *            the messages
	 * @param lastIndexDownloaded
	 *            the last index downloaded
	 * @param lastMessageTotal
	 *            the last message total
	 * @param firstMessage
	 *            the first message
	 * @param lastMessage
	 *            the last message
	 * @param fullName
	 *            the full name
	 * @param lastNode
	 *            the last node
	 */
	public NewsGroup(final String name, final NewsGroup parentNewsGroup, final Set<Topic> topics,
	        final Set<Message> messages, final int lastIndexDownloaded, final int lastMessageTotal,
	        final int firstMessage, final int lastMessage, final String fullName,
	        final boolean lastNode) {
		this.name = name;
		this.parentNewsGroup = parentNewsGroup;
		this.topics = topics;
		this.messages = messages;
		this.lastIndexDownloaded = lastIndexDownloaded;
		this.lastMessageTotal = lastMessageTotal;
		this.lastMessage = lastMessage;
		this.firstMessage = firstMessage;

		this.fullName = fullName;
		this.lastNode = lastNode;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	@Override
	public int compareTo(final NewsGroup that) {
		return this.getName().compareTo(that.getName());
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(final Object object) {
		if (!(object instanceof NewsGroup)) {
			return false;
		}
		final NewsGroup that = (NewsGroup) object;
		return (this.getFullName().equals(that.getFullName()));
	}

	public int getArticleCount() {
		final int total = this.lastMessage - this.firstMessage;
		if (total < this.estimatedArticleCount) {
			return total;
		}
		return this.estimatedArticleCount;
	}

	/**
	 * Gets the first message.
	 *
	 * @return the first message
	 */
	public int getFirstMessage() {
		return this.firstMessage;
	}

	/**
	 * Gets the full name.
	 *
	 * @return the full name
	 */
	public String getFullName() {
		return this.fullName;
	}

	/**
	 * Gets the id.
	 *
	 * @return the id
	 */
	public int getId() {
		return this.id;
	}

	/**
	 * Gets the last index downloaded.
	 *
	 * @return the last index downloaded
	 */
	public int getLastIndexDownloaded() {
		return this.lastIndexDownloaded;
	}

	/**
	 * Gets the last message.
	 *
	 * @return the last message
	 */
	public int getLastMessage() {
		return this.lastMessage;
	}

	/**
	 * Gets the last message total.
	 *
	 * @return the last message total
	 */
	public int getLastMessageTotal() {
		return this.lastMessageTotal;
	}

	/**
	 * * Messages in this NewsGroup.
	 *
	 * @return the messages
	 */
	public Set<Message> getMessages() {
		return this.messages;
	}

	/**
	 * Gets the name.
	 *
	 * @return the name
	 */
	public String getName() {
		return this.name;
	}

	/**
	 * Gets the parent news group.
	 *
	 * @return the parent news group
	 */
	public NewsGroup getParentNewsGroup() {
		return this.parentNewsGroup;
	}

	/**
	 * * Message Threads in this NewsGroup.
	 *
	 * @return the topics
	 */
	public Set<Topic> getTopics() {
		return this.topics;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		int hashCode = 0;
		hashCode = (29 * hashCode) + this.id;

		return hashCode;
	}

	/**
	 * Checks if is last node.
	 *
	 * @return true, if is last node
	 */
	public boolean isLastNode() {
		return this.lastNode;
	}

	public void setArticleCount(final int i) {
		this.estimatedArticleCount = i;
	}

	/**
	 * Sets the first message.
	 *
	 * @param firstMessage
	 *            the new first message
	 */
	public void setFirstMessage(final int firstMessage) {
		this.firstMessage = firstMessage;
	}

	/**
	 * Sets the full name.
	 *
	 * @param fullName
	 *            the new full name
	 */
	public void setFullName(final String fullName) {
		this.fullName = fullName;
	}

	/**
	 * Sets the id.
	 *
	 * @param id
	 *            the new id
	 */
	protected void setId(final int id) {
		this.id = id;
	}

	/**
	 * Sets the last index downloaded.
	 *
	 * @param lastIndexDownloaded
	 *            the new last index downloaded
	 */
	public void setLastIndexDownloaded(final int lastIndexDownloaded) {
		this.lastIndexDownloaded = lastIndexDownloaded;
	}

	/**
	 * Sets the last message.
	 *
	 * @param lastMessage
	 *            the new last message
	 */
	public void setLastMessage(final int lastMessage) {
		this.lastMessage = lastMessage;
	}

	/**
	 * Sets the last message total.
	 *
	 * @param lastMessageTotal
	 *            the new last message total
	 */
	public void setLastMessageTotal(final int lastMessageTotal) {
		this.lastMessageTotal = lastMessageTotal;
	}

	/**
	 * Sets the last node.
	 *
	 * @param lastNode
	 *            the new last node
	 */
	public void setLastNode(final boolean lastNode) {
		this.lastNode = lastNode;
	}

	/**
	 * Sets the messages.
	 *
	 * @param messages
	 *            the new messages
	 */
	public void setMessages(final Set<Message> messages) {
		this.messages = messages;
	}

	/**
	 * Sets the name.
	 *
	 * @param name
	 *            the new name
	 */
	public void setName(final String name) {
		this.name = name;
	}

	/**
	 * Sets the parent news group.
	 *
	 * @param parentNewsGroup
	 *            the new parent news group
	 */
	public void setParentNewsGroup(final NewsGroup parentNewsGroup) {
		this.parentNewsGroup = parentNewsGroup;
	}

	/**
	 * Sets the topics.
	 *
	 * @param topics
	 *            the new topics
	 */
	public void setTopics(final Set<Topic> topics) {
		this.topics = topics;
	}

	/**
	 * toString.
	 *
	 * @return String
	 */
	@Override
	public String toString() {
		return this.getFullName() + "(" + this.getArticleCount() + ")";
	}

} public class ResultRow implements Comparable<ResultRow> {

	/** The key. */
	private final Object key;

	/** The type. */
	private final Class<?> type;

	/** The count. */
	private final int count;

	/**
	 * Instantiates a new result row.
	 *
	 * @param key
	 *            the key
	 * @param count
	 *            the count
	 * @param type
	 *            the type
	 */
	public ResultRow(final Object key, final int count, final Class<?> type) {
		this.key = key;
		this.type = type;
		this.count = count;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	@Override
	public int compareTo(final ResultRow that) {
		// reverse order to get top ones first
		return -(this.count - that.count);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (this.getClass() != obj.getClass()) {
			return false;
		}
		final ResultRow other = (ResultRow) obj;
		if (this.count != other.count) {
			return false;
		}
		if (this.key == null) {
			if (other.key != null) {
				return false;
			}
		}
		else if (!this.key.equals(other.key)) {
			return false;
		}
		return true;
	}

	/**
	 * Gets the count.
	 *
	 * @return the count
	 */
	public int getCount() {
		return this.count;
	}

	/**
	 * Gets the key.
	 *
	 * @return the key
	 */
	public Object getKey() {
		return this.key;
	}

	/**
	 * Gets the type.
	 *
	 * @return the type
	 */
	public Class<?> getType() {
		return this.type;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = (prime * result) + this.count;
		result = (prime * result) + (this.key == null ? 0 : this.key.hashCode());
		return result;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return this.getCount() + " " + this.getKey();
	}

} public class Topic implements java.io.Serializable {

	/**
	 *
	 */
	private static final long serialVersionUID = -4646650675535168051L;

	/** The id. */
	private int id;

	/** The newsgroups. */
	private Set<NewsGroup> newsgroups = new HashSet<>(0);

	/** The subject. */
	private String subject;

	/**
	 * Instantiates a new topic.
	 */
	public Topic() {
	}

	/**
	 * Instantiates a new topic.
	 *
	 * @param subject
	 *            the subject
	 * @param newsgroups
	 *            the newsgroups
	 */
	public Topic(final String subject, final Set<NewsGroup> newsgroups) {
		this.subject = subject;
		this.newsgroups = newsgroups;
	}

	public Topic(final Topic topic) {
		this(topic.subject, topic.newsgroups);
		this.id = topic.id;
	}

	@Override
	public boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (this.getClass() != obj.getClass()) {
			return false;
		}
		final Topic other = (Topic) obj;
		if (this.id != other.id) {
			return false;
		}
		if (this.newsgroups == null) {
			if (other.newsgroups != null) {
				return false;
			}
		}
		else if (!this.newsgroups.equals(other.newsgroups)) {
			return false;
		}
		if (this.subject == null) {
			if (other.subject != null) {
				return false;
			}
		}
		else if (!this.subject.equals(other.subject)) {
			return false;
		}
		return true;
	}

	/**
	 * Gets the id.
	 *
	 * @return the id
	 */
	public int getId() {
		return this.id;
	}

	/**
	 * Gets the newsgroups.
	 *
	 * @return the newsgroups
	 */
	public Set<NewsGroup> getNewsgroups() {
		return this.newsgroups;
	}

	/**
	 * Gets the subject.
	 *
	 * @return the subject
	 */
	public String getSubject() {
		return this.subject;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = (prime * result) + this.id;
		result = (prime * result) + ((this.newsgroups == null) ? 0 : this.newsgroups.hashCode());
		result = (prime * result) + ((this.subject == null) ? 0 : this.subject.hashCode());
		return result;
	}

	/**
	 * Sets the id.
	 *
	 * @param id
	 *            the new id
	 * @return
	 */
	protected Topic setId(final int id) {
		this.id = id;
		return this;
	}

	/**
	 * Sets the newsgroups.
	 *
	 * @param newsgroups
	 *            the new newsgroups
	 * @return
	 */
	public Topic setNewsgroups(final Set<NewsGroup> newsgroups) {
		this.newsgroups = newsgroups;
		return this;
	}

	/**
	 * Sets the subject.
	 *
	 * @param subject
	 *            the new subject
	 * @return
	 */
	public Topic setSubject(final String subject) {
		this.subject = subject;
		return this;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return this.getSubject();
	}

} public class UsenetUser implements java.io.Serializable {

	/**
	 *
	 */
	private static final long serialVersionUID = 6240031352036083751L;

	/** The email. */
	private String email;

	/** The gender. */
	private String gender;

	/** The id. */
	private int id;

	/** The ipaddress. */
	private String ipaddress;

	/** The location. */
	private Location location;

	/** The name. */
	private String name;

	/**
	 * Instantiates a new usenet user.
	 */
	public UsenetUser() {
	}

	/**
	 * Instantiates a new usenet user.
	 *
	 * @param name
	 *            the name
	 * @param email
	 *            the email
	 * @param ipaddress
	 *            the ipaddress
	 * @param gender
	 *            the gender
	 * @param location
	 *            the location
	 */
	public UsenetUser(final String name, final String email, final String ipaddress,
	        final String gender, final Location location) {
		this.name = name;
		this.email = email;
		this.ipaddress = ipaddress;
		this.gender = gender;
		this.location = location;
	}

	public UsenetUser(final UsenetUser poster) {
		this(poster.name, poster.email, poster.ipaddress, poster.gender, poster.location);
	}

	@Override
	public boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (this.getClass() != obj.getClass()) {
			return false;
		}
		final UsenetUser other = (UsenetUser) obj;
		if (this.email == null) {
			if (other.email != null) {
				return false;
			}
		}
		else if (!this.email.equals(other.email)) {
			return false;
		}
		if (this.gender == null) {
			if (other.gender != null) {
				return false;
			}
		}
		else if (!this.gender.equals(other.gender)) {
			return false;
		}
		if (this.id != other.id) {
			return false;
		}
		if (this.ipaddress == null) {
			if (other.ipaddress != null) {
				return false;
			}
		}
		else if (!this.ipaddress.equals(other.ipaddress)) {
			return false;
		}
		if (this.location == null) {
			if (other.location != null) {
				return false;
			}
		}
		else if (!this.location.equals(other.location)) {
			return false;
		}
		if (this.name == null) {
			if (other.name != null) {
				return false;
			}
		}
		else if (!this.name.equals(other.name)) {
			return false;
		}
		return true;
	}

	/**
	 * Gets the email.
	 *
	 * @return the email
	 */
	public String getEmail() {
		return this.email;
	}

	/**
	 * Gets the gender.
	 *
	 * @return the gender
	 */
	public String getGender() {
		return this.gender;
	}

	/**
	 * Gets the id.
	 *
	 * @return the id
	 */
	public int getId() {
		return this.id;
	}

	/**
	 * Gets the ipaddress.
	 *
	 * @return the ipaddress
	 */
	public String getIpaddress() {
		return this.ipaddress;
	}

	/**
	 * Gets the location.
	 *
	 * @return the location
	 */
	public Location getLocation() {
		return this.location;
	}

	/**
	 * Gets the name.
	 *
	 * @return the name
	 */
	public String getName() {
		return this.name;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = (prime * result) + ((this.email == null) ? 0 : this.email.hashCode());
		result = (prime * result) + ((this.gender == null) ? 0 : this.gender.hashCode());
		result = (prime * result) + this.id;
		result = (prime * result) + ((this.ipaddress == null) ? 0 : this.ipaddress.hashCode());
		result = (prime * result) + ((this.location == null) ? 0 : this.location.hashCode());
		result = (prime * result) + ((this.name == null) ? 0 : this.name.hashCode());
		return result;
	}

	/**
	 * Sets the email.
	 *
	 * @param email
	 *            the new email
	 * @return
	 */
	public UsenetUser setEmail(final String email) {
		this.email = email;
		return this;
	}

	/**
	 * Sets the gender.
	 *
	 * @param gender
	 *            the new gender
	 * @return
	 */
	public UsenetUser setGender(final String gender) {
		this.gender = gender;
		return this;
	}

	/**
	 * Sets the id.
	 *
	 * @param id
	 *            the new id
	 * @return
	 */
	protected UsenetUser setId(final int id) {
		this.id = id;
		return this;
	}

	/**
	 * Sets the ipaddress.
	 *
	 * @param ipaddress
	 *            the new ipaddress
	 * @return
	 */
	public UsenetUser setIpaddress(final String ipaddress) {
		this.ipaddress = ipaddress;
		return this;
	}

	/**
	 * Sets the location.
	 *
	 * @param location
	 *            the new location
	 * @return
	 */
	public UsenetUser setLocation(final Location location) {
		this.location = location;
		return this;
	}

	/**
	 * Sets the name.
	 *
	 * @param name
	 *            the new name
	 * @return
	 */
	public UsenetUser setName(final String name) {
		this.name = name;
		return this;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return this.getName() + "(" + this.getEmail() + ")";
	}

} public class FullDownloadWorker extends SwingWorker {

	/** The download queue. */
	private static LinkedBlockingQueue<DownloadRequest> downloadQueue = new LinkedBlockingQueue<>();

	/** The log. */
	private static UNISoNLogger log;

	/** The Constant downloaders. */
	private final static ArrayList<FullDownloadWorker> downloaders = new ArrayList<>();

	/** The client. */
	private final NewsClient client;

	/** The download. */
	private boolean download = true;

	private final LinkedBlockingQueue<NewsArticle> outQueue;

	private final UNISoNController controller;

	/**
	 * Adds the download request.
	 *
	 * @Deprecated move to DownloaderImpl if we can
	 * @param usenetID
	 *            the usenet id
	 * @param mode
	 *            the mode
	 * @param log1
	 *            the log
	 * @param nntpReader
	 * @param helper
	 * @param session
	 * @throws UNISoNException
	 *             the UNI so n exception
	 */

	public synchronized static void addDownloadRequest(final String usenetID,
	        final DownloadMode mode, final UNISoNLogger log1, final String nntpHost,
	        final LinkedBlockingQueue<NewsArticle> queue, final NewsClient newsClient,
	        final NewsGroupReader nntpReader, final HibernateHelper helper, final Session session)
	                throws UNISoNException {
		final DownloadRequest request = new DownloadRequest(usenetID, mode);

		FullDownloadWorker.log = log1;
		FullDownloadWorker.downloadQueue.add(request);
		if (FullDownloadWorker.downloaders.size() < 1) {
			FullDownloadWorker.startDownloaders(1, nntpHost, queue, newsClient);
		}
		DataHibernatorWorker.startHibernators(nntpReader, helper, queue, session);
	}

	/**
	 * s Queue size.
	 *
	 * @return the int
	 */
	static int queueSize() {
		return FullDownloadWorker.downloadQueue.size();
	}

	/**
	 * Start downloaders.
	 *
	 * @param numberOfDownloaders
	 *            the number of downloaders
	 * @throws UNISoNException
	 *             the UNI so n exception
	 */
	private static void startDownloaders(final int numberOfDownloaders, final String host,
	        final LinkedBlockingQueue<NewsArticle> queue, final NewsClient newsClient)
	                throws UNISoNException {
		for (int i = 0; i < numberOfDownloaders; i++) {

			FullDownloadWorker.downloaders.add(new FullDownloadWorker(host, queue, newsClient));
		}
	}

	/**
	 * Instantiates a new full download worker.
	 *
	 * @param server
	 *            the server
	 * @param outQueue
	 *            the out queue
	 * @param newsClient
	 * @throws UNISoNException
	 *             the UNI so n exception
	 */
	FullDownloadWorker(final String server, final LinkedBlockingQueue<NewsArticle> outQueue,
	        final NewsClient newsClient) throws UNISoNException {
		super("FullDownload");
		this.outQueue = outQueue;
		this.client = newsClient;
		this.controller = UNISoNController.getInstance();
		try {
			this.client.connect(server);
		}
		catch (final IOException e) {
			throw new UNISoNException("Failed to connect", e);
		}

		this.start();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see uk.co.sleonard.unison.input.SwingWorker#construct()
	 */
	@Override
	public Object construct() {
		System.out.println("construct");

		try {
			while (this.download) {
				while (FullDownloadWorker.downloadQueue.size() > 0) {
					this.storeNextMessage(this.outQueue);
				}
				// Wait a bit. If no messages then close downloader
				Thread.sleep(5000);
				if ((FullDownloadWorker.downloadQueue.size() == 0)
				        && (FullDownloadWorker.downloaders.size() > 1)) {
					this.download = false;
				}
			}
		}
		catch (@SuppressWarnings("unused") final InterruptedException e) {
			return "Interrupted";
		}
		catch (final UNISoNException e) {
			this.controller.getGui().showAlert("Error in download:" + e);
			e.printStackTrace();
			return "FAIL";
		}
		return "Completed";
	}

	/**
	 * Convert header string to article.
	 *
	 * @param theInfo
	 *            the the info
	 * @return the news article
	 * @throws UNISoNException
	 *             the UNI so n exception
	 */
	NewsArticle convertHeaderStringToArticle(final String theInfo) throws UNISoNException {
		final HashMap<String, String> headerFields = new HashMap<>();

		final StringTokenizer st = new StringTokenizer(theInfo, "\n");
		String key = null;
		String value = null;

		// Skip these as they can contain illegal characters and we don't need
		// them anyway
		final List<String> ignoreList = new ArrayList<>();
		ignoreList.add("X-FACE");
		ignoreList.add("FACE");
		ignoreList.add("CANCEL-LOCK");

		boolean body = false;
		final StringBuffer bodyBuffer = new StringBuffer();

		while (st.hasMoreTokens()) {
			final String row = st.nextToken();

			if (row.startsWith(" ")) {
				value += "," + row;
			}
			else {
				if ((null != key) && !ignoreList.contains(key)) {
					headerFields.put(key, value);
				}
				final int indexOf = row.indexOf(":");
				if (indexOf > -1) {
					key = row.substring(0, indexOf);
				}
				else {
					value += "," + row;
				}

				// System.out.println("KEY: " + key + " ROW:" + row);

				// this is the message body
				if (key.equalsIgnoreCase("X-Received-Date")) {
					body = true;
				}
				else if (body) {
					bodyBuffer.append(row + "\n");
				}
				if ((null != key) && !ignoreList.contains(key)) {
					value = row.replaceFirst(key + ":", "").trim();
				}
			}
			if (null != key) {
				// Store with upper case as NNTP servers differs
				headerFields.put(key.toUpperCase(), value);
			}
		}
		// System.out.println("MAP:" + headerFields);
		final int messageNumber = -1;
		final String references = headerFields.get("REFERENCES");
		Date date;
		try {
			date = StringUtils.stringToDate(headerFields.get("DATE"));
		}
		catch (final DateTimeParseException e) {
			throw new UNISoNException("Failed to parse date", e);
		}

		final NewsArticle article = new NewsArticle(headerFields.get("MESSAGE-ID"), messageNumber,
		        date, headerFields.get("FROM"), headerFields.get("SUBJECT"), references,
		        bodyBuffer.toString(), headerFields.get("NEWSGROUPS"),
		        headerFields.get("NNTP-POSTING-HOST"));

		if (body) {
			article.setContent(bodyBuffer.toString());
		}
		return article;
	}

	/**
	 * Convert reader to article.
	 *
	 * @param reader
	 *            the reader
	 * @return the news article
	 * @throws UNISoNException
	 *             the UNI so n exception
	 */
	NewsArticle convertReaderToArticle(final Reader reader) throws UNISoNException {
		if (reader != null) {
			final String theInfo = this.readerToString(reader);
			return this.convertHeaderStringToArticle(theInfo);

		}
		return null;
	}

	/**
	 * Download article.
	 *
	 * @param request
	 *            the request
	 * @return the news article
	 * @throws UNISoNException
	 *             the UNI so n exception
	 */
	NewsArticle downloadArticle(final DownloadRequest request) throws UNISoNException {
		NewsArticle article = null;
		try {
			switch (request.getMode()) {
				case HEADERS:
					article = this.downloadHeader(request);
					break;
				case ALL:
					article = this.downloadFullMessage(request);
					break;
				case BASIC:
					break;
				default:
					break;
			}
		}
		catch (final MalformedServerReplyException e) {
			throw new UNISoNException("Failed to download message:" + e.getMessage(), e);
		}
		catch (final Throwable e) {
			throw new UNISoNException("Failed to download message:", e);
		}

		if (null == article) {
			FullDownloadWorker.log.log("Skipped message " + request.getUsenetID());
		}
		return article;
	}

	/**
	 * Download full message.
	 *
	 * @param request
	 *            the request
	 * @return the news article
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 * @throws UNISoNException
	 *             the UNI so n exception
	 */
	NewsArticle downloadFullMessage(final DownloadRequest request)
	        throws IOException, UNISoNException {
		try (Reader reader = this.client.retrieveArticle(request.getUsenetID());) {
			NewsArticle article = null;
			if (null != reader) {
				article = this.convertReaderToArticle(reader);
			}
			else {
				System.err.println("No message returned");
			}
			return article;
		}
	}

	/**
	 * Download header.
	 *
	 * @param request
	 *            the request
	 * @return the news article
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 * @throws UNISoNException
	 *             the UNI so n exception
	 */
	private NewsArticle downloadHeader(final DownloadRequest request)
	        throws IOException, UNISoNException {
		try (Reader reader = this.client.retrieveArticleHeader(request.getUsenetID());) {
			NewsArticle article = null;
			if (null != reader) {
				article = this.convertReaderToArticle(reader);
			}
			else {
				System.err.println("No message returned");
			}
			return article;
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see uk.co.sleonard.unison.input.SwingWorker#finished()
	 */
	@Override
	public void finished() {
		FullDownloadWorker.downloaders.remove(this);
		if (FullDownloadWorker.downloaders.size() == 0) {
			FullDownloadWorker.log.alert("Download of extra fields complete");
		}
	}

	/**
	 * Poll queue.
	 *
	 * @return the download request
	 */
	private synchronized DownloadRequest pollQueue() {
		final DownloadRequest request = FullDownloadWorker.downloadQueue.poll();
		return request;
	}

	/**
	 * Convert a {@link Reader} instance to a String.
	 *
	 * @param reader
	 *            The Reader instance
	 * @return String
	 */
	private String readerToString(final Reader reader) {
		String temp = null;
		StringBuffer sb = null;
		final BufferedReader bufReader = new BufferedReader(reader);

		sb = new StringBuffer();
		try {
			temp = bufReader.readLine();
			while (temp != null) {
				sb.append(temp);
				sb.append("\n");
				temp = bufReader.readLine();
			}
		}
		catch (final IOException e) {
			e.printStackTrace();
		}

		return sb.toString();
	}

	/**
	 * Store next message.
	 *
	 * @return true, if successful
	 * @throws UNISoNException
	 *             the UNI so n exception
	 */
	private boolean storeNextMessage(final LinkedBlockingQueue<NewsArticle> queue)
	        throws UNISoNException {
		final DownloadRequest request = this.pollQueue();

		final NewsArticle article = this.downloadArticle(request);
		if (null != article) {
			FullDownloadWorker.log.log("Got:" + article.getSubject() + " " + article.getFrom() + " "
			        + article.getArticleId() + " to q: " + queue.size() + "[" + new Date());

			queue.add(article);
			return true;
		}
		return false;
	}
} public class NewsClientImpl implements NewsClient {

	/** The logger. */
	private static Logger	logger	= Logger.getLogger("NewsClient");
	/** The host. */
	private String			host;

	/** The message count. */
	private int					messageCount;
	private final NNTPClient	client;

	/**
	 * Instantiates a new news client.
	 */
	public NewsClientImpl() {
		this(new NNTPClient());
	}

	public NewsClientImpl(final NNTPClient nntpClient) {
		this.client = nntpClient;
	}

	/**
	 * Connect to the news server with default settings and anonymous login.
	 *
	 * @param server
	 *            the server
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	@Override
	public void connect(final String server) throws IOException {
		final int port = 119;
		final String username = null;
		final String password = null;
		try {
			this.connect(server, port, username, password);
		}
		catch (final UNISoNException e) {
			throw new IOException(e);
		}
	}

	@Override
	public void connect(final String host1, final int port) throws SocketException, IOException {
		this.client.connect(host1, port);
	}

	/**
	 * Connect.
	 *
	 * @param server
	 *            the server
	 * @param port
	 *            the port
	 * @param username
	 *            the username
	 * @param password
	 *            the password
	 * @throws UNISoNException
	 *             the UNI so n exception
	 */
	@Override
	public void connect(final String server, final int port, final String username,
	        final String password) throws UNISoNException {
		try {
			if ((null == this.host) || !this.host.equals(server) || !this.isConnected()) {
				// Connect to the news server
				this.connect(server, port);

				// See if we need to authenticate
				if (username != null) {
					this.client.authenticate(username, password);
				}
				this.host = server;
			}
		}
		catch (final ConnectException e) {
			throw new UNISoNException(
			        "Connection refused. \n" + "Check your settings are correct: server " + server
			                + " username " + username + " password " + password,
			        e);
		}
		catch (final UnknownHostException e) {
			throw new UNISoNException(
			        "Connection refused. \n" + "Either " + server + " is refusing conections \n"
			                + "or there is no internet connection. \n" + "Try another host.",
			        e);
		}
		catch (final IOException e) {
			e.printStackTrace();
			throw new UNISoNException("problem connecting to new server");
		}
	}

	/**
	 * Connect to news group.
	 *
	 * @param host
	 *            the host
	 * @param newsgroup
	 *            the newsgroup
	 * @throws Exception
	 *             the exception
	 */
	@Override
	public void connectToNewsGroup(final String hostInput, final String newsgroup)
	        throws Exception {
		if (!this.isConnected()) {
			this.connect(hostInput);
		}
		this.selectNewsgroup(newsgroup);
	}

	/**
	 * Disconnect from the News Server.
	 */
	@Override
	public void disconnect() {
		try {
			this.client.logout();
			this.client.disconnect();
		}
		catch (final Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Gets the message count.
	 *
	 * @return the message count
	 */
	@Override
	public int getMessageCount() {
		return this.messageCount;
	}

	@Override
	public boolean isConnected() {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * Returns the news groups that satisfy the specified wildcard use asterisk for wild character.
	 *
	 * @param wildcard
	 *            the wildcard
	 * @param nntpserver
	 *            the nntpserver
	 * @return the sets the
	 * @throws UNISoNException
	 *             the UNI so n exception
	 */
	@SuppressWarnings("deprecation")
	@Override
	public Set<NewsGroup> listNewsGroups(final String wildcard, final String nntpserver)
	        throws UNISoNException {

		final Set<NewsGroup> groupSet = new TreeSet<>();

		try {
			this.connect(nntpserver);

			final NewsgroupInfo[] groups = this.client.listNewsgroups(wildcard.toLowerCase());
			NewsClientImpl.logger.info("Number of Groups matching " + wildcard + " on " + nntpserver
			        + ": " + (null != groups ? groups.length : 0));
			if (null != groups) {
				for (final NewsgroupInfo element : groups) {
					if ((element.getArticleCount() > 0)
					        & ((element.getLastArticle() - element.getFirstArticle()) > 0)) {
						groupSet.add(new NewsGroup(element));
					}
				}
			}
		}
		catch (final Exception e) {
			throw new UNISoNException("Failed to connect", e);
		}

		return groupSet;
	}

	@Override
	public int quit() throws IOException {
		// TODO Auto-generated method stub
		return 0;
	}

	/**
	 * Reconnect.
	 *
	 * @throws UNISoNException
	 *             the UNI so n exception
	 */
	@Override
	public void reconnect() throws UNISoNException {
		// If it should be connected but has timed out
		if (!this.isConnected()) {
			try {
				this.connect(this.host);
			}
			catch (final IOException e) {
				throw new UNISoNException("Failed to connect", e);
			}
		}
	}

	@Override
	public Reader retrieveArticle(final String articleId) throws IOException {
		return this.client.retrieveArticle(articleId);
	}

	@Override
	public Reader retrieveArticleHeader(final String articleId) throws IOException {
		return this.client.retrieveArticleHeader(articleId);
	}

	@Override
	public BufferedReader retrieveArticleInfo(final long lowArticleNumber,
	        final long highArticleNumber) throws IOException {
		return this.client.retrieveArticleInfo(lowArticleNumber, highArticleNumber);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.apache.commons.net.nntp.NNTPClient#selectNewsgroup(java.lang.String)
	 */
	@Override
	public boolean selectNewsgroup(final String newsgroup) throws IOException {
		return this.client.selectNewsgroup(newsgroup);
	}

	/**
	 * Sets the message count.
	 *
	 * @param messageCount
	 *            the new message count
	 */
	@Override
	public void setMessageCount(final int messageCount) {
		this.messageCount = messageCount;
	}
} public class StringUtils {

	static final String[] DATE_SEPARATORS = { "/", "-", ".", "," };

	/**
	 * Compress.
	 *
	 * @param str
	 *            the str
	 * @return the byte[]
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static final byte[] compress(final String str) throws IOException {
		final ByteArrayOutputStream out = new ByteArrayOutputStream();
		try (final ZipOutputStream zout = new ZipOutputStream(out);) {
			zout.putNextEntry(new ZipEntry("0"));
			zout.write(str.getBytes());
			zout.closeEntry();
			final byte[] compressed = out.toByteArray();
			return compressed;
		}
	}

	/**
	 * Convert commas to list.
	 *
	 * @param commaSeparatedString
	 *            the comma separated string
	 * @return the list
	 */
	static List<String> convertCommasToList(final String commaSeparatedString) {
		final Vector<String> words = new Vector<>();

		final StringTokenizer tok = new StringTokenizer(commaSeparatedString, ",");
		while (tok.hasMoreTokens()) {
			words.add(tok.nextToken());
		}
		return words;
	}

	/**
	 * Convert date to string.
	 *
	 * @param date
	 *            the date
	 * @return the string
	 */
	/*
	 * Returns an NNTP-format date string. This is only required when clients use the NEWGROUPS or
	 * NEWNEWS methods, therefore rarely: we don't cache any of the variables here.
	 */
	public static String convertDateToString(final Date date) {
		final String NNTP_DATE_FORMAT = "yyyyMMdd HHmmss 'GMT'";

		final DateFormat df = new SimpleDateFormat(NNTP_DATE_FORMAT);
		final Calendar cal = new GregorianCalendar();
		final TimeZone gmt = TimeZone.getTimeZone("GMT");
		cal.setTimeZone(gmt);
		df.setCalendar(cal);
		cal.setTime(date);
		return df.format(date);
	}

	/**
	 * Convert string to list.
	 *
	 * @param field
	 *            the field
	 * @param delimiters
	 *            the delimiters
	 * @return the vector
	 */
	public static Vector<String> convertStringToList(final String field, final String delimiters) {
		final Vector<String> list = new Vector<>();
		if (null == field) {
			return list;
		}
		final StringTokenizer fields = new StringTokenizer(field, delimiters);

		while (fields.hasMoreElements()) {
			final String nextToken = fields.nextToken();
			list.add(nextToken);
			// System.out.println("Adding " + nextToken);
		}
		return list;
	}

	/**
	 * Decompress.
	 *
	 * @param compressed
	 *            the compressed
	 * @return the string
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static final String decompress(final byte[] compressed) throws IOException {
		if (null == compressed) {
			return null;
		}
		try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
		        final ByteArrayInputStream in = new ByteArrayInputStream(compressed);
		        final ZipInputStream zin = new ZipInputStream(in);) {
			final ZipEntry entry = zin.getNextEntry();
			entry.getName();
			final byte[] buffer = new byte[1024];
			int offset = zin.read(buffer);
			while (offset != -1) {
				out.write(buffer, 0, offset);
				offset = zin.read(buffer);
			}
			final String decompressed = out.toString();
			out.close();
			zin.close();
			return decompressed;
		}
	}

	/**
	 * Join.
	 *
	 * @param strings
	 *            the strings
	 * @param delimiter
	 *            the delimiter
	 * @return the string
	 */
	public static String join(final String[] strings, final String delimiter) {
		final StringBuffer buf = new StringBuffer();

		for (int i = 0; i < (strings.length - 1); i++) {
			buf.append(strings[i] + delimiter);
		}
		buf.append(strings[strings.length - 1]);

		return buf.toString();
	}

	/**
	 * Read the properties file on src/main/resource and process to String Array.
	 *
	 * @author Elton <elton_12_nunes@hotmail.com>
	 * @return Return the server list on Array
	 */
	public static String[] loadServerList() {

		final Properties prop = new Properties();
		final String file = "servers.properties";

		try (final InputStream resources = ClassLoader.getSystemResourceAsStream(file);) {
			if (null == resources) {
				throw new IOException("can't find " + file);
			}
			prop.load(resources);
			final List<String> list = StringUtils.convertCommasToList(prop.getProperty("servers"));

			return list.toArray(new String[list.size()]);
		}
		catch (final IOException io) {
			io.printStackTrace();
		}

		return new String[] { "empty" };
	}

	/**
	 * Convert the String with date to Date Object.
	 *
	 * @author Elton <elton_12_nunes@hotmail.com>
	 * @since 11/06/2016
	 * @return Return the date.
	 */
	public static Date stringToDate(final String text) throws UNISoNException {
		StringBuilder pattern = null;

		if ((null == text) || text.equals("")) {
			return null;
		}

		final String yyyy = "yyyy";
		final String dd = "dd";
		final String mm = "MM";

		if (text.length() == 8) {
			try {
				// 20101229
				pattern = new StringBuilder().append(yyyy).append(mm).append(dd);
				final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern.toString());
				final LocalDate localDate = LocalDate.parse(text, formatter);
				return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
			}
			catch (final DateTimeParseException e) {
				// Try other.
			}
			try {
				// 13122010
				pattern = new StringBuilder().append(dd).append(mm).append(yyyy);
				final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern.toString());
				final LocalDate localDate = LocalDate.parse(text, formatter);
				return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
			}
			catch (final DateTimeParseException e) {
				// Try other.
			}
			try {
				// 12201601
				pattern = new StringBuilder().append(mm).append(yyyy).append(dd);
				final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern.toString());
				final LocalDate localDate = LocalDate.parse(text, formatter);
				return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
			}
			catch (final DateTimeParseException e) {
				throw new UNISoNException("Failed to parse date:" + text, e);
			}
		}
		else if (text.length() == 10) {
			for (final String separator : StringUtils.DATE_SEPARATORS) {
				if (text.contains(separator)) {
					try {
						// Ex.12/05/1994
						pattern = new StringBuilder().append(dd).append(separator).append(mm)
						        .append(separator).append(yyyy);
						final DateTimeFormatter formatter = DateTimeFormatter
						        .ofPattern(pattern.toString());
						final LocalDate localDate = LocalDate.parse(text, formatter);
						return Date
						        .from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
					}
					catch (final DateTimeParseException e) {
					}
					try {
						// Ex.1994/12/05
						pattern = new StringBuilder().append(yyyy).append(separator).append(mm)
						        .append(separator).append(dd);
						final DateTimeFormatter formatter = DateTimeFormatter
						        .ofPattern(pattern.toString());
						final LocalDate localDate = LocalDate.parse(text, formatter);
						return Date
						        .from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
					}
					catch (final DateTimeParseException e) {
					}
					try {
						// Ex.12/1994/05
						pattern = new StringBuilder().append(mm).append(separator).append(yyyy)
						        .append(separator).append(dd);
						final DateTimeFormatter formatter = DateTimeFormatter
						        .ofPattern(pattern.toString());
						final LocalDate localDate = LocalDate.parse(text, formatter);
						return Date
						        .from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
					}
					catch (final DateTimeParseException e) {
					}
				}
			}
			throw new UNISoNException("Failed to parse date:" + text);
		}
		else {
			String dateText = text.trim();
			if (dateText.substring(0, 1).matches("[a-zA-Z]") && dateText.contains("(")) {
				// Sun, 18 Jan 2015 23:40:56 +0000 (UTC)
				// TRIM TO
				// 18 Jan 2015 23:40:56 +0000
				dateText = dateText.substring(4, dateText.indexOf("(")).trim();

				final DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME;
				final ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateText, formatter);
				return Date.from(zonedDateTime.toInstant());
			}
			else if (dateText.substring(0, 1).matches("[a-zA-Z]")) {
				dateText = dateText.substring(4, dateText.length()).trim();
				// Sun, 18 Jan 2015 23:40:56 +0000
				// TRIM TO
				// 18 Jan 2015 23:40:56 +0000
				final DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME;
				final ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateText, formatter);
				return Date.from(zonedDateTime.toInstant());
			}
			else {
				// 30 Jan 2015 23:37:13 GMT
				final DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME;
				final LocalDate localDate = LocalDate.parse(dateText, formatter);
				return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
			}
		}
	}

} public class TreeNode extends DefaultMutableTreeNode {

	/** The Constant serialVersionUID. */
	private static final long	serialVersionUID	= -8471464838090837440L;

	/** The node name. */
	private String				nodeName;

	/**
	 * Instantiates a new tree node.
	 *
	 * @param childObject
	 *            the child object
	 * @param name
	 *            the name
	 */
	public TreeNode(final Object childObject, final String name) {
		super(childObject);
		this.nodeName = name;
	}

	/**
	 * Sets the name.
	 *
	 * @param name
	 *            the new name
	 */
	public void setName(final String name) {
		this.nodeName = name;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.swing.tree.DefaultMutableTreeNode#toString()
	 */
	@Override
	public String toString() {
		return this.nodeName;
	}
}
import java.awt.Dimension;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.Vector;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListModel;
import javax.swing.ListModel;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.Session;
import uk.co.sleonard.unison.UNISoNController;
import uk.co.sleonard.unison.UNISoNException;
import uk.co.sleonard.unison.UNISoNLogger;
import uk.co.sleonard.unison.datahandling.DAO.DownloadRequest.DownloadMode;
import uk.co.sleonard.unison.datahandling.DAO.GUIItem;
import uk.co.sleonard.unison.datahandling.DAO.Location;
import uk.co.sleonard.unison.datahandling.DAO.Message;
import uk.co.sleonard.unison.datahandling.DAO.NewsGroup;
import uk.co.sleonard.unison.datahandling.DAO.ResultRow;
import uk.co.sleonard.unison.datahandling.DAO.Topic;
import uk.co.sleonard.unison.datahandling.DAO.UsenetUser;
import uk.co.sleonard.unison.input.FullDownloadWorker;
import uk.co.sleonard.unison.input.NewsClientImpl;
import uk.co.sleonard.unison.utils.StringUtils;
import uk.co.sleonard.unison.utils.TreeNode;
/*
 * MessageStoreViewer.java
 *
 * Created on 28 November 2007, 09:04
 */

package uk.co.sleonard.unison.gui.generated;





/**
 * The Class MessageStoreViewer.
 *
 * @author Stephen <github@leonarduk.com>
 * @since v1.0.0
 *
 */
class MessageStoreViewer extends javax.swing.JPanel implements Observer, UNISoNLogger {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = -4431795072981463365L;

	/** The body pane. */
	// Variables declaration - do not modify//GEN-BEGIN:variables
	private javax.swing.JTextArea bodyPane;

	/** The body scroll pane. */
	private javax.swing.JScrollPane bodyScrollPane;

	/** The crosspost combo box. */
	private javax.swing.JComboBox<NewsGroup> crosspostComboBox;

	/** The filter toggle. */
	private javax.swing.JToggleButton filterToggle;

	/** The from date field. */
	private javax.swing.JTextField fromDateField;

	/** The from date label. */
	private javax.swing.JLabel fromDateLabel;

	/** The get body button. */
	private javax.swing.JButton getBodyButton;

	/** The groups hierarchy. */
	private javax.swing.JTree groupsHierarchy;

	/** The groups scroll pane. */
	private javax.swing.JScrollPane groupsScrollPane;

	/** The headers button. */
	private javax.swing.JButton headersButton;

	/** The location field. */
	private javax.swing.JTextField locationField;

	/** The location label. */
	private javax.swing.JLabel locationLabel;

	/** The missing messages check. */
	private javax.swing.JCheckBox missingMessagesCheck;

	/** The refresh button. */
	private javax.swing.JButton refreshButton;

	/** The sender field. */
	private javax.swing.JTextField senderField;

	/** The sender label. */
	private javax.swing.JLabel senderLabel;

	/** The sent date field. */
	private javax.swing.JTextField sentDateField;

	/** The sent date label. */
	private javax.swing.JLabel sentDateLabel;

	/** The stats tab pane. */
	private javax.swing.JTabbedPane statsTabPane;

	/** The subject field. */
	private javax.swing.JTextField subjectField;

	/** The subject label. */
	private javax.swing.JLabel subjectLabel;

	/** The to date field. */
	private javax.swing.JTextField toDateField;

	/** The todate label. */
	private javax.swing.JLabel todateLabel;

	/** The top countries list. */
	private javax.swing.JList<GUIItem<Object>> topCountriesList;

	/** The top countries scroll pane. */
	private javax.swing.JScrollPane topCountriesScrollPane;

	/** The top groups list. */
	private javax.swing.JList<GUIItem<Object>> topGroupsList;

	/** The top groups scroll pane. */
	private javax.swing.JScrollPane topGroupsScrollPane;

	/** The top posters list. */
	private javax.swing.JList<GUIItem<Object>> topPostersList;

	/** The top posters scroll pane. */
	private javax.swing.JScrollPane topPostersScrollPane;

	/** The topics hierarchy. */
	private javax.swing.JTree topicsHierarchy;

	/** The topics scroll pane. */
	private javax.swing.JScrollPane topicsScrollPane;

	/** The session. */
	private Session session;

	/** The newsgroup tree root. */
	private TreeNode newsgroupTreeRoot;

	/** The topic root. */
	private TreeNode topicRoot;

	private final UNISoNController controller;

	// End of variables declaration//GEN-END:variables
	/**
	 * Creates new form MessageStoreViewer.
	 */
	public MessageStoreViewer() {
		this.initComponents();

		final Dimension size = this.sentDateField.getPreferredSize();
		this.sentDateField.setMaximumSize(size);
		this.sentDateField.setPreferredSize(size);
		this.subjectField.setMaximumSize(size);
		this.subjectField.setPreferredSize(size);
		this.senderField.setMaximumSize(size);
		this.senderField.setPreferredSize(size);
		this.controller = UNISoNController.getInstance();

		try {

			this.session = this.controller.helper().getHibernateSession();

			// FIXME disable all non-workng parts
			// headersButton.setVisible(false);
			this.getBodyButton.setVisible(false);
			this.missingMessagesCheck.setVisible(false);

			this.switchFilter(this.filterToggle.isSelected());
		}
		catch (final UNISoNException e) {
			this.controller.getGui().showAlert("Error :" + e.getMessage());
		}
	}

	/**
	 * Adds the child node.
	 *
	 * @param root
	 *            the root
	 * @param childObject
	 *            the child object
	 * @return the tree node
	 */
	private TreeNode addChildNode(final TreeNode root, final Object childObject) {
		return this.addChildNode(root, childObject, "");
	}

	/**
	 * Adds the child node.
	 *
	 * @param root
	 *            the root
	 * @param childObject
	 *            the child object
	 * @param name
	 *            the name
	 * @return the tree node
	 */
	private TreeNode addChildNode(final TreeNode root, final Object childObject,
	        final String nameInput) {
		String name = nameInput;
		if (childObject instanceof Set<?>) {
			if (((Set<?>) childObject).size() == 0) {
				// if no entries then don't add it
				return null;
			}
		}
		else if (childObject instanceof String) {
			name += " : " + childObject;
		}
		else {
			name += UNISoNController.getInstance().helper().getText(childObject);
		}

		final TreeNode child = new TreeNode(childObject, name);
		root.add(child);

		return child;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see uk.co.sleonard.unison.gui.UNISoNLogger#alert(java.lang.String)
	 */
	@Override
	public void alert(final String message) {
		this.log(message);
		this.controller.getGui().showAlert(message);
	}

	/**
	 * Creates the message hierarchy.
	 *
	 * @param set
	 *            the set
	 * @param root
	 *            the root
	 * @param matchId
	 *            the match id
	 * @param fillInMissing
	 *            the fill in missing
	 * @return the sets the
	 */
	private Set<Message> createMessageHierarchy(final Set<Message> set, final TreeNode root,
	        final Object matchId, final boolean fillInMissing) {
		final Set<TreeNode> matches = new HashSet<>();
		final Set<Message> copy = new HashSet<>(set);

		for (final Message next : set) {
			// compare to the last refered message, ie. the one they replied to
			String previousId = "ROOT";
			try {
				final List<String> msgList = StringUtils
				        .convertStringToList(next.getReferencedMessages(), " ");

				if (msgList.size() > 0) {
					final String lastMessageId = msgList.get(0);
					if (fillInMissing) {
						previousId = lastMessageId;

					}
					// else ignore it and add to root
				}
			}
			catch (final ObjectNotFoundException e) {
				e.printStackTrace();
			}

			// if it matches then it refers to previous so add as a child to
			// previous
			if (previousId.equals(matchId)) {
				final TreeNode child = this.addChildNode(root, next);
				matches.add(child);
				copy.remove(next);
			}
		}
		Set<Message> remainder = new HashSet<>(copy);
		for (final TreeNode next : matches) {
			remainder = this.createMessageHierarchy(remainder, next,
			        ((Message) next.getUserObject()).getUsenetMessageID(), fillInMissing);
		}
		return copy;
	}

	/**
	 * Crosspost combo box action performed.
	 *
	 * @param evt
	 *            the evt
	 */
	private void crosspostComboBoxActionPerformed(final java.awt.event.ActionEvent evt) {// GEN-FIRST:event_crosspostComboBoxActionPerformed
		final UNISoNController controller = UNISoNController.getInstance();
		final NewsGroup selectedGroup = (NewsGroup) this.crosspostComboBox.getSelectedItem();
		controller.getFilter().setSelectedNewsgroup(selectedGroup);
		this.refreshTopicHierarchy();
		// controller.showAlert("You chose " + selectedGroup);
	}// GEN-LAST:event_crosspostComboBoxActionPerformed

	/**
	 * Expand node.
	 *
	 * @param root
	 *            the root
	 * @param fillInMissing
	 *            the fill in missing
	 */
	private void expandNode(final TreeNode root, final boolean fillInMissing) {

		final Object userObject = root.getUserObject();
		if (userObject instanceof Topic) {
			final Topic topic = (Topic) userObject;
			this.createMessageHierarchy(
			        UNISoNController.getInstance().getDatabase().getMessages(topic, this.session),
			        root, "ROOT", fillInMissing);
		}
	}

	/**
	 * Filter toggle action performed.
	 *
	 * @param evt
	 *            the evt
	 */
	private void filterToggleActionPerformed(final java.awt.event.ActionEvent evt) {// GEN-FIRST:event_filterToggleActionPerformed
		try {
			this.switchFilter(this.filterToggle.isSelected());
		}
		catch (final UNISoNException e) {
			this.controller.getGui().showAlert("Error :" + e.getMessage());
		}
	}// GEN-LAST:event_filterToggleActionPerformed

	/**
	 * Gets the body button action performed.
	 *
	 * @param evt
	 *            the evt
	 * @return the body button action performed
	 */
	private void getBodyButtonActionPerformed(final java.awt.event.ActionEvent evt) {// GEN-FIRST:event_getBodyButtonActionPerformed
		// TODO add your handling code here:
	}// GEN-LAST:event_getBodyButtonActionPerformed

	/**
	 * Gets the list model.
	 *
	 * @param results
	 *            the results
	 * @return the list model
	 */
	private ListModel<GUIItem<Object>> getListModel(final List<ResultRow> results) {
		final DefaultListModel<GUIItem<Object>> model = new DefaultListModel<>();
		for (final ListIterator<ResultRow> iter = results.listIterator(); iter.hasNext();) {
			final Object next = iter.next();
			String name = next.toString();
			if (next instanceof UsenetUser) {
				name = ((UsenetUser) next).getName() + "<" + ((UsenetUser) next).getEmail() + ">";
			}
			else if (next instanceof Location) {